diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac76fcbf47..1fd3651c57 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Build 6.1 +name: Build 6.1 on: workflow_call: @@ -22,32 +22,63 @@ on: jobs: build: - runs-on: ubuntu-latest env: - DO_DEPLOY: "${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/6.1') }}" + DO_DEPLOY: "${{ github.event_name == 'push' && startsWith(github.ref, 'refs/heads/6.2') }}" LUCEE_BUILD_JAVA_VERSION: 21 LUCEE_TEST_JAVA_VERSION: '' services: - ldap: - # image: kwart/ldap-server - image: rroemhild/test-openldap - ports: - - 10389:10389 - - 10636:10636 - sql-server: - # Docker Hub image - image: mcr.microsoft.com/mssql/server:2019-latest + ldap_with_creds: + image: ${{ (github.secret_source != 'none') && 'rroemhild/test-openldap' || '' }} + ports: + - 10389:10389 + - 10636:10636 + credentials: + username: ${{ secrets.DOCKERHUB_PUBLIC_USERNAME || ' ' }} # empty string to avoid invalid template + password: ${{ secrets.DOCKERHUB_PUBLIC_TOKEN || ' ' }} + ldap_no_creds: + image: ${{ (github.secret_source == 'none') && 'rroemhild/test-openldap' || '' }} + ports: + - 10389:10389 + - 10636:10636 + sql-server_with_creds: + image: ${{ (github.secret_source != 'none') && 'mcr.microsoft.com/mssql/server:2022-latest' || '' }} + env: + MSSQL_PID: Express + ACCEPT_EULA: Y + SA_PASSWORD: Lucee!1433 # password must be complex or the service won't start + ports: + - 1433:1433 + options: --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P ${SA_PASSWORD} -Q 'SELECT 1' || exit 1" --health-interval 10s --health-timeout 5s --health-retries 5 + credentials: + username: ${{ secrets.DOCKERHUB_PUBLIC_USERNAME || ' ' }} + password: ${{ secrets.DOCKERHUB_PUBLIC_TOKEN || ' ' }} + sql-server_without_creds: + image: ${{ (github.secret_source == 'none') && 'mcr.microsoft.com/mssql/server:2022-latest' || '' }} env: MSSQL_PID: Express ACCEPT_EULA: Y SA_PASSWORD: Lucee!1433 # password must be complex or the service won't start ports: - 1433:1433 - options: --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P ${SA_PASSWORD} -Q 'SELECT 1' || exit 1" --health-interval 10s --health-timeout 5s --health-retries 5 - redis: + options: --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U SA -P ${SA_PASSWORD} -Q 'SELECT 1' || exit 1" --health-interval 10s --health-timeout 5s --health-retries 5 + redis_with_creds: + image: ${{ (github.secret_source != 'none') && 'redis' || '' }} + # Set health checks to wait until redis has started + options: >- + --health-cmd "redis-cli ping" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + # Maps port 6379 on service container to the host + - 6379:6379 + credentials: + username: ${{ secrets.DOCKERHUB_PUBLIC_USERNAME || ' ' }} + password: ${{ secrets.DOCKERHUB_PUBLIC_TOKEN || ' ' }} + redis_without_creds: # Docker Hub image - image: redis + image: ${{ (github.secret_source == 'none') && 'redis' || '' }} # Set health checks to wait until redis has started options: >- --health-cmd "redis-cli ping" @@ -57,8 +88,8 @@ jobs: ports: # Maps port 6379 on service container to the host - 6379:6379 - greenmail: - image: greenmail/standalone:1.6.9 + greenmail_with_creds: + image: ${{ (github.secret_source != 'none') && 'greenmail/standalone:1.6.9' || '' }} ports: - 3025:3025 #SMTP - 3110:3110 #POP3 @@ -67,9 +98,31 @@ jobs: - 3993:3993 #IMAPS - 3995:3995 #POP3S - 8080:8080 #API + credentials: + username: ${{ secrets.DOCKERHUB_PUBLIC_USERNAME || ' ' }} + password: ${{ secrets.DOCKERHUB_PUBLIC_TOKEN || ' ' }} + greenmail_no_creds: + image: ${{ (github.secret_source == 'none') && 'greenmail/standalone:1.6.9' || '' }} + ports: + - 3025:3025 #SMTP + - 3110:3110 #POP3 + - 3143:3143 #IMAP + - 3465:3465 #SMTPS + - 3993:3993 #IMAPS + - 3995:3995 #POP3S + - 8080:8080 #API + steps: # when workflow is run via a workflow_call, these vars are found under input, which doesn't exist otherwise # so lets copy them over to the normal env vars + + - name: Login to Docker Hub + uses: docker/login-action@v3 + if: ${{ github.secret_source != 'none' }} + with: + username: ${{ secrets.DOCKERHUB_PUBLIC_USERNAME }} + password: ${{ secrets.DOCKERHUB_PUBLIC_TOKEN }} + - name: Configure Build Java Version if: ${{ inputs.LUCEE_BUILD_JAVA_VERSION != '' }} run: echo "LUCEE_BUILD_JAVA_VERSION=${{ inputs.LUCEE_BUILD_JAVA_VERSION }}" >> $GITHUB_ENV diff --git a/.github/workflows/osv-scanner.yml b/.github/workflows/osv-scanner.yml index 6037873432..c4db6492bb 100644 --- a/.github/workflows/osv-scanner.yml +++ b/.github/workflows/osv-scanner.yml @@ -20,6 +20,6 @@ jobs: with: go-version: '>=1.21.6' - name: Install Google OSV Scanner - run: go install github.com/google/osv-scanner/cmd/osv-scanner@v1 + run: go install github.com/google/osv-scanner/cmd/osv-scanner@v1.8.1 - name: Run OSV vulnerabilities Scanner run: 'PATH="${PATH}:$(go env GOPATH)/bin" osv-scanner -r ${{ github.workspace }}' diff --git a/ant/build-core.xml b/ant/build-core.xml index 3cf44725c2..6fcd17457d 100644 --- a/ant/build-core.xml +++ b/ant/build-core.xml @@ -798,7 +798,6 @@ name="buildLoaderMaven" depends="_buildLoaderMaven,upload_to_s3" description="generate the loader jar" > - @@ -900,45 +899,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -954,7 +915,7 @@ after:${goal} - + @@ -963,7 +924,7 @@ - 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 cd; + private Struct properties; + private String _default; + private String name; + + public AIEngineFactory(ClassDefinition 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 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 it = data.valueIterator(); + List list = new ArrayList<>(); + while (it.hasNext()) { + + list.add(new GeminiModel(Caster.toStruct(it.next()), charset)); + } + return list; + } + throw new ApplicationException("Chat GPT did answer with the mime type [" + ct.getMimeType() + "] that is not supported, only [application/json] is supported"); + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } +} diff --git a/core/src/main/java/lucee/runtime/ai/google/GeminiModel.java b/core/src/main/java/lucee/runtime/ai/google/GeminiModel.java new file mode 100644 index 0000000000..0cfeb39cd2 --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/google/GeminiModel.java @@ -0,0 +1,20 @@ +package lucee.runtime.ai.google; + +import lucee.runtime.ai.AIModelSupport; +import lucee.runtime.exp.PageException; +import lucee.runtime.op.Caster; +import lucee.runtime.type.Struct; +import lucee.runtime.type.util.KeyConstants; + +public class GeminiModel extends AIModelSupport { + + public GeminiModel(Struct raw, String charset) throws PageException { + // TODO make split better + super(Caster.toString(raw.get(KeyConstants._name)).substring("models/".length()), raw, charset); + } + + @Override + public String getLabel() { + return Caster.toString(raw.get(KeyConstants._displayName, null), null); + } +} diff --git a/core/src/main/java/lucee/runtime/ai/google/GeminiResponse.java b/core/src/main/java/lucee/runtime/ai/google/GeminiResponse.java new file mode 100644 index 0000000000..11d4841716 --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/google/GeminiResponse.java @@ -0,0 +1,58 @@ +package lucee.runtime.ai.google; + +import lucee.commons.io.CharsetUtil; +import lucee.runtime.ai.Response; +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.Array; +import lucee.runtime.type.Struct; +import lucee.runtime.type.util.KeyConstants; + +public class GeminiResponse implements Response { + + private Struct raw; + private String charset; + + public GeminiResponse(Struct raw, String charset) { + this.raw = raw; + this.charset = charset; + } + + @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 String getAnswer() { + Array arr = Caster.toArray(raw.get("candidates", null), null); + + if (arr == null) return null; + Struct sct = Caster.toStruct(arr.get(1, null), null); + if (sct == null) return null; + sct = Caster.toStruct(sct.get("content", null), null); + if (sct == null) return null; + + arr = Caster.toArray(sct.get("parts", null), null); + if (arr == null) return null; + + sct = Caster.toStruct(arr.get(1, null), null); + if (sct == null) return null; + + return Caster.toString(sct.get(KeyConstants._text, null), null); + } + + public Struct getData() { + return raw; + } + +} diff --git a/core/src/main/java/lucee/runtime/ai/google/GeminiSession.java b/core/src/main/java/lucee/runtime/ai/google/GeminiSession.java new file mode 100644 index 0000000000..3dece0aeef --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/google/GeminiSession.java @@ -0,0 +1,166 @@ +package lucee.runtime.ai.google; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +import lucee.commons.io.CharsetUtil; +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.AIEngineSupport; +import lucee.runtime.ai.AIResponseListener; +import lucee.runtime.ai.AISessionSupport; +import lucee.runtime.ai.AIUtil; +import lucee.runtime.ai.Conversation; +import lucee.runtime.ai.ConversationImpl; +import lucee.runtime.ai.RequestSupport; +import lucee.runtime.ai.Response; +import lucee.runtime.converter.JSONConverter; +import lucee.runtime.converter.JSONDateFormat; +import lucee.runtime.exp.ApplicationException; +import lucee.runtime.exp.PageException; +import lucee.runtime.interpreter.JSONExpressionInterpreter; +import lucee.runtime.listener.SerializationSettings; +import lucee.runtime.op.Caster; +import lucee.runtime.type.Array; +import lucee.runtime.type.ArrayImpl; +import lucee.runtime.type.Struct; +import lucee.runtime.type.StructImpl; +import lucee.runtime.type.util.KeyConstants; + +public class GeminiSession extends AISessionSupport { + private GeminiEngine geminiEngine; + private String initalMessage; + + public GeminiSession(GeminiEngine engine, String initalMessage, long timeout) { + super(engine, timeout); + this.geminiEngine = engine; + this.initalMessage = initalMessage; + } + + @Override + public Response inquiry(String message) throws PageException { + return inquiry(message, null); + } + + @Override + public Response inquiry(String message, AIResponseListener listener) throws PageException { + try { + // if (listener != null) throw new ApplicationException("listener not supported yet."); + Struct root = new StructImpl(StructImpl.TYPE_LINKED); + + // contents + Array contents = new ArrayImpl(); + root.set(KeyConstants._contents, contents); + + // add system + if (!StringUtil.isEmpty(initalMessage, true)) { + contents.append(createParts("user", initalMessage)); + } + + // Add conversation history + for (Conversation c: getHistoryAsList()) { // question msg = new StructImpl(); + contents.append(createParts("user", c.getRequest().getQuestion())); + contents.append(createParts("model", c.getResponse().getAnswer())); + } + + // https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=rest&hl=de + // curl + + // parts + contents.append(createParts("user", message)); + + JSONConverter json = new JSONConverter(true, CharsetUtil.UTF8, JSONDateFormat.PATTERN_CF, false); + String str = json.serialize(null, root, SerializationSettings.SERIALIZE_AS_COLUMN, null); + URL url = geminiEngine.toURL(geminiEngine.baseURL, GeminiEngine.CHAT, listener != null ? GeminiEngine.TYPE_STREAM : GeminiEngine.TYPE_REG); + HTTPResponse rsp = HTTPEngine4Impl.post(url, null, null, getTimeout(), false, geminiEngine.mimetype, geminiEngine.charset, AIEngineSupport.DEFAULT_USERAGENT, + geminiEngine.proxy, new Header[] { + + // new HeaderImpl("Authorization", "Bearer " + geminiEngine.apikey), + + new HeaderImpl("Content-Type", "application/json") + + }, geminiEngine.formfields, str); + + ContentType ct = rsp.getContentType(); + + // stream true + if ("text/event-stream".equals(ct.getMimeType())) { + String cs = ct.getCharset(); + if (Util.isEmpty(cs, true)) cs = geminiEngine.charset; + JSONExpressionInterpreter interpreter = new JSONExpressionInterpreter(); + GeminiStreamResponse response = new GeminiStreamResponse(cs, listener); + try (BufferedReader reader = new BufferedReader( + cs == null ? new InputStreamReader(rsp.getContentAsStream()) : new InputStreamReader(rsp.getContentAsStream(), cs))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.startsWith("data: ")) continue; + line = line.substring(6); + response.addPart(Caster.toStruct(interpreter.interpret(null, line))); + } + } + catch (Exception e) { + throw Caster.toPageException(e); + } + getHistoryAsList().add(new ConversationImpl(new RequestSupport(message), response)); + return response; + } + + else if ("application/json".equals(ct.getMimeType())) { + String cs = ct.getCharset(); + if (Util.isEmpty(cs, true)) cs = geminiEngine.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) { + String msg = Caster.toString(err.get(KeyConstants._message, null), null); + String code = Caster.toString(err.get(KeyConstants._code, null), null); + String status = Caster.toString(err.get(KeyConstants._status, null), null); + if (!StringUtil.isEmpty(msg, true)) throw AIUtil.toException(getEngine(), msg, status, code); + throw AIUtil.toException(getEngine(), getEngine().getLabel() + " did reposne with status code [" + rsp.getStatusCode() + "]", null, + Caster.toString(rsp.getStatusCode(), null)); + } + + GeminiResponse response = new GeminiResponse(raw, cs); + getHistoryAsList().add(new ConversationImpl(new RequestSupport(message), response)); + return response; + + } + else { + String cs = ct.getCharset(); + if (Util.isEmpty(cs, true)) cs = geminiEngine.charset; + throw new ApplicationException("Gemini did answer with the mime type [" + ct.getMimeType() + "] that is not supported, only [application/json] is supported"); + } + } + catch ( + + Exception e) { + throw Caster.toPageException(e); + } + } + + private Struct createParts(String role, String msg) throws PageException { + Struct sctContents = new StructImpl(StructImpl.TYPE_LINKED); + + Array parts = new ArrayImpl(); + Struct sct = new StructImpl(StructImpl.TYPE_LINKED); + parts.append(sct); + sct.set(KeyConstants._text, msg); + + if (role != null) sctContents.set(KeyConstants._role, role.trim()); + sctContents.set(KeyConstants._parts, parts); + + return sctContents; + } + + @Override + public void release() { + // nothing to give up + } + +} diff --git a/core/src/main/java/lucee/runtime/ai/google/GeminiStreamResponse.java b/core/src/main/java/lucee/runtime/ai/google/GeminiStreamResponse.java new file mode 100644 index 0000000000..abb41f67ab --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/google/GeminiStreamResponse.java @@ -0,0 +1,70 @@ +package lucee.runtime.ai.google; + +import lucee.commons.io.CharsetUtil; +import lucee.runtime.ai.AIResponseListener; +import lucee.runtime.ai.Response; +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.Array; +import lucee.runtime.type.ArrayImpl; +import lucee.runtime.type.Struct; +import lucee.runtime.type.util.KeyConstants; + +public class GeminiStreamResponse implements Response { + + private Array raw = new ArrayImpl(); + + private String charset; + private StringBuilder answer = new StringBuilder(); + + private AIResponseListener listener; + + public GeminiStreamResponse(String charset, AIResponseListener listener) { + this.charset = charset; + this.listener = listener; + } + + @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 String getAnswer() { + return answer.toString(); + } + + public Array getData() { + return raw; + } + + public void addPart(Struct part) { + raw.appendEL(part); + Array arr = Caster.toArray(part.get("candidates", null), null); + if (arr == null) return; + Struct sct = Caster.toStruct(arr.get(1, null), null); + if (sct == null) return; + sct = Caster.toStruct(sct.get("content", null), null); + if (sct == null) return; + + arr = Caster.toArray(sct.get("parts", null), null); + if (arr == null) return; + + sct = Caster.toStruct(arr.get(1, null), null); + if (sct == null) return; + + String str = Caster.toString(sct.get(KeyConstants._text, null), null); + if (listener != null) listener.listen(str); + answer.append(str); + } + +} diff --git a/core/src/main/java/lucee/runtime/ai/openai/ChatGPTEngine.java b/core/src/main/java/lucee/runtime/ai/openai/ChatGPTEngine.java deleted file mode 100644 index a49ec5e79c..0000000000 --- a/core/src/main/java/lucee/runtime/ai/openai/ChatGPTEngine.java +++ /dev/null @@ -1,184 +0,0 @@ -package lucee.runtime.ai.openai; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import lucee.print; -import lucee.commons.io.CharsetUtil; -import lucee.commons.io.IOUtil; -import lucee.commons.io.log.LogUtil; -import lucee.commons.io.res.ContentType; -import lucee.commons.io.res.ResourcesImpl; -import lucee.commons.net.HTTPUtil; -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.Request; -import lucee.runtime.ai.Response; -import lucee.runtime.converter.JSONConverter; -import lucee.runtime.converter.JSONDateFormat; -import lucee.runtime.exp.ApplicationException; -import lucee.runtime.exp.PageException; -import lucee.runtime.interpreter.JSONExpressionInterpreter; -import lucee.runtime.listener.SerializationSettings; -import lucee.runtime.net.proxy.ProxyData; -import lucee.runtime.op.Caster; -import lucee.runtime.type.Array; -import lucee.runtime.type.ArrayImpl; -import lucee.runtime.type.Struct; -import lucee.runtime.type.StructImpl; -import lucee.runtime.type.util.KeyConstants; - -public class ChatGPTEngine implements AIEngine { - private static final URL DEFAULT_URL; - private static final int DEFAULT_TIMEOUT = 0; - private static final String DEFAULT_CHARSET = null; - private static final String DEFAULT_MIMETYPE = null; - private static final String DEFAULT_USERAGENT = "Lucee (AI Request)"; - private static final String DEFAULT_MODEL = "gpt-3.5-turbo"; - - static { - URL tmp = null; - try { - tmp = new URL("https://api.openai.com/v1/chat/completions"); - } - catch (MalformedURLException e) { - log(e); - } - DEFAULT_URL = tmp; - } - - private Struct properties; - private URL url; - private String secretKey; - private int timeout; - private String charset; - private String mimetype; - private ProxyData proxy = null; - private Map formfields = null; - private String model; - private List conversationHistory = new ArrayList<>(); - - @Override - public AIEngine init(Struct properties) throws PageException { - this.properties = properties; - - // URL - String str = Caster.toString(properties.get(KeyConstants._URL, null), null); - if (!Util.isEmpty(str, true)) { - try { - url = HTTPUtil.toURL(str.trim(), HTTPUtil.ENCODED_AUTO); - } - catch (Exception e) { - url = DEFAULT_URL; - } - } - else url = DEFAULT_URL; - - // secret key - str = Caster.toString(properties.get(KeyConstants._secretKey, null), null); - if (Util.isEmpty(str, true)) { - throw new ApplicationException("the property [secretKey] is required for the AI Engine ChatGPT!"); - } - secretKey = str.trim(); - - // timeout - timeout = Caster.toIntValue(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, DEFAULT_MODEL), DEFAULT_MODEL); - return this; - } - - @Override - public Response invoke(Request req) throws PageException { - try { - Array arr = new ArrayImpl(); - // Add conversation history - for (Struct msg: conversationHistory) { - arr.append(msg); - } - // Add new user messages - for (String q: req.getQuestions()) { - Struct msg = new StructImpl(); - msg.set(KeyConstants._role, "user"); - msg.set(KeyConstants._content, q); - arr.append(msg); - } - - Struct sct = new StructImpl(); - sct.set(KeyConstants._model, model); - sct.set(KeyConstants._messages, arr); - - JSONConverter json = new JSONConverter(true, CharsetUtil.UTF8, JSONDateFormat.PATTERN_CF, false); - String str = json.serialize(null, sct, SerializationSettings.SERIALIZE_AS_COLUMN, null); - - HTTPResponse rsp = HTTPEngine4Impl.post(url, null, null, timeout, false, mimetype, charset, DEFAULT_USERAGENT, proxy, - new Header[] { new HeaderImpl("Authorization", "Bearer " + secretKey), new HeaderImpl("Content-Type", "application/json") }, formfields, str); - - 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 ChatGPTUtil.toException(Caster.toString(err.get(KeyConstants._message)), Caster.toString(err.get(KeyConstants._type, null), null), - Caster.toString(err.get(KeyConstants._code, null), null)); - } - - // Add assistant's response to the conversation history - Array choices = Caster.toArray(raw.get(KeyConstants._choices)); - if (choices != null && choices.size() > 0) { - Struct choice = Caster.toStruct(choices.getE(1)); - Struct message = Caster.toStruct(choice.get(KeyConstants._message)); - conversationHistory.add(message); - } - - return new ChatGPTResponse(raw, cs); - } - else { - throw new ApplicationException("Chat GPT did answer with the mime type [" + ct.getMimeType() + "] that is not supported, only [application/json] is supported"); - } - } - catch (Exception e) { - throw Caster.toPageException(e); - } - } - - private static void log(MalformedURLException e) { - LogUtil.log("ai", e); - } - - public static void main(String[] args) throws PageException, IOException { - Struct props = new StructImpl(); - - props.set(KeyConstants._secretKey, ""); - // props.set(KeyConstants._model, "gpt-4"); - - AIEngine ai = new ChatGPTEngine().init(props); - - String code = IOUtil.toString(ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Test/test-cfconfig/webapps/ROOT/test3.cfm"), CharsetUtil.UTF8); - - Request req = new Request(new String[] { "Please analyze the following Lucee (CFML) code for best practices, performance, and security improvements", - " give me suggestions for doc comments", "The code is intended for Lucee version 5.4.4.42.", "keep it as short as possible", "Here is the code:", code }); - Response rsp = ai.invoke(req); - - print.e(rsp.getAnswer()); - print.e("-----------------------------------"); - print.e(rsp); - } -} diff --git a/core/src/main/java/lucee/runtime/ai/openai/ChatGPTUtil.java b/core/src/main/java/lucee/runtime/ai/openai/ChatGPTUtil.java deleted file mode 100644 index 47fc5d4dfa..0000000000 --- a/core/src/main/java/lucee/runtime/ai/openai/ChatGPTUtil.java +++ /dev/null @@ -1,16 +0,0 @@ -package lucee.runtime.ai.openai; - -import lucee.loader.engine.CFMLEngine; -import lucee.loader.engine.CFMLEngineFactory; -import lucee.runtime.exp.PageException; - -public class ChatGPTUtil { - - public static Exception toException(String msg, String type, String code) { - CFMLEngine eng = CFMLEngineFactory.getInstance(); - PageException ae = eng.getExceptionUtil().createApplicationException(msg, "type:" + type + ";code:" + code); - ae.setErrorCode(code); - return ae; - } - -} diff --git a/core/src/main/java/lucee/runtime/ai/openai/OpenAIEngine.java b/core/src/main/java/lucee/runtime/ai/openai/OpenAIEngine.java new file mode 100644 index 0000000000..e9b296c583 --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/openai/OpenAIEngine.java @@ -0,0 +1,507 @@ +package lucee.runtime.ai.openai; + +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.ArrayList; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import lucee.commons.io.CharsetUtil; +import lucee.commons.io.IOUtil; +import lucee.commons.io.res.ContentType; +import lucee.commons.io.res.Resource; +import lucee.commons.lang.StringUtil; +import lucee.commons.net.HTTPUtil; +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.AIEngineFile; +import lucee.runtime.ai.AIEngineSupport; +import lucee.runtime.ai.AIFile; +import lucee.runtime.ai.AIFileSupport; +import lucee.runtime.ai.AIModel; +import lucee.runtime.ai.AISession; +import lucee.runtime.ai.AIUtil; +import lucee.runtime.converter.JSONConverter; +import lucee.runtime.converter.JSONDateFormat; +import lucee.runtime.exp.ApplicationException; +import lucee.runtime.exp.PageException; +import lucee.runtime.interpreter.JSONExpressionInterpreter; +import lucee.runtime.listener.SerializationSettings; +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.StructImpl; +import lucee.runtime.type.util.KeyConstants; + +public class OpenAIEngine extends AIEngineSupport implements AIEngineFile { + // https://platform.openai.com/docs/api-reference/introduction + + private static final long DEFAULT_TIMEOUT = 3000L; + private static final String DEFAULT_CHARSET = null; + private static final String DEFAULT_MIMETYPE = null; + private static final URL DEFAULT_URL_OPENAI; + private static final URL DEFAULT_URL_OLLAMA; + + // TODO + // post https://api.openai.com/v1/audio/speech + // post https://api.openai.com/v1/audio/transcriptions + // post https://api.openai.com/v1/audio/translations + + static { + + // ChatGPT + URL tmp = null; + try { + tmp = new URL("https://api.openai.com/v1/"); + // tmp = new URL("https://api.customopenai.com/v1/chat/completions"); + // https://chatgpt.com/g/g-EFSGvsHVN-lucee + } + catch (MalformedURLException e) { + log(e); + } + DEFAULT_URL_OPENAI = tmp; + + // Ollama (lokal) + tmp = null; + try { + tmp = new URL("http://localhost:11434/v1/"); + // tmp = new URL("https://api.customopenai.com/v1/chat/completions"); + // https://chatgpt.com/g/g-EFSGvsHVN-lucee + } + catch (MalformedURLException e) { + log(e); + } + DEFAULT_URL_OLLAMA = tmp; + + } + + Struct properties; + String secretKey; + long timeout = DEFAULT_TIMEOUT; + String charset; + String mimetype; + ProxyData proxy = null; + Map formfields = null; + String model; + private String systemMessage; + + private URL baseURL; + public Double temperature = null; + + @Override + public AIEngine init(AIEngineFactory factory, Struct properties) throws PageException { + super.init(factory); + this.properties = properties; + + // URL + /// we support some hard coded types to keep it simple + String str = Caster.toString(properties.get(KeyConstants._type, null), null); + if (!Util.isEmpty(str, true)) { + if ("chatgpt".equals(str.trim()) || "openai".equals(str.trim())) baseURL = DEFAULT_URL_OPENAI; + else if ("ollama".equals(str.trim())) baseURL = DEFAULT_URL_OLLAMA; + else throw new ApplicationException( + "ATM only 2 types are supported [openai, ollama], for any other endpoint simply define the attribute `url` that looks like this [https://api.lucee.com/v1/]."); + } + else { + str = Caster.toString(properties.get(KeyConstants._URL, null), null); + if (!Util.isEmpty(str, true)) { + if (!str.endsWith("/")) str += "/"; + try { + baseURL = HTTPUtil.toURL(str.trim(), HTTPUtil.ENCODED_AUTO); + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + else baseURL = DEFAULT_URL_OPENAI; + } + + // secret key + str = Caster.toString(properties.get(KeyConstants._secretKey, null), null); + if (!Util.isEmpty(str, true)) secretKey = str.trim(); + + // 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 = DEFAULT_CHARSET; + // 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); + } + // temperature + temperature = Caster.toDouble(properties.get(KeyConstants._temperature, null), null); + if (temperature != null && (temperature < 0D || temperature > 1D)) { + throw new ApplicationException("temperature has to be a number between 0 and 1, now it is [" + temperature + "]"); + } + + // message + systemMessage = Caster.toString(properties.get(KeyConstants._message, null), null); + return this; + } + + @Override + public AISession createSession(String inialMessage, long timeout) { + return new OpenAISession(this, StringUtil.isEmpty(inialMessage, true) ? systemMessage : inialMessage.trim(), timeout); + } + + @Override + public String getLabel() { + return "ChatGPT"; + } + + @Override + public long getTimeout() { + return timeout; + } + + public URL getBaseURL() { + return baseURL; + } + + @Override + public List getModels() throws PageException { + try { + + URL url = new URL(baseURL, "models"); + HTTPResponse rsp = HTTPEngine4Impl.get(url, null, null, timeout, false, charset, AIEngineSupport.DEFAULT_USERAGENT, proxy, + new Header[] { new HeaderImpl("Authorization", "Bearer " + secretKey), 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))); + throwIfError(raw); + + Array data = Caster.toArray(raw.get(KeyConstants._data)); + Iterator it = data.valueIterator(); + List list = new ArrayList<>(); + while (it.hasNext()) { + list.add(new OpenAIModel(Caster.toStruct(it.next()), charset)); + } + return list; + } + throw new ApplicationException("OpenAI did answer with the mime type [" + ct.getMimeType() + "] that is not supported, only [application/json] is supported"); + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + private void throwIfError(Struct raw) throws PageException { + 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)); + } + } + + public Struct createFineTuningJob(String trainingFileId) throws PageException { + try { + URI url = new URI(getBaseURL() + "fine_tuning/jobs"); + InputStream is = null; + // Create HttpClient + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + // Create HttpPost request + HttpPost post = new HttpPost(url); + post.setHeader("Content-Type", "application/json"); + post.setHeader("Authorization", "Bearer " + secretKey); + + Struct sct = new StructImpl(); + sct.set("training_file", trainingFileId); + sct.set(KeyConstants._model, model); + JSONConverter json = new JSONConverter(true, CharsetUtil.UTF8, JSONDateFormat.PATTERN_CF, false); + String str = json.serialize(null, sct, SerializationSettings.SERIALIZE_AS_COLUMN, null); + StringEntity entity = new StringEntity(str); + post.setEntity(entity); + + // Execute the request + try (CloseableHttpResponse response = httpClient.execute(post)) { + HttpEntity responseEntity = response.getEntity(); + String responseString = EntityUtils.toString(responseEntity, charset); + + Struct raw = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, responseString)); + throwIfError(raw); + return raw; + } + } + finally { + IOUtil.close(is); + } + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + @Override + public String uploadFile(Resource jsonl, String purpose) throws PageException { + if (StringUtil.isEmpty(purpose, true)) purpose = "assistants"; + else purpose = purpose.trim(); + + try { + URI url = new URI(getBaseURL() + "files"); + InputStream is = null; + // Create HttpClient + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + // Create HttpPost request + HttpPost uploadFile = new HttpPost(url); + uploadFile.setHeader("Authorization", "Bearer " + secretKey); + + // Build the multipart entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("purpose", purpose, org.apache.http.entity.ContentType.TEXT_PLAIN); + builder.addBinaryBody("file", is = jsonl.getInputStream(), org.apache.http.entity.ContentType.APPLICATION_OCTET_STREAM, jsonl.getName()); + + HttpEntity multipart = builder.build(); + uploadFile.setEntity(multipart); + + // Execute the request + CloseableHttpResponse response = httpClient.execute(uploadFile); + try { + + // Get response + HttpEntity responseEntity = response.getEntity(); + String responseString = EntityUtils.toString(responseEntity, charset); + + /* + * { "object": "file", "id": "file-NvDokaQZjf06auxzzU5ONayK", "purpose": "fine-tune", "filename": + * "markdown_data.jsonl", "bytes": 179207, "created_at": 1723452279, "status": "processed", + * "status_details": null } + */ + + Struct raw = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, responseString)); + throwIfError(raw); + return Caster.toString(raw.get(KeyConstants._id)); + + } + finally { + response.close(); + } + } + finally { + IOUtil.close(is); + } + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + @Override + public List listFiles() throws PageException { + try { + URI url = new URI(getBaseURL() + "files"); + InputStream is = null; + // Create HttpClient + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + // Create HttpPost request + HttpGet get = new HttpGet(url); + get.setHeader("Authorization", "Bearer " + secretKey); + + // Execute the request + try (CloseableHttpResponse response = httpClient.execute(get)) { + // Get response + HttpEntity responseEntity = response.getEntity(); + List list = new ArrayList<>(); + if ("application/json".equals(responseEntity.getContentType().getValue())) { + String responseString = EntityUtils.toString(responseEntity, charset); + Struct raw = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, responseString)); + throwIfError(raw); + Array data = Caster.toArray(raw.get(KeyConstants._data)); + Iterator it = data.getIterator(); + Struct sct; + while (it.hasNext()) { + sct = Caster.toStruct(it.next()); + list.add(new AIFileSupport( + + Caster.toString(sct.get(KeyConstants._object)), + + Caster.toString(sct.get(KeyConstants._id)), + + Caster.toString(sct.get("purpose")), + + Caster.toString(sct.get(KeyConstants._filename)), + + Caster.toLongValue(sct.get(KeyConstants._bytes)), + + Caster.toDatetime(new Date(Caster.toLongValue(sct.get("created_at")) * 1000L), null), + + Caster.toString(sct.get(KeyConstants._status)), + + Caster.toString(sct.get("status_details", null)))); + + } + } + return list; + } + } + finally { + IOUtil.close(is); + } + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + @Override + public AIFile getFile(String id) throws PageException { + try { + URI url = new URI(getBaseURL() + "files/" + id.trim()); + InputStream is = null; + // Create HttpClient + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + // Create HttpPost request + HttpGet get = new HttpGet(url); + get.setHeader("Authorization", "Bearer " + secretKey); + + // Execute the request + try (CloseableHttpResponse response = httpClient.execute(get)) { + // Get response + HttpEntity responseEntity = response.getEntity(); + if ("application/json".equals(responseEntity.getContentType().getValue())) { + String responseString = EntityUtils.toString(responseEntity, charset); + Struct sct = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, responseString)); + throwIfError(sct); + return new AIFileSupport( + + Caster.toString(sct.get(KeyConstants._object)), + + Caster.toString(sct.get(KeyConstants._id)), + + Caster.toString(sct.get("purpose")), + + Caster.toString(sct.get(KeyConstants._filename)), + + Caster.toLongValue(sct.get(KeyConstants._bytes)), + + Caster.toDatetime(new Date(Caster.toLongValue(sct.get("created_at")) * 1000L), null), + + Caster.toString(sct.get(KeyConstants._status)), + + Caster.toString(sct.get("status_details", null))); + + } + throw new ApplicationException("The AI did answer with the mime type [" + responseEntity.getContentType().getValue() + + "] that is not supported, only [application/json] is supported"); + } + + } + finally { + IOUtil.close(is); + } + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + @Override + public InputStream getFileContent(String id) throws PageException { + try { + URI url = new URI(getBaseURL() + "files/" + id.trim() + "/content"); + + // Create HttpClient + CloseableHttpClient httpClient = HttpClients.createDefault(); + + // Create HttpGet request + HttpGet get = new HttpGet(url); + get.setHeader("Authorization", "Bearer " + secretKey); + + // Execute the request + CloseableHttpResponse response = httpClient.execute(get); + HttpEntity responseEntity = response.getEntity(); + + if ("application/json".equals(responseEntity.getContentType().getValue())) { + String responseString = EntityUtils.toString(responseEntity, charset); + Struct sct = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, responseString)); + throwIfError(sct); + } + + // Return the InputStream, caller is responsible for closing it + return responseEntity.getContent(); + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + @Override + public boolean deleteFile(String id) throws PageException { + try { + URI url = new URI(getBaseURL() + "files/" + id.trim()); + InputStream is = null; + // Create HttpClient + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + // Create HttpPost request + HttpDelete get = new HttpDelete(url); + get.setHeader("Authorization", "Bearer " + secretKey); + + // Execute the request + try (CloseableHttpResponse response = httpClient.execute(get)) { + // Get response + HttpEntity responseEntity = response.getEntity(); + if ("application/json".equals(responseEntity.getContentType().getValue())) { + String responseString = EntityUtils.toString(responseEntity, charset); + Struct sct = Caster.toStruct(new JSONExpressionInterpreter().interpret(null, responseString)); + throwIfError(sct); + return Caster.toBooleanValue(sct.get(KeyConstants._deleted)); + + } + throw new ApplicationException("The AI did answer with the mime type [" + responseEntity.getContentType().getValue() + + "] that is not supported, only [application/json] is supported"); + } + + } + finally { + IOUtil.close(is); + } + + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } +} diff --git a/core/src/main/java/lucee/runtime/ai/openai/OpenAIModel.java b/core/src/main/java/lucee/runtime/ai/openai/OpenAIModel.java new file mode 100644 index 0000000000..898a5e188d --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/openai/OpenAIModel.java @@ -0,0 +1,42 @@ +package lucee.runtime.ai.openai; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import lucee.commons.lang.StringUtil; +import lucee.runtime.ai.AIModelSupport; +import lucee.runtime.exp.PageException; +import lucee.runtime.op.Caster; +import lucee.runtime.type.Struct; +import lucee.runtime.type.util.KeyConstants; +import lucee.runtime.type.util.ListUtil; + +public class OpenAIModel extends AIModelSupport { + + private static Map labels = new ConcurrentHashMap<>(); + + public OpenAIModel(Struct raw, String charset) throws PageException { + super(Caster.toString(raw.get(KeyConstants._id)), raw, charset); + } + + @Override + public String getLabel() { + final String name = getName(); + String label = labels.get(name); + if (label == null) { + synchronized (labels) { + label = labels.get(name); + if (label == null) { + StringBuilder sb = new StringBuilder(); + for (String str: ListUtil.listToList(name, '-', true)) { + if (sb.length() > 0) sb.append('-'); + sb.append(StringUtil.ucFirst(str)); + } + label = sb.toString(); + labels.put(name, label); + } + } + } + return label; + } +} diff --git a/core/src/main/java/lucee/runtime/ai/openai/ChatGPTResponse.java b/core/src/main/java/lucee/runtime/ai/openai/OpenAIResponse.java similarity index 89% rename from core/src/main/java/lucee/runtime/ai/openai/ChatGPTResponse.java rename to core/src/main/java/lucee/runtime/ai/openai/OpenAIResponse.java index 52096b9d0f..ae945702f3 100644 --- a/core/src/main/java/lucee/runtime/ai/openai/ChatGPTResponse.java +++ b/core/src/main/java/lucee/runtime/ai/openai/OpenAIResponse.java @@ -11,12 +11,12 @@ import lucee.runtime.type.Struct; import lucee.runtime.type.util.KeyConstants; -public class ChatGPTResponse implements Response { +public class OpenAIResponse implements Response { private Struct raw; private String charset; - public ChatGPTResponse(Struct raw, String charset) { + public OpenAIResponse(Struct raw, String charset) { this.raw = raw; this.charset = charset; } @@ -44,4 +44,8 @@ public String getAnswer() { return Caster.toString(sct.get(KeyConstants._content, null), null); } + public Struct getData() { + return raw; + } + } diff --git a/core/src/main/java/lucee/runtime/ai/openai/OpenAISession.java b/core/src/main/java/lucee/runtime/ai/openai/OpenAISession.java new file mode 100644 index 0000000000..62fb2ee5ae --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/openai/OpenAISession.java @@ -0,0 +1,164 @@ +package lucee.runtime.ai.openai; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +import lucee.commons.io.CharsetUtil; +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.AIEngineSupport; +import lucee.runtime.ai.AIResponseListener; +import lucee.runtime.ai.AISessionSupport; +import lucee.runtime.ai.AIUtil; +import lucee.runtime.ai.Conversation; +import lucee.runtime.ai.ConversationImpl; +import lucee.runtime.ai.RequestSupport; +import lucee.runtime.ai.Response; +import lucee.runtime.converter.JSONConverter; +import lucee.runtime.converter.JSONDateFormat; +import lucee.runtime.exp.ApplicationException; +import lucee.runtime.exp.PageException; +import lucee.runtime.interpreter.JSONExpressionInterpreter; +import lucee.runtime.listener.SerializationSettings; +import lucee.runtime.op.Caster; +import lucee.runtime.type.Array; +import lucee.runtime.type.ArrayImpl; +import lucee.runtime.type.Struct; +import lucee.runtime.type.StructImpl; +import lucee.runtime.type.util.KeyConstants; + +public class OpenAISession extends AISessionSupport { + + private OpenAIEngine openaiEngine; + private String initalMessage; + + public OpenAISession(OpenAIEngine engine, String initalMessage, long timeout) { + super(engine, timeout); + this.openaiEngine = engine; + this.initalMessage = initalMessage; + } + + @Override + public Response inquiry(String message) throws PageException { + return inquiry(message, null); + } + + @Override + public Response inquiry(String message, AIResponseListener listener) throws PageException { + try { + Struct msg; + Array arr = new ArrayImpl(); + + // add system + if (!StringUtil.isEmpty(initalMessage)) { + msg = new StructImpl(StructImpl.TYPE_LINKED); + msg.set(KeyConstants._role, "system"); + msg.set(KeyConstants._content, initalMessage); + arr.append(msg); + } + + // Add conversation history + for (Conversation c: getHistoryAsList()) { + // question + msg = new StructImpl(StructImpl.TYPE_LINKED); + msg.set(KeyConstants._role, "user"); + msg.set(KeyConstants._content, c.getRequest().getQuestion()); + arr.append(msg); + // answer + msg = new StructImpl(StructImpl.TYPE_LINKED); + msg.set(KeyConstants._role, "assistant"); + msg.set(KeyConstants._content, c.getResponse().getAnswer()); + arr.append(msg); + + } + + // Add new user messages + msg = new StructImpl(StructImpl.TYPE_LINKED); + msg.set(KeyConstants._role, "user"); + msg.set(KeyConstants._content, message); + arr.append(msg); + + Struct sct = new StructImpl(StructImpl.TYPE_LINKED); + sct.set(KeyConstants._model, openaiEngine.model); + sct.set(KeyConstants._messages, arr); + sct.set(KeyConstants._stream, listener != null); + if (openaiEngine.temperature != null) sct.set(KeyConstants._temperature, openaiEngine.temperature); + // TODO response_format + // TODO frequency_penalty + // TODO logit_bias + // TODO logprobs + // TODO top_logprobs + // TODO max_tokens + // TODO presence_penalty + + JSONConverter json = new JSONConverter(true, CharsetUtil.UTF8, JSONDateFormat.PATTERN_CF, false); + String str = json.serialize(null, sct, SerializationSettings.SERIALIZE_AS_COLUMN, null); + + URL url = new URL(openaiEngine.getBaseURL(), "chat/completions"); + HTTPResponse rsp = HTTPEngine4Impl.post(url, null, null, getTimeout(), false, openaiEngine.mimetype, openaiEngine.charset, AIEngineSupport.DEFAULT_USERAGENT, + openaiEngine.proxy, new Header[] { new HeaderImpl("Authorization", "Bearer " + openaiEngine.secretKey), new HeaderImpl("Content-Type", "application/json") }, + openaiEngine.formfields, str); + + ContentType ct = rsp.getContentType(); + // stream false + if ("application/json".equals(ct.getMimeType())) { + String cs = ct.getCharset(); + // getContent(rsp, cs); + if (Util.isEmpty(cs, true)) cs = openaiEngine.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.getEngine(), Caster.toString(err.get(KeyConstants._message)), Caster.toString(err.get(KeyConstants._type, null), null), + Caster.toString(err.get(KeyConstants._code, null), null)); + } + + OpenAIResponse response = new OpenAIResponse(raw, cs); + getHistoryAsList().add(new ConversationImpl(new RequestSupport(message), response)); + return response; + } + // stream true + else if ("text/event-stream".equals(ct.getMimeType())) { + String cs = ct.getCharset(); + if (Util.isEmpty(cs, true)) cs = openaiEngine.charset; + JSONExpressionInterpreter interpreter = new JSONExpressionInterpreter(); + OpenAIStreamResponse response = new OpenAIStreamResponse(cs, listener); + try (BufferedReader reader = new BufferedReader( + cs == null ? new InputStreamReader(rsp.getContentAsStream()) : new InputStreamReader(rsp.getContentAsStream(), cs))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.startsWith("data: ")) continue; + line = line.substring(6); + if ("[DONE]".equals(line)) break; + response.addPart(Caster.toStruct(interpreter.interpret(null, line))); + + } + } + catch (Exception e) { + throw Caster.toPageException(e); + } + getHistoryAsList().add(new ConversationImpl(new RequestSupport(message), response)); + return response; + } + else { + throw new ApplicationException("The AI did answer with the mime type [" + ct.getMimeType() + "] that is not supported, only [application/json] is supported"); + } + } + catch (Exception e) { + throw Caster.toPageException(e); + } + } + + @Override + public void release() { + // nothing to give up + } + +} diff --git a/core/src/main/java/lucee/runtime/ai/openai/OpenAIStreamResponse.java b/core/src/main/java/lucee/runtime/ai/openai/OpenAIStreamResponse.java new file mode 100644 index 0000000000..2e97054d5c --- /dev/null +++ b/core/src/main/java/lucee/runtime/ai/openai/OpenAIStreamResponse.java @@ -0,0 +1,67 @@ +package lucee.runtime.ai.openai; + +import lucee.commons.io.CharsetUtil; +import lucee.runtime.ai.AIResponseListener; +import lucee.runtime.ai.Response; +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.Array; +import lucee.runtime.type.Struct; +import lucee.runtime.type.util.KeyConstants; + +public class OpenAIStreamResponse implements Response { + + private Struct raw = null; + private Array choices = null; + + private String charset; + private StringBuilder answer = new StringBuilder(); + private AIResponseListener listener; + + public OpenAIStreamResponse(String charset, AIResponseListener listener) { + this.charset = charset; + this.listener = listener; + } + + @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 String getAnswer() { + return answer.toString(); + } + + public Struct getData() { + return raw; + } + + public void addPart(Struct part) { + if (raw == null) raw = part; + // raw.appendEL(part); + Array arr = Caster.toArray(part.get("choices", null), null); + // print.e(arr); + if (arr == null) return; + Struct sct = Caster.toStruct(arr.get(1, null), null); + if (sct == null) return; + if (choices == null) choices = arr; + else choices.appendEL(sct); + + sct = Caster.toStruct(sct.get(KeyConstants._delta, null), null); + if (sct == null) return; + String str = Caster.toString(sct.get(KeyConstants._content, null), null); + answer.append(str); + if (listener != null) listener.listen(str); + } + +} diff --git a/core/src/main/java/lucee/runtime/cache/legacy/CacheItemFS.java b/core/src/main/java/lucee/runtime/cache/legacy/CacheItemFS.java index 60362365fb..2db2d21af4 100644 --- a/core/src/main/java/lucee/runtime/cache/legacy/CacheItemFS.java +++ b/core/src/main/java/lucee/runtime/cache/legacy/CacheItemFS.java @@ -91,7 +91,7 @@ public void store(byte[] barr, boolean append) throws IOException { protected static void _flushAll(PageContext pc, Resource dir) throws IOException { if (dir == null) dir = getDirectory(pc); - ResourceUtil.removeChildrenEL(dir); + ResourceUtil.removeChildrenEL(dir, false); } protected static void _flush(PageContext pc, Resource dir, String expireurl) throws IOException { diff --git a/core/src/main/java/lucee/runtime/component/PropertyImpl.java b/core/src/main/java/lucee/runtime/component/PropertyImpl.java index 67907e3841..472a4d31ce 100644 --- a/core/src/main/java/lucee/runtime/component/PropertyImpl.java +++ b/core/src/main/java/lucee/runtime/component/PropertyImpl.java @@ -24,6 +24,7 @@ import lucee.runtime.Component; import lucee.runtime.converter.ConverterException; import lucee.runtime.converter.ScriptConverter; +import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.PageException; import lucee.runtime.op.Caster; import lucee.runtime.op.Duplicator; @@ -153,7 +154,7 @@ public Object getValue() { @Override public Type getASMType() throws PageException { - return ASMUtil.toType(getType(), true); + return ASMUtil.toType(ThreadLocalPageContext.get(), getType(), true); } /** diff --git a/core/src/main/java/lucee/runtime/config/CFConfigImport.java b/core/src/main/java/lucee/runtime/config/CFConfigImport.java index c1d9335572..5f0451838e 100644 --- a/core/src/main/java/lucee/runtime/config/CFConfigImport.java +++ b/core/src/main/java/lucee/runtime/config/CFConfigImport.java @@ -1,11 +1,9 @@ package lucee.runtime.config; -import java.io.File; import java.nio.charset.Charset; import java.util.Iterator; import java.util.Map.Entry; -import lucee.commons.io.DevNullOutputStream; import lucee.commons.io.SystemUtil; import lucee.commons.io.log.LogUtil; import lucee.commons.io.res.Resource; @@ -13,18 +11,14 @@ import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; import lucee.loader.util.Util; -import lucee.runtime.PageContext; -import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.PageException; import lucee.runtime.interpreter.JSONExpressionInterpreter; import lucee.runtime.op.Caster; -import lucee.runtime.thread.SerializableCookie; import lucee.runtime.type.Collection; import lucee.runtime.type.Collection.Key; import lucee.runtime.type.KeyImpl; import lucee.runtime.type.Struct; import lucee.runtime.util.Cast; -import lucee.runtime.util.PageContextUtil; public class CFConfigImport { @@ -98,18 +92,9 @@ public CFConfigImport(Config config, Struct data, Charset charset, String passwo } public Struct execute(boolean throwException) throws PageException { - boolean unregister = false; - PageContext pc = ThreadLocalPageContext.get(); Struct json = null; try { - if (pc == null) { - - pc = PageContextUtil.getPageContext(config, null, (File) SystemUtil.getTempDirectory(), "localhost", "/", "", SerializableCookie.COOKIES0, null, null, null, - DevNullOutputStream.DEV_NULL_OUTPUT_STREAM, true, 100000, false); - unregister = true; - - } if (validatePassword && Util.isEmpty(password)) { String sysprop = "lucee." + type.toUpperCase() + ".admin.password"; String envVarName = sysprop.replace('.', '_').toUpperCase(); @@ -162,12 +147,7 @@ public Struct execute(boolean throwException) throws PageException { if (throwException) { throw Caster.toPageException(t); } - else LogUtil.log(pc, "deploy", t); - } - finally { - if (unregister) { - pc.getConfig().getFactory().releaseLuceePageContext(pc, true); - } + LogUtil.log("deploy", "config-imprt", t); } if (throwException && exd != null) throw exd; return json; diff --git a/core/src/main/java/lucee/runtime/config/ConfigAdmin.java b/core/src/main/java/lucee/runtime/config/ConfigAdmin.java index 36269687d7..3c60f1a120 100755 --- a/core/src/main/java/lucee/runtime/config/ConfigAdmin.java +++ b/core/src/main/java/lucee/runtime/config/ConfigAdmin.java @@ -116,6 +116,7 @@ import lucee.runtime.listener.AppListenerUtil; import lucee.runtime.listener.SerializationSettings; import lucee.runtime.monitor.Monitor; +import lucee.runtime.net.ntp.NtpClient; import lucee.runtime.net.proxy.ProxyData; import lucee.runtime.op.Caster; import lucee.runtime.op.Decision; @@ -399,9 +400,7 @@ public void setMailLog(Config config, String logFile, String level) throws PageE else { setClass(logger, null, "appender", ci.getLogEngine().appenderClassDefintion("resource")); setClass(logger, null, "layout", ci.getLogEngine().layoutClassDefintion("classic")); - Struct args = new StructImpl(); - args.set(KeyConstants._path, logFile); - logger.setEL("appenderArguments", args); + logger.setEL("appenderArguments", "path:" + logFile); } logger.setEL("logLevel", level); } @@ -1190,7 +1189,7 @@ public static void updateJar(Config config, Resource resJar, boolean reloadWhenC if (fileLib.length() != resJar.length()) { IOUtil.closeEL(config.getClassLoader()); ResourceUtil.copy(resJar, fileLib); - if (reloadWhenClassicJar) ConfigWebUtil.reloadLib(config); + // NEXT if (reloadWhenClassicJar) ConfigWebUtil.reloadLib(config); } } @@ -1463,25 +1462,25 @@ else if (access >= SecurityManager.VALUE_1 && access <= SecurityManager.VALUE_10 if (!StringUtil.isEmpty(id)) el.setEL(KeyConstants._id, id); else if (el.containsKey(KeyConstants._id)) el.removeEL(KeyConstants._id); - el.setEL("dsn", dsn); - el.setEL("username", username); - el.setEL("password", ConfigWebUtil.encrypt(password)); + el.setEL(KeyConstants._dsn, dsn); + el.setEL(KeyConstants._username, username); + el.setEL(KeyConstants._password, ConfigWebUtil.encrypt(password)); - el.setEL("host", host); + el.setEL(KeyConstants._host, host); if (!StringUtil.isEmpty(timezone)) el.setEL(KeyConstants._timezone, timezone); else if (el.containsKey(KeyConstants._timezone)) el.removeEL(KeyConstants._timezone); - el.setEL("database", database); - el.setEL("port", Caster.toString(port)); - el.setEL("connectionLimit", Caster.toString(connectionLimit)); - el.setEL("connectionTimeout", Caster.toString(idleTimeout)); - el.setEL("liveTimeout", Caster.toString(liveTimeout)); - el.setEL("metaCacheTimeout", Caster.toString(metaCacheTimeout)); - el.setEL("blob", Caster.toString(blob)); - el.setEL("clob", Caster.toString(clob)); - el.setEL("allow", Caster.toString(allow)); - el.setEL("validate", Caster.toString(validate)); - el.setEL("storage", Caster.toString(storage)); - el.setEL("custom", toStringURLStyle(custom)); + el.setEL(KeyConstants._database, database); + el.setEL(KeyConstants._port, Caster.toString(port)); + el.setEL(KeyConstants._connectionLimit, Caster.toString(connectionLimit)); + el.setEL(KeyConstants._connectionTimeout, Caster.toString(idleTimeout)); + el.setEL(KeyConstants._liveTimeout, Caster.toString(liveTimeout)); + el.setEL(KeyConstants._metaCacheTimeout, Caster.toString(metaCacheTimeout)); + el.setEL(KeyConstants._blob, Caster.toString(blob)); + el.setEL(KeyConstants._clob, Caster.toString(clob)); + el.setEL(KeyConstants._allow, Caster.toString(allow)); + el.setEL(KeyConstants._validate, Caster.toString(validate)); + el.setEL(KeyConstants._storage, Caster.toString(storage)); + el.setEL(KeyConstants._custom, custom); if (!StringUtil.isEmpty(dbdriver)) el.setEL("dbdriver", Caster.toString(dbdriver)); @@ -1541,7 +1540,7 @@ else if (access >= SecurityManager.VALUE_1 && access <= SecurityManager.VALUE_10 el.setEL("validate", Caster.toString(validate)); el.setEL("storage", Caster.toString(storage)); if (allow > -1) el.setEL("allow", Caster.toString(allow)); - el.setEL("custom", toStringURLStyle(custom)); + el.setEL("custom", custom); if (!StringUtil.isEmpty(dbdriver)) el.setEL("dbdriver", Caster.toString(dbdriver)); @@ -1625,6 +1624,24 @@ private void _removeStartupHook(ClassDefinition cd) throws PageException { } } + private void _removeStartupHook(String component) { + + Array children = ConfigWebUtil.getAsArray("startupHooks", root); + Key[] keys = children.keys(); + // Remove + for (int i = keys.length - 1; i >= 0; i--) { + Key key = keys[i]; + Struct tmp = Caster.toStruct(children.get(key, null), null); + if (tmp == null) continue; + + String n = Caster.toString(tmp.get(KeyConstants._component, null), null); + if (n != null && n.equalsIgnoreCase(component)) { + children.removeEL(key); + break; + } + } + } + private void unloadStartupIfNecessary(ConfigPro config, ClassDefinition cd, boolean force) { ConfigBase.Startup startup = config.getStartups().get(cd.getClassName()); if (startup == null) return; @@ -1641,12 +1658,12 @@ private void unloadStartupIfNecessary(ConfigPro config, ClassDefinition cd, b } } - public void updateJDBCDriver(String label, String id, ClassDefinition cd) throws PageException { + public void updateJDBCDriver(String label, String id, ClassDefinition cd, String connectionString) throws PageException { checkWriteAccess(); - _updateJDBCDriver(label, id, cd); + _updateJDBCDriver(label, id, cd, connectionString); } - private void _updateJDBCDriver(String label, String id, ClassDefinition cd) throws PageException { + private void _updateJDBCDriver(String label, String id, ClassDefinition cd, String connectionString) throws PageException { // check if label exists if (StringUtil.isEmpty(label)) throw new ApplicationException("missing label for jdbc driver [" + cd.getClassName() + "]"); @@ -1674,6 +1691,8 @@ private void _updateJDBCDriver(String label, String id, ClassDefinition cd) thro } child.setEL("label", label); + if (!StringUtil.isEmpty(connectionString, true)) child.setEL("connectionString", connectionString); + if (!StringUtil.isEmpty(id)) child.setEL(KeyConstants._id, id); else child.removeEL(KeyConstants._id); // make sure the class exists @@ -1735,6 +1754,34 @@ private void _updateStartupHook(ClassDefinition cd) throws PageException { } } + private void _updateStartupHook(String component) { + // unloadStartupIfNecessary(config, cd, false); + + Array children = ConfigWebUtil.getAsArray("startupHooks", root); + + // Update + Struct child = null; + for (int i = 1; i <= children.size(); i++) { + Struct tmp = Caster.toStruct(children.get(i, null), null); + if (tmp == null) continue; + + String n = ConfigWebUtil.getAsString("component", tmp, null); + if (n.equalsIgnoreCase(component)) { + child = tmp; + break; + } + } + + // Insert + if (child == null) { + child = new StructImpl(Struct.TYPE_LINKED); + children.appendEL(child); + } + + // make sure the class exists + child.setEL(KeyConstants._component, component); + } + public void updateGatewayEntry(String id, ClassDefinition cd, String componentPath, String listenerCfcPath, int startupMode, Struct custom, boolean readOnly) throws PageException { checkWriteAccess(); @@ -1766,7 +1813,7 @@ void _updateGatewayEntry(String id, ClassDefinition cd, String componentPath, St el.setEL("cfcPath", componentPath); el.setEL("listenerCFCPath", listenerCfcPath); el.setEL("startupMode", GatewayEntryImpl.toStartup(startupMode, "automatic")); - el.setEL("custom", toStringURLStyle(custom)); + el.setEL("custom", custom); el.setEL("readOnly", Caster.toString(readOnly)); return; } @@ -1779,7 +1826,7 @@ void _updateGatewayEntry(String id, ClassDefinition cd, String componentPath, St el.setEL("listenerCFCPath", listenerCfcPath); el.setEL("startupMode", GatewayEntryImpl.toStartup(startupMode, "automatic")); setClass(el, null, "", cd); - el.setEL("custom", toStringURLStyle(custom)); + el.setEL("custom", custom); el.setEL("readOnly", Caster.toString(readOnly)); } @@ -1929,7 +1976,7 @@ else if (_default == ConfigPro.CACHE_TYPE_WEBSERVICE) { if (key.getString().equalsIgnoreCase(name)) { Struct el = Caster.toStruct(conns.get(key, null), null); setClass(el, null, "", cd); - el.setEL("custom", toStringURLStyle(custom)); + el.setEL("custom", custom); el.setEL("readOnly", Caster.toString(readOnly)); el.setEL("storage", Caster.toString(storage)); return; @@ -1940,7 +1987,7 @@ else if (_default == ConfigPro.CACHE_TYPE_WEBSERVICE) { Struct data = new StructImpl(Struct.TYPE_LINKED); conns.setEL(name, data); setClass(data, null, "", cd); - data.setEL("custom", toStringURLStyle(custom)); + data.setEL("custom", custom); data.setEL("readOnly", Caster.toString(readOnly)); data.setEL("storage", Caster.toString(storage)); @@ -2128,21 +2175,6 @@ private int getDatasourceLength(ConfigPro config) { return len; } - private static String toStringURLStyle(Struct sct) { - if (sct == null) return ""; - Iterator> it = sct.entryIterator(); - Entry e; - StringBuilder rtn = new StringBuilder(); - while (it.hasNext()) { - e = it.next(); - if (rtn.length() > 0) rtn.append('&'); - rtn.append(URLEncoder.encode(e.getKey().getString())); - rtn.append('='); - rtn.append(URLEncoder.encode(Caster.toString(e.getValue(), ""))); - } - return rtn.toString(); - } - private static String encode(String str) { try { return URLEncodedFormat.invoke(str, "UTF-8", false); @@ -2182,7 +2214,7 @@ private void getResourceProviders(ResourceProvider[] providers, Query qry, Struc String cn = ConfigWebUtil.getAsString("class", p, null); String name = ConfigWebUtil.getAsString("bundleName", p, null); String version = ConfigWebUtil.getAsString("bundleVersion", p, null); - ClassDefinition cd = new ClassDefinitionImpl(cn, name, version, ThreadLocalPageContext.getConfig().getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(p, null, false, ThreadLocalPageContext.getConfig().getIdentification()); String scheme = Caster.toString(p.get("scheme", null), null); if (StringUtil.isEmpty(scheme)) { try { @@ -2880,6 +2912,37 @@ public void updateTimeZone(String timeZone) throws SecurityException { } + /** + * update the timeServer + * + * @param timeServer + * @param useTimeServer + * @throws PageException + */ + public void updateTimeServer(String timeServer, Boolean useTimeServer) throws PageException { + checkWriteAccess(); + if (useTimeServer != null && useTimeServer.booleanValue() && !StringUtil.isEmpty(timeServer, true)) { + try { + new NtpClient(timeServer).getOffset(); + } + catch (IOException e) { + try { + new NtpClient(timeServer).getOffset(); + } + catch (IOException ee) { + throw Caster.toPageException(ee); + } + } + } + + boolean hasAccess = ConfigWebUtil.hasAccess(config, SecurityManager.TYPE_SETTING); + if (!hasAccess) throw new SecurityException("no access to update regional setting"); + + root.setEL("timeserver", timeServer.trim()); + if (useTimeServer != null) root.setEL("useTimeserver", Caster.toBooleanValue(useTimeServer)); + else rem(root, "useTimeserver"); + } + public void updateComponentDeepSearch(Boolean deepSearch) throws SecurityException { checkWriteAccess(); boolean hasAccess = ConfigWebUtil.hasAccess(config, SecurityManager.TYPE_SETTING); @@ -3945,7 +4008,7 @@ public void updateUpdateAdminMode(String mode, boolean merge, boolean keep) thro for (ConfigWeb cw: webs) { try { for (RHExtension ext: ((ConfigPro) cw).getRHExtensions()) { - ext.addToAvailable(); + ext.addToAvailable(cw); } } catch (Exception e) { @@ -4356,7 +4419,7 @@ public void removeRHExtension(String id) throws PageException { if (child == null) continue; try { - rhe = new RHExtension(config, Caster.toString(child.get(KeyConstants._id), null), Caster.toString(child.get(KeyConstants._version), null)); + rhe = RHExtension.getInstance(config, Caster.toString(child.get(KeyConstants._id), null), Caster.toString(child.get(KeyConstants._version), null)); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -4390,12 +4453,12 @@ public static void updateArchive(ConfigPro config, Resource arc, boolean reload) public static void updateCore(ConfigServerImpl config, Resource core, boolean reload) throws PageException { try { // get patches directory - CFMLEngineFactory factory = ConfigWebUtil.getCFMLEngineFactory(config); + CFMLEngine engine = ConfigWebUtil.getEngine(config); ConfigServerImpl cs = config; Version v; v = CFMLEngineFactory.toVersion(core.getName(), null); Log logger = cs.getLog("deploy"); - File f = factory.getResourceRoot(); + File f = engine.getCFMLEngineFactory().getResourceRoot(); Resource res = ResourcesImpl.getFileResourceProvider().getResource(f.getAbsolutePath()); Resource pd = res.getRealResource("patches"); if (!pd.exists()) pd.mkdirs(); @@ -4515,10 +4578,10 @@ public static RHExtension _updateRHExtension(ConfigPro config, Resource ext, boo public RHExtension updateRHExtension(Config config, Resource ext, boolean reload, boolean force, short action) throws PageException { RHExtension rhext; try { - rhext = new RHExtension(config, ext); - if (RHExtension.ACTION_COPY == action) rhext.copyToInstalled(); - else if (RHExtension.ACTION_MOVE == action) rhext.moveToInstalled(); - rhext.validate(); + rhext = RHExtension.getInstance(config, ext); + if (RHExtension.ACTION_COPY == action) rhext.copyToInstalled(config); + else if (RHExtension.ACTION_MOVE == action) rhext.moveToInstalled(config); + rhext.validate(config); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -4551,13 +4614,8 @@ protected static void _removeRHExtension(ConfigPro config, RHExtension rhext, RH public void updateRHExtension(Config config, RHExtension rhext, boolean reload, boolean force) throws PageException { try { - if (!force && ConfigAdmin.hasRHExtensionInstalled((ConfigPro) config, rhext.toExtensionDefinition()) != null) { - String msg = "the extension " + rhext.getName() + " (id: " + rhext.getId() + ") in version " + rhext.getVersion() + " is already installed"; - Log log = config.getLog("deploy"); - if (log != null) { - log.debug("install", msg); - } - return; + if (!force && _hasRHExtensionInstalled((ConfigPro) config, rhext.toExtensionDefinition()) != null) { + throw new ApplicationException("the extension " + rhext.getName() + " (id: " + rhext.getId() + ") in version " + rhext.getVersion() + " is already installed"); } } catch (Exception e) { @@ -4731,7 +4789,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.isBundle()) { _updateCache(cd); reloadNecessary = true; @@ -4746,7 +4804,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); String _id = map.get("id"); if (!StringUtil.isEmpty(_id) && cd != null && cd.hasClass()) { _updateCacheHandler(_id, cd); @@ -4762,7 +4820,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _updateSearchEngine(cd); reloadNecessary = true; @@ -4777,7 +4835,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); String scheme = map.get("scheme"); if (cd != null && cd.hasClass() && !StringUtil.isEmpty(scheme)) { Struct args = new StructImpl(Struct.TYPE_LINKED); @@ -4796,7 +4854,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _updateORMEngine(cd); @@ -4812,7 +4870,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _updateWebserviceHandler(cd); @@ -4828,7 +4886,7 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _updateMonitorEnabled(true); _updateMonitor(cd, map.get("type"), map.get("name"), true); @@ -4844,11 +4902,13 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); String _label = map.get("label"); String _id = map.get("id"); + String _dsn = map.get("connectionString"); + if (StringUtil.isEmpty(_dsn, true)) _dsn = map.get("dsn"); if (cd != null && cd.isBundle()) { - _updateJDBCDriver(_label, _id, cd); + _updateJDBCDriver(_label, _id, cd, _dsn); reloadNecessary = true; } logger.info("extension", "Update JDBC Driver [" + _label + ":" + cd + "] from extension [" + rhext.getName() + ":" + rhext.getVersion() + "]"); @@ -4861,12 +4921,21 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); + String cfc = map.get("component"); + + // class if (cd != null && cd.isBundle()) { _updateStartupHook(cd); reloadNecessary = true; + logger.info("extension", "Update Startup Hook [" + cd + "] from extension [" + rhext.getName() + ":" + rhext.getVersion() + "]"); + } + // component + else if (!StringUtil.isEmpty(cfc, true)) { + _updateStartupHook(cfc); + reloadNecessary = true; + logger.info("extension", "Update Startup Hook [" + cfc + "] from extension [" + rhext.getName() + ":" + rhext.getVersion() + "]"); } - logger.info("extension", "Update Startup Hook [" + cd + "] from extension [" + rhext.getName() + ":" + rhext.getVersion() + "]"); } } @@ -4932,25 +5001,34 @@ public void updateRHExtension(Config config, RHExtension rhext, boolean reload, // id String id = Caster.toString(map.get("id"), null); // class - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); // component path String cfcPath = Caster.toString(map.get("cfcPath"), null); + if (StringUtil.isEmpty(cfcPath)) cfcPath = Caster.toString(map.get("cfc-path"), null); if (StringUtil.isEmpty(cfcPath)) cfcPath = Caster.toString(map.get("componentPath"), null); + if (StringUtil.isEmpty(cfcPath)) cfcPath = Caster.toString(map.get("component-path"), null); + // listener component path String listenerCfcPath = Caster.toString(map.get("listenerCFCPath"), null); if (StringUtil.isEmpty(listenerCfcPath)) listenerCfcPath = Caster.toString(map.get("listenerComponentPath"), null); // startup mode String strStartupMode = Caster.toString(map.get("startupMode"), "automatic"); + if (StringUtil.isEmpty(strStartupMode)) strStartupMode = Caster.toString(map.get("startup-mode"), "automatic"); int startupMode = GatewayEntryImpl.toStartup(strStartupMode, GatewayEntryImpl.STARTUP_MODE_AUTOMATIC); // read only - boolean readOnly = Caster.toBooleanValue(map.get("readOnly"), false); + String strReadOnly = Caster.toString(map.get("readOnly"), null); + if (StringUtil.isEmpty(strReadOnly)) strReadOnly = Caster.toString(map.get("read-only"), null); + boolean readOnly = Caster.toBooleanValue(strReadOnly, false); // custom Struct custom = Caster.toStruct(map.get("custom"), null); - /* - * print.e("::::::::::::::::::::::::::::::::::::::::::"); print.e("id:"+id); print.e("cd:"+cd); - * print.e("cfc:"+cfcPath); print.e("listener:"+listenerCfcPath); - * print.e("startupMode:"+startupMode); print.e(custom); - */ + + // print.e("::::::::::::::::::::::::::::::::::::::::::"); + // print.e("id:" + id); + // print.e("cd:" + cd); + // print.e("cfc:" + cfcPath); + // print.e("listener:" + listenerCfcPath); + // print.e("startupMode:" + startupMode); + // print.e(custom); if (!StringUtil.isEmpty(id) && (!StringUtil.isEmpty(cfcPath) || (cd != null && cd.hasClass()))) { _updateGatewayEntry(id, cd, cfcPath, listenerCfcPath, startupMode, custom, readOnly); @@ -5059,7 +5137,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); String _id = map.get("id"); if (!StringUtil.isEmpty(_id) && cd != null && cd.hasClass()) { @@ -5076,7 +5154,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.isBundle()) { _removeCache(cd); // reload=true; @@ -5091,7 +5169,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _removeSearchEngine(); // reload=true; @@ -5106,7 +5184,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); String scheme = map.get("scheme"); if (cd != null && cd.hasClass()) { _removeResourceProvider(scheme); @@ -5121,7 +5199,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _removeORMEngine(); @@ -5137,7 +5215,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.hasClass()) { _removeWebserviceHandler(); @@ -5171,7 +5249,7 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); if (cd != null && cd.isBundle()) { _removeJDBCDriver(cd); } @@ -5185,10 +5263,15 @@ private void removeRHExtension(Config config, RHExtension rhe, RHExtension repla Map map; while (itl.hasNext()) { map = itl.next(); - ClassDefinition cd = RHExtension.toClassDefinition(config, map, null); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinition(map, false, config.getIdentification()); + String cfc = map.get("component"); + if (cd != null && cd.isBundle()) { _removeStartupHook(cd); } + else if (!StringUtil.isEmpty(cfc, true)) { + _removeStartupHook(cfc); + } logger.info("extension", "Remove Startup Hook [" + cd + "] from extension [" + rhe.getName() + ":" + rhe.getVersion() + "]"); } } @@ -5492,7 +5575,7 @@ public void updateRemoteClientUsage(String code, String displayname) { usage.setEL(code, displayname); Struct extensions = _getRootElement("remoteClients"); - extensions.setEL("usage", toStringURLStyle(usage)); + extensions.setEL("usage", usage); } @@ -5508,7 +5591,7 @@ public void removeRemoteClientUsage(String code) { usage.removeEL(KeyImpl.init(code)); Struct extensions = _getRootElement("remoteClients"); - extensions.setEL("usage", toStringURLStyle(usage)); + extensions.setEL("usage", usage); } @@ -5638,7 +5721,7 @@ public void updateDebugEntry(String type, String iprange, String label, String p el.setEL("label", label); el.setEL("path", path); el.setEL("fullname", fullname); - el.setEL("custom", toStringURLStyle(custom)); + el.setEL("custom", custom); } public void removeDebugEntry(String id) throws SecurityException { @@ -6317,7 +6400,7 @@ public BundleDefinition[] _updateExtension(ConfigPro config, RHExtension ext) th Resource r; if (!StringUtil.isEmpty(res) && (r = ResourceUtil.toResourceExisting(config, res, null)) != null) { try { - RHExtension _ext = new RHExtension(config, r);// TODO not load it again! + RHExtension _ext = RHExtension.getInstance(config, r);// TODO not load it again! if (_ext != null && _ext.getId().equalsIgnoreCase(ext.getId())) { old = RHExtension.toBundleDefinitions(ConfigWebUtil.getAsString("bundles", el, null)); // get existing bundles before populate new ones ext.populate(el, false); @@ -6373,7 +6456,7 @@ private RHExtension getRHExtension(final ConfigPro config, final String id, fina if (!id.equals(_id)) continue; try { - return new RHExtension(config, _id, Caster.toString(tmp.get(KeyConstants._version), null)); + return RHExtension.getInstance(config, _id, Caster.toString(tmp.get(KeyConstants._version), null)); } catch (Exception e) { return defaultValue; @@ -6412,7 +6495,7 @@ private RHExtension _hasRHExtensionInstalled(ConfigPro config, ExtensionDefintio v = Caster.toString(sct.get(KeyConstants._version, null), null); if (!RHExtension.isInstalled(config, id, v)) continue; - if (ed.equals(new ExtensionDefintion(id, v))) return new RHExtension(config, id, v); + if (ed.equals(new ExtensionDefintion(id, v))) return RHExtension.getInstance(config, id, v); } return null; } @@ -6590,6 +6673,6 @@ public void updateFormUrlAsStruct(Boolean formUrlAsStruct) throws SecurityExcept public void updateConfig(Struct data, boolean flushExistingData) throws SecurityException { checkWriteAccess(); if (flushExistingData) root.clear(); - StructUtil.merge(true, root, data); + StructUtil.merge(root, data); } } diff --git a/core/src/main/java/lucee/runtime/config/ConfigFactory.java b/core/src/main/java/lucee/runtime/config/ConfigFactory.java index a99af7644e..bb332ff0e8 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigFactory.java +++ b/core/src/main/java/lucee/runtime/config/ConfigFactory.java @@ -220,14 +220,18 @@ static Struct loadDocumentCreateIfFails(Resource configFile, String type) throws catch (Exception e) { // rename buggy config files if (configFile.exists()) { - LogUtil.log(ThreadLocalPageContext.getConfig(), Log.LEVEL_INFO, ConfigFactory.class.getName(), - "Config file [" + configFile + "] was not valid and has been replaced"); - LogUtil.log(ThreadLocalPageContext.get(), ConfigFactory.class.getName(), e); - int count = 1; Resource bugFile; + int count = 1; Resource configDir = configFile.getParentResource(); while ((bugFile = configDir.getRealResource("lucee-" + type + "." + (count++) + ".buggy")).exists()) { } + + LogUtil.log(ThreadLocalPageContext.getConfig(), Log.LEVEL_INFO, ConfigFactory.class.getName(), + "The configuration file [" + configFile + + "] contained syntax errors and could not be read. A new configuration file has been created, and the invalid file has been renamed to [" + bugFile + + "]."); + LogUtil.log(ThreadLocalPageContext.get(), ConfigFactory.class.getName(), e); + IOUtil.copy(configFile, bugFile); configFile.delete(); } @@ -257,7 +261,7 @@ else if (old instanceof InputSource) { else { new ConverterException("inputing data is invalid, cannot cast [" + old.getClass().getName() + "] to a Resource or an InputSource"); } - Struct root = ConfigWebUtil.getAsStruct(reader.getData(), "cfLuceeConfiguration", "luceeConfiguration", "lucee-configuration"); + Struct root = ConfigWebUtil.getAsStruct(reader.getData(), false, "cfLuceeConfiguration", "luceeConfiguration", "lucee-configuration"); //////////////////// charset //////////////////// { @@ -943,7 +947,7 @@ private static Struct _loadDocument(Resource res) throws IOException, PageExcept // That step is not necessary anymore TODO remove if (StringUtil.endsWithIgnoreCase(name, ".xml.cfm") || StringUtil.endsWithIgnoreCase(name, ".xml")) { try { - return ConfigWebUtil.getAsStruct(new XMLConfigReader(res, true, new ReadRule(), new NameRule()).getData(), "cfLuceeConfiguration", "luceeConfiguration", + return ConfigWebUtil.getAsStruct(new XMLConfigReader(res, true, new ReadRule(), new NameRule()).getData(), false, "cfLuceeConfiguration", "luceeConfiguration", "lucee-configuration"); } catch (SAXException e) { diff --git a/core/src/main/java/lucee/runtime/config/ConfigImpl.java b/core/src/main/java/lucee/runtime/config/ConfigImpl.java index 8db1ccd99e..b9ab4b54e9 100755 --- a/core/src/main/java/lucee/runtime/config/ConfigImpl.java +++ b/core/src/main/java/lucee/runtime/config/ConfigImpl.java @@ -41,7 +41,6 @@ import org.osgi.framework.BundleException; import org.osgi.framework.Version; -import lucee.commons.digest.HashUtil; import lucee.commons.io.CharsetUtil; import lucee.commons.io.FileUtil; import lucee.commons.io.SystemUtil; @@ -57,7 +56,6 @@ import lucee.commons.io.res.ResourcesImpl.ResourceProviderFactory; import lucee.commons.io.res.filter.ExtensionResourceFilter; import lucee.commons.io.res.type.compress.Compress; -import lucee.commons.io.res.util.ResourceClassLoader; import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.CharSet; import lucee.commons.lang.ClassException; @@ -77,6 +75,7 @@ import lucee.runtime.Page; import lucee.runtime.PageContext; import lucee.runtime.PageSource; +import lucee.runtime.ai.AIEngineFactory; import lucee.runtime.cache.CacheConnection; import lucee.runtime.cache.ram.RamCache; import lucee.runtime.cache.tag.CacheHandler; @@ -107,6 +106,7 @@ import lucee.runtime.exp.SecurityException; import lucee.runtime.exp.TemplateException; import lucee.runtime.extension.Extension; +import lucee.runtime.extension.ExtensionDefintion; import lucee.runtime.extension.ExtensionProvider; import lucee.runtime.extension.RHExtension; import lucee.runtime.extension.RHExtensionProvider; @@ -114,6 +114,8 @@ import lucee.runtime.listener.AppListenerUtil; import lucee.runtime.listener.ApplicationContext; import lucee.runtime.listener.ApplicationListener; +import lucee.runtime.listener.JavaSettings; +import lucee.runtime.listener.JavaSettingsImpl; import lucee.runtime.net.mail.Server; import lucee.runtime.net.proxy.ProxyData; import lucee.runtime.net.proxy.ProxyDataImpl; @@ -350,6 +352,7 @@ public abstract class ConfigImpl extends ConfigBase implements ConfigPro { private RHExtensionProvider[] rhextensionProviders = Constants.RH_EXTENSION_PROVIDERS; + private List extensionsDefs; private RHExtension[] rhextensions = RHEXTENSIONS_EMPTY; private String extensionsMD5; private boolean allowRealPath = true; @@ -379,7 +382,6 @@ public abstract class ConfigImpl extends ConfigBase implements ConfigPro { private Map ormengines = new HashMap(); private ClassDefinition cdORMEngine; private ORMConfiguration ormConfig; - private ResourceClassLoader resourceCL; private ImportDefintion componentDefaultImport = new ImportDefintionImpl(Constants.DEFAULT_PACKAGE, "*"); private boolean componentLocalSearch = true; @@ -432,6 +434,15 @@ public abstract class ConfigImpl extends ConfigBase implements ConfigPro { private boolean showMetric; private boolean showTest; + private JavaSettings javaSettings; + private Map javaSettingsInstances = new ConcurrentHashMap<>(); + + private boolean fullNullSupport = false; + + private Resource extInstalled; + private Resource extAvailable; + + protected Map aiEngineFactories; /** * @return the allowURLRequestTimeout @@ -649,9 +660,14 @@ public int getQueryVarUsage() { @Override public ClassLoader getClassLoader() { - ResourceClassLoader rcl = getResourceClassLoader(null); - if (rcl != null) return rcl; - return new lucee.commons.lang.ClassLoaderHelper().getClass().getClassLoader(); + ClassLoader cl = null; + try { + cl = getRPCClassLoader(false); + } + catch (IOException e) { + } + if (cl != null) return cl; + return SystemUtil.getCombinedClassLoader(); } @@ -666,25 +682,6 @@ public ClassLoader getClassLoaderEnv() { public ClassLoader getClassLoaderCore() { return new lucee.commons.lang.ClassLoaderHelper().getClass().getClassLoader(); } - /* - * public ClassLoader getClassLoaderLoader() { return new TP().getClass().getClassLoader(); } - */ - - @Override - public ResourceClassLoader getResourceClassLoader() { - if (resourceCL == null) throw new RuntimeException("no RCL defined yet!"); - return resourceCL; - } - - @Override - public ResourceClassLoader getResourceClassLoader(ResourceClassLoader defaultValue) { - if (resourceCL == null) return defaultValue; - return resourceCL; - } - - protected void setResourceClassLoader(ResourceClassLoader resourceCL) { - this.resourceCL = resourceCL; - } @Override public Locale getLocale() { @@ -1440,7 +1437,7 @@ protected void setTempDirectory(Resource tempDirectory, boolean flush) throws Ex LogUtil.log(this, Log.LEVEL_ERROR, "loading", "temp directory [" + tempDirectory + "] is not writable"); } } - if (flush) ResourceUtil.removeChildrenEL(tempDirectory);// start with an empty temp directory + if (flush) ResourceUtil.removeChildrenEL(tempDirectory, true);// start with an empty temp directory this.tempDirectory = tempDirectory; } @@ -1517,6 +1514,10 @@ protected void setDataSources(Map datasources) { this.datasources = datasources; } + protected void setAIEngineFactories(Map aiEngineFactories) { + this.aiEngineFactories = aiEngineFactories; + } + /** * @param mailServers The mailsServers to set. */ @@ -2240,26 +2241,12 @@ protected void setClientScopeDirSize(long clientScopeDirSize) { @Override public ClassLoader getRPCClassLoader(boolean reload) throws IOException { - return getRPCClassLoader(reload, null); + return PhysicalClassLoader.getRPCClassLoader(this, getJavaSettings(), reload); } @Override - public ClassLoader getRPCClassLoader(boolean reload, ClassLoader[] parents) throws IOException { - String key = toKey(parents); - PhysicalClassLoader rpccl = rpcClassLoaders.get(key); - if (rpccl == null || reload) { - synchronized (key) { - rpccl = rpcClassLoaders.get(key); - if (rpccl == null || reload) { - Resource dir = getClassDirectory().getRealResource("RPC/" + key); - if (!dir.exists()) { - ResourceUtil.createDirectoryEL(dir, true); - } - rpcClassLoaders.put(key, rpccl = new PhysicalClassLoader(this, dir, parents != null && parents.length == 0 ? null : parents, false, null)); - } - } - } - return rpccl; + public ClassLoader getRPCClassLoader(boolean reload, JavaSettings js) throws IOException { + return PhysicalClassLoader.getRPCClassLoader(this, js != null ? js : getJavaSettings(), reload); } private static final Object dclt = new SerializableObject(); @@ -2273,23 +2260,13 @@ public PhysicalClassLoader getDirectClassLoader(boolean reload) throws IOExcepti if (!dir.exists()) { ResourceUtil.createDirectoryEL(dir, true); } - directClassLoader = new PhysicalClassLoader(this, dir, null); + directClassLoader = PhysicalClassLoader.getPhysicalClassLoader(this, dir, reload); } } } return directClassLoader; } - private String toKey(ClassLoader[] parents) { - if (parents == null || parents.length == 0) return "orphan"; - - StringBuilder sb = new StringBuilder(); - for (ClassLoader parent: parents) { - sb.append(';').append(System.identityHashCode(parent)); - } - return HashUtil.create64BitHashAsString(sb.toString()); - } - public void resetRPCClassLoader() { rpcClassLoaders.clear(); } @@ -2687,15 +2664,6 @@ public Resource getVideoDirectory() { return dir; } - @Override - public Resource getExtensionDirectory() { - // TODO take from tag - Resource dir = getConfigDir().getRealResource("extensions/installed"); - if (!dir.exists()) dir.mkdirs(); - - return dir; - } - @Override public ExtensionProvider[] getExtensionProviders() { throw new RuntimeException("no longer supported, use getRHExtensionProviders() instead."); @@ -2725,10 +2693,19 @@ public String getExtensionsMD5() { } protected void setExtensions(RHExtension[] extensions, String md5) { + this.extensionsDefs = null; this.rhextensions = extensions; this.extensionsMD5 = md5; } + protected void setExtensionDefinitions(List extensionsDefs) { + this.extensionsDefs = extensionsDefs; + } + + public List getExtensionDefinitions() { + return this.extensionsDefs; + } + @Override public boolean isExtensionEnabled() { throw new PageRuntimeException("no longer supported"); @@ -3889,8 +3866,6 @@ boolean isEmpty(ClassDefinition cd) { return cd == null || StringUtil.isEmpty(cd.getClassName()); } - private boolean fullNullSupport = false; - protected final void setFullNullSupport(boolean fullNullSupport) { this.fullNullSupport = fullNullSupport; } @@ -3916,12 +3891,13 @@ public LogEngine getLogEngine() { } protected void setCachedAfterTimeRange(TimeSpan ts) { - this.cachedAfterTimeRange = ts; + if (ts != null && ts.getMillis() > 0) { + this.cachedAfterTimeRange = ts; + } } @Override public TimeSpan getCachedAfterTimeRange() { - if (this.cachedAfterTimeRange != null && this.cachedAfterTimeRange.getMillis() <= 0) this.cachedAfterTimeRange = null; return this.cachedAfterTimeRange; } @@ -3976,4 +3952,61 @@ public int getReturnFormat() { protected void setReturnFormat(int returnFormat) { this.returnFormat = returnFormat; } + + @Override + public JavaSettings getJavaSettings(String id) { + return javaSettingsInstances.get(id); + } + + @Override + public void setJavaSettings(String id, JavaSettings js) { + javaSettingsInstances.put(id, js); + } + + public void setJavaSettings(JavaSettings js) { + javaSettings = js; + } + + @Override + public JavaSettings getJavaSettings() { + if (javaSettings == null) { + synchronized (javaSettingsInstances) { + if (javaSettings == null) { + javaSettings = JavaSettingsImpl.getInstance(this, new StructImpl(), null); + } + } + } + return javaSettings; + } + + @Override + public Resource getExtensionDirectory() { + return getExtensionInstalledDir(); + } + + @Override + public Resource getExtensionInstalledDir() { + if (extInstalled == null) { + synchronized (SystemUtil.createToken("extensions", "installed")) { + if (extInstalled == null) { + extInstalled = getConfigDir().getRealResource("extensions/installed"); + if (!extInstalled.exists()) extInstalled.mkdirs(); + } + } + } + return extInstalled; + } + + @Override + public Resource getExtensionAvailableDir() { + if (extAvailable == null) { + synchronized (SystemUtil.createToken("extensions", "available")) { + if (extAvailable == null) { + extAvailable = getConfigDir().getRealResource("extensions/available"); + if (!extAvailable.exists()) extAvailable.mkdirs(); + } + } + } + return extAvailable; + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/config/ConfigPro.java b/core/src/main/java/lucee/runtime/config/ConfigPro.java index e3020ddab6..dd600192f1 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigPro.java +++ b/core/src/main/java/lucee/runtime/config/ConfigPro.java @@ -15,7 +15,6 @@ import lucee.commons.io.res.Resource; import lucee.commons.io.res.ResourcesImpl.ResourceProviderFactory; import lucee.commons.io.res.type.compress.Compress; -import lucee.commons.io.res.util.ResourceClassLoader; import lucee.commons.lang.CharSet; import lucee.commons.lang.ClassException; import lucee.commons.lang.PhysicalClassLoader; @@ -24,6 +23,8 @@ import lucee.runtime.Mapping; import lucee.runtime.PageContext; import lucee.runtime.PageSource; +import lucee.runtime.ai.AIEngineFactory; +import lucee.runtime.ai.AIEnginePool; import lucee.runtime.cache.tag.CacheHandler; import lucee.runtime.component.ImportDefintion; import lucee.runtime.customtag.InitFile; @@ -36,6 +37,7 @@ import lucee.runtime.extension.ExtensionDefintion; import lucee.runtime.extension.RHExtension; import lucee.runtime.extension.RHExtensionProvider; +import lucee.runtime.listener.JavaSettings; import lucee.runtime.orm.ORMConfiguration; import lucee.runtime.orm.ORMEngine; import lucee.runtime.osgi.OSGiUtil.BundleDefinition; @@ -65,6 +67,8 @@ public interface ConfigPro extends Config { public static final int DEBUG_DUMP = 64; public static final int DEBUG_TEMPLATE = 128; public static final int DEBUG_THREAD = 256; + public static final int DEBUG_ALL = DEBUG_DATABASE + DEBUG_EXCEPTION + DEBUG_TRACING + DEBUG_TIMER + DEBUG_IMPLICIT_ACCESS + DEBUG_QUERY_USAGE + DEBUG_DUMP + DEBUG_TEMPLATE + + DEBUG_THREAD; public static final int MODE_CUSTOM = 1; public static final int MODE_STRICT = 2; @@ -183,9 +187,7 @@ public interface ConfigPro extends Config { public boolean getTypeChecking(); - public ResourceClassLoader getResourceClassLoader(); - - public ClassLoader getRPCClassLoader(boolean reload, ClassLoader[] parents) throws IOException; + public ClassLoader getRPCClassLoader(boolean reload, JavaSettings js) throws IOException; public PageSource toPageSource(Mapping[] mappings, Resource res, PageSource defaultValue); @@ -334,8 +336,6 @@ public interface ConfigPro extends Config { */ public List loadLocalExtensions(boolean validate); - public ResourceClassLoader getResourceClassLoader(ResourceClassLoader defaultValue); - public Compress getCompressInstance(Resource zipFile, int format, boolean caseSensitive) throws IOException; public int getPasswordOrigin(); @@ -386,4 +386,22 @@ public Resource[] getResources(PageContext pc, Mapping[] mappings, String realPa public boolean getShowMetric(); public boolean getShowTest(); + + public Resource getExtensionInstalledDir(); + + public Resource getExtensionAvailableDir(); + + public JavaSettings getJavaSettings(); + + public JavaSettings getJavaSettings(String id); + + public void setJavaSettings(String id, JavaSettings js); + + public Resource getMavenDir(); + + public Collection getAIEngineFactoryNames(); + + public AIEngineFactory getAIEngineFactory(String name); + + public AIEnginePool getAIEnginePool(); } diff --git a/core/src/main/java/lucee/runtime/config/ConfigServerFactory.java b/core/src/main/java/lucee/runtime/config/ConfigServerFactory.java index 7d7940b536..cd2fe04563 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigServerFactory.java +++ b/core/src/main/java/lucee/runtime/config/ConfigServerFactory.java @@ -175,23 +175,21 @@ public static ConfigServerImpl newInstance(CFMLEngineImpl engine, Map amfEngineCD; + + private Map amfEngineArgs; + + private List localExtensions; + + private long localExtHash; + private int localExtSize = -1; + + private GatewayMap gatewayEntries; + + private short adminMode = ADMINMODE_SINGLE; + + private Resource mvnDir; + /** * @param engine * @param srvConfig @@ -376,6 +398,8 @@ public Map getLabels() { private ThreadQueue threadQueue = new ThreadQueueImpl(ThreadQueuePro.MODE_BLOCKING, null); // before the queue is loaded we block all requests + private AIEnginePool aiEnginePool; + public ThreadQueue setThreadQueue(ThreadQueue threadQueue) { return this.threadQueue = threadQueue; } @@ -632,23 +656,6 @@ public boolean allowRequestTimeout() { return engine.allowRequestTimeout(); } - private IdentificationServer id; - - private String libHash; - - private ClassDefinition amfEngineCD; - - private Map amfEngineArgs; - - private List localExtensions; - - private long localExtHash; - private int localExtSize = -1; - - private GatewayMap gatewayEntries; - - private short adminMode = ADMINMODE_SINGLE; - public String[] getAuthenticationKeys() { return authKeys == null ? new String[0] : authKeys; } @@ -805,7 +812,7 @@ public List loadLocalExtensions(boolean validate) { } if (ed == null) { try { - ext = new RHExtension(this, locReses[i]); + ext = RHExtension.getInstance(this, locReses[i]); ed = new ExtensionDefintion(ext.getId(), ext.getVersion()); ed.setSource(ext); @@ -873,4 +880,40 @@ public void setAdminMode(short adminMode) { public short getAdminMode() { return adminMode; } + + @Override + public Resource getMavenDir() { + if (mvnDir == null) { + synchronized (this) { + if (mvnDir == null) { + mvnDir = ResourceUtil.getCanonicalResourceEL(getConfigDir().getRealResource("../mvn/")); + + mvnDir.mkdirs(); + } + } + } + return mvnDir; + } + + @Override + public Collection getAIEngineFactoryNames() { + return aiEngineFactories.keySet(); + } + + @Override + public AIEngineFactory getAIEngineFactory(String name) { + return aiEngineFactories.get(name); + } + + @Override + public AIEnginePool getAIEnginePool() { + if (aiEnginePool == null) { + synchronized (this) { + if (aiEnginePool == null) { + aiEnginePool = new AIEnginePool(); + } + } + } + return aiEnginePool; + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/config/ConfigWebFactory.java b/core/src/main/java/lucee/runtime/config/ConfigWebFactory.java index d7bf7db5c4..0651fd91be 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigWebFactory.java +++ b/core/src/main/java/lucee/runtime/config/ConfigWebFactory.java @@ -87,6 +87,9 @@ import lucee.runtime.Component; import lucee.runtime.Mapping; import lucee.runtime.MappingImpl; +import lucee.runtime.PageContext; +import lucee.runtime.ai.AIEngine; +import lucee.runtime.ai.AIEngineFactory; import lucee.runtime.cache.CacheConnection; import lucee.runtime.cache.CacheConnectionImpl; import lucee.runtime.cache.ServerCacheConnection; @@ -127,6 +130,7 @@ import lucee.runtime.exp.ExpressionException; import lucee.runtime.exp.PageException; import lucee.runtime.exp.SecurityException; +import lucee.runtime.extension.ExtensionDefintion; import lucee.runtime.extension.RHExtension; import lucee.runtime.extension.RHExtensionProvider; import lucee.runtime.functions.other.CreateUUID; @@ -135,6 +139,8 @@ import lucee.runtime.gateway.GatewayEntryImpl; import lucee.runtime.listener.AppListenerUtil; import lucee.runtime.listener.ApplicationListener; +import lucee.runtime.listener.JavaSettings; +import lucee.runtime.listener.JavaSettingsImpl; import lucee.runtime.listener.MixedAppListener; import lucee.runtime.listener.ModernAppListener; import lucee.runtime.listener.SerializationSettings; @@ -175,7 +181,6 @@ import lucee.runtime.tag.listener.TagListener; import lucee.runtime.type.Array; import lucee.runtime.type.Collection.Key; -import lucee.runtime.type.KeyImpl; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; import lucee.runtime.type.UDF; @@ -538,13 +543,12 @@ synchronized static void load(ConfigServerImpl cs, ConfigImpl config, ConfigWebI _loadSecurity(cs, config, root, log); if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded security"); } - try { - ConfigWebUtil.loadLib(cs, config); - } - catch (Throwable t) { - ExceptionUtil.rethrowIfNecessary(t); - log(config, log, t); + + if (!essentialOnly) { + _loadJavaSettings(cs, config, root, log); // define compile type + if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded java settings"); } + if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded lib"); if (!essentialOnly) { @@ -560,8 +564,16 @@ synchronized static void load(ConfigServerImpl cs, ConfigImpl config, ConfigWebI if (!essentialOnly) { _loadExtensionBundles(cs, config, root, log); - if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded extension bundles"); + if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded extension"); + + } + else { + _loadExtensionDefinition(cs, config, root, log); + if (LOG) + LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded extension definitions"); + } + if (!essentialOnly) { _loadWS(cs, config, root, log); if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded webservice"); @@ -677,6 +689,9 @@ synchronized static void load(ConfigServerImpl cs, ConfigImpl config, ConfigWebI _loadLogin(cs, config, root, log); if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded login"); + _loadAI(cs, config, root, log); + if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded AI"); + _loadStartupHook(cs, config, root, log); if (LOG) LogUtil.logGlobal(ThreadLocalPageContext.getConfig(cs == null ? config : cs), Log.LEVEL_DEBUG, ConfigWebFactory.class.getName(), "loaded startup hook"); } @@ -724,7 +739,7 @@ private static boolean createSaltAndPW(Struct root, Config config, boolean essen } } else { - LogUtil.log(config, Log.LEVEL_ERROR, "application", "no password set and no password file found at [" + pwFile + "]"); + LogUtil.log(config, Log.LEVEL_DEBUG, "application", "no password set and no password file found at [" + pwFile + "]"); } } return rtn; @@ -846,29 +861,27 @@ else if (!StringUtil.isEmpty(strProviderCFC) && !StringUtil.isEmpty(strProviderS } } - private static ClassDefinition getClassDefinition(Struct data, String prefix, Identification id) { + private static ClassDefinition getClassDefinition(Struct data, String prefix, Identification id) throws PageException { + String attrName; String cn; - String bn; - String bv; + + // FUTURE remove if (StringUtil.isEmpty(prefix)) { cn = getAttr(data, "class"); - bn = getAttr(data, "bundleName"); - bv = getAttr(data, "bundleVersion"); + attrName = "class"; } else { if (prefix.endsWith("-")) prefix = prefix.substring(0, prefix.length() - 1); cn = getAttr(data, prefix + "Class"); - bn = getAttr(data, prefix + "BundleName"); - bv = getAttr(data, prefix + "BundleVersion"); + attrName = prefix + "Class"; } - // proxy jar libary no longer provided, so if still this class name is used .... - if ("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(cn)) { - cn = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + // proxy jar library no longer provided, so if still this class name is used .... + if (cn != null && "com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(cn)) { + data.set(attrName, "com.microsoft.sqlserver.jdbc.SQLServerDriver"); } - ClassDefinition cd = new ClassDefinitionImpl(cn, bn, bv, id); - // if(!StringUtil.isEmpty(cd.className,true))cd.getClazz(); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(data, prefix, true, id); return cd; } @@ -940,6 +953,53 @@ private static void _loadCacheHandler(ConfigServerImpl configServer, ConfigImpl } } + private static void _loadAI(ConfigServerImpl configServer, ConfigImpl config, Struct root, Log log) { + try { + boolean hasCS = configServer != null; + + // we only load this for the server context + if (hasCS) return; + Struct ai = ConfigWebUtil.getAsStruct(root, false, "ai"); + if (ai != null) { + + ClassDefinition cd; + String strId; + Iterator> it = ai.entryIterator(); + Entry entry; + Struct data, custom; + String _default; + Map engines = new HashMap<>(); + + while (it.hasNext()) { + try { + entry = it.next(); + data = Caster.toStruct(entry.getValue(), null); + if (data == null) continue; + strId = entry.getKey().getString(); + + cd = getClassDefinition(data, "", config.getIdentification()); + if (cd.hasClass() && !StringUtil.isEmpty(strId)) { + strId = strId.trim().toLowerCase(); + + custom = Caster.toStruct(data.get(KeyConstants._custom, null), null); + if (custom == null) custom = Caster.toStruct(data.get(KeyConstants._properties, null), null); + if (custom == null) custom = Caster.toStruct(data.get(KeyConstants._arguments, null), null); + _default = Caster.toString(data.get(KeyConstants._default, null), null); + engines.put(strId, AIEngineFactory.load(config, cd, custom, strId, _default, false)); + } + } + catch (Exception e) { + log(config, log, e); + } + } + config.setAIEngineFactories(engines); + } + } + catch (Exception ex) { + log(config, log, ex); + } + } + private static void _loadDumpWriter(ConfigServerImpl configServer, ConfigImpl config, Struct root, Log log) { try { boolean hasCS = configServer != null; @@ -2286,7 +2346,7 @@ else if (access >= SecurityManager.VALUE_1 && access <= SecurityManager.VALUE_10 else if (hasCS) config.setPSQL(configServer.getPSQL()); // Data Sources - Struct dataSources = ConfigWebUtil.getAsStruct("dataSources", root); + Struct dataSources = ConfigWebUtil.getAsStruct(root, false, "dataSources"); if (accessCount == -1) accessCount = dataSources.size(); if (dataSources.size() < accessCount) accessCount = dataSources.size(); @@ -2302,7 +2362,7 @@ else if (access >= SecurityManager.VALUE_1 && access <= SecurityManager.VALUE_10 dataSource = Caster.toStruct(e.getValue(), null); if (dataSource == null) continue; - if (dataSource.containsKey("database")) { + if (dataSource.containsKey(KeyConstants._database)) { try { // do we have an id? jdbc = config.getJDBCDriverById(getAttr(dataSource, "id"), null); @@ -2354,7 +2414,7 @@ else if (!cd.isBundle()) { Caster.toLongValue(getAttr(dataSource, "metaCacheTimeout"), 60000), toBoolean(getAttr(dataSource, "blob"), true), toBoolean(getAttr(dataSource, "clob"), true), Caster.toIntValue(getAttr(dataSource, "allow"), DataSource.ALLOW_ALL), toBoolean(getAttr(dataSource, "validate"), false), toBoolean(getAttr(dataSource, "storage"), false), getAttr(dataSource, "timezone"), - toStruct(getAttr(dataSource, "custom")), getAttr(dataSource, "dbdriver"), ParamSyntax.toParamSyntax(dataSource, ParamSyntax.DEFAULT), + ConfigWebUtil.getAsStruct(dataSource, true, "custom"), getAttr(dataSource, "dbdriver"), ParamSyntax.toParamSyntax(dataSource, ParamSyntax.DEFAULT), toBoolean(getAttr(dataSource, "literalTimestampWithTSOffset"), false), toBoolean(getAttr(dataSource, "alwaysSetTimeout"), false), toBoolean(getAttr(dataSource, "requestExclusive"), false), toBoolean(getAttr(dataSource, "alwaysResetConnections"), false) @@ -2448,7 +2508,7 @@ public static JDBCDriver[] _loadJDBCDrivers(ConfigServerImpl configServer, Confi try { Bundle bundle = OSGiUtil.loadBundle(cd.getName(), cd.getVersion(), config.getIdentification(), null, false); String cn = JDBCDriver.extractClassName(bundle); - cd = new ClassDefinitionImpl(config.getIdentification(), cn, cd.getName(), cd.getVersion()); + cd = new ClassDefinitionImpl(cn, cd.getName(), cd.getVersionAsString(), config.getIdentification()); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -2608,7 +2668,7 @@ else if (hasCS) { } { - Struct custom = toStruct(getAttr(data, "custom")); + Struct custom = ConfigWebUtil.getAsStruct(data, true, "custom"); // Workaround for old EHCache class definitions if (cd.getClassName() != null && cd.getClassName().endsWith(".EHCacheLite")) { @@ -2776,7 +2836,7 @@ private static void _loadGateway(ConfigServerImpl configServer, final ConfigImpl id = e.getKey().getLowerString(); ge = new GatewayEntryImpl(id, getClassDefinition(eConnection, "", config.getIdentification()), getAttr(eConnection, "cfcPath"), - getAttr(eConnection, "listenerCFCPath"), getAttr(eConnection, "startupMode"), toStruct(getAttr(eConnection, "custom")), + getAttr(eConnection, "listenerCFCPath"), getAttr(eConnection, "startupMode"), ConfigWebUtil.getAsStruct(eConnection, true, "custom"), Caster.toBooleanValue(getAttr(eConnection, "readOnly"), false)); if (!StringUtil.isEmpty(id)) { @@ -2805,25 +2865,6 @@ else if (hasCS) { } } - private static Struct toStruct(String str) { - - Struct sct = new StructImpl(StructImpl.TYPE_LINKED); - try { - String[] arr = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(str, '&')); - - String[] item; - for (int i = 0; i < arr.length; i++) { - item = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(arr[i], '=')); - if (item.length == 2) sct.setEL(KeyImpl.init(URLDecoder.decode(item[0], true).trim()), URLDecoder.decode(item[1], true)); - else if (item.length == 1) sct.setEL(KeyImpl.init(URLDecoder.decode(item[0], true).trim()), ""); - } - } - catch (PageException ee) { - } - - return sct; - } - private static void setDatasource(ConfigImpl config, Map datasources, String datasourceName, ClassDefinition cd, String server, String databasename, int port, String dsn, String user, String pass, TagListener listener, int connectionLimit, int idleTimeout, int liveTimeout, int minIdle, int maxIdle, int maxTotal, long metaCacheTimeout, boolean blob, boolean clob, int allow, boolean validate, boolean storage, String timezone, Struct custom, String dbdriver, ParamSyntax ps, @@ -3540,7 +3581,23 @@ private static void _loadUpdate(ConfigServer configServer, Config config, Struct * @param doc */ private static void _loadAdminMode(ConfigServerImpl config, Struct root) { - config.setAdminMode(ConfigWebUtil.toAdminMode(getAttr(root, "mode"), ConfigImpl.ADMINMODE_SINGLE)); + final short undefined = -1; + short am = undefined; + + // force by env var + String str = SystemUtil.getSystemPropOrEnvVar("lucee.admin.mode", null); + if (!StringUtil.isEmpty(str, true)) { + am = ConfigWebUtil.toAdminMode(str, undefined); + } + + // when not forced + if (am == undefined) { + am = ConfigWebUtil.toAdminMode(getAttr(root, "mode"), undefined); + if (am == undefined) { + am = ConfigWebUtil.toAdminMode(SystemUtil.getSystemPropOrEnvVar("lucee.admin.mode.default", null), ConfigImpl.ADMINMODE_SINGLE); + } + } + config.setAdminMode(am); } private static void _loadSetting(ConfigServerImpl configServer, ConfigImpl config, Struct root, Log log) { @@ -3630,10 +3687,7 @@ private static void _loadRemoteClient(ConfigServerImpl configServer, ConfigImpl Struct _clients = ConfigWebUtil.getAsStruct("remoteClients", root); // usage - String strUsage = getAttr(_clients, "usage"); - Struct sct; - if (!StringUtil.isEmpty(strUsage)) sct = toStruct(strUsage);// config.setRemoteClientUsage(toStruct(strUsage)); - else sct = new StructImpl(); + Struct sct = ConfigWebUtil.getAsStruct(_clients, true, "usage");// config.setRemoteClientUsage(toStruct(strUsage)); // TODO make this generic if (configServer != null) { String sync = Caster.toString(configServer.getRemoteClientUsage().get("synchronisation", ""), ""); @@ -3789,7 +3843,7 @@ else if (StringUtil.startsWithIgnoreCase(streamtype, "class:")) { String classname = streamtype.substring(6); try { - return (PrintStream) ClassUtil.loadInstance(classname); + return (PrintStream) ClassUtil.loadInstance((PageContext) null, classname); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -4227,6 +4281,7 @@ else if (strCompileType.equals("always")) { else if (hasCS) { config.setCompileType(configServer.getCompileType()); } + } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -4234,6 +4289,28 @@ else if (hasCS) { } } + private static void _loadJavaSettings(ConfigServerImpl configServer, ConfigImpl config, Struct root, Log log) { + try { + if (config instanceof ConfigServerImpl) { + + Resource lib = config.getLibraryDirectory(); + Resource[] libs = lib.listResources(ExtensionResourceFilter.EXTENSION_JAR_NO_DIR); + + ConfigServerImpl csi = (ConfigServerImpl) config; + Struct javasettings = ConfigWebUtil.getAsStruct(root, false, "javasettings"); + + if (javasettings != null && javasettings.size() > 0) { + JavaSettings js = JavaSettingsImpl.getInstance(config, javasettings, libs); + csi.setJavaSettings(js); + } + } + } + catch (Throwable t) { + ExceptionUtil.rethrowIfNecessary(t); + log(config, null, t); + } + } + private static void _loadConstants(ConfigServerImpl configServer, ConfigImpl config, Struct root) { try { boolean hasCS = configServer != null; @@ -4322,7 +4399,14 @@ private static void _loadStartupHook(ConfigServerImpl configServer, ConfigImpl c try { child = Caster.toStruct(it.next()); if (child == null) continue; + // component + String cfc = Caster.toString(child.get(KeyConstants._component, null), null); + if (!StringUtil.isEmpty(cfc, true)) { + // TODO start hook + continue; + } + // class ClassDefinition cd = getClassDefinition(child, "", config.getIdentification()); ConfigBase.Startup existing = config.getStartups().get(cd.getClassName()); @@ -4641,7 +4725,7 @@ private static void _loadDebug(ConfigServerImpl configServer, ConfigImpl config, if (e == null) continue; id = getAttr(e, "id"); list.put(id, new DebugEntry(id, getAttr(e, "type"), getAttr(e, "iprange"), getAttr(e, "label"), getAttr(e, "path"), getAttr(e, "fullname"), - toStruct(getAttr(e, "custom")))); + ConfigWebUtil.getAsStruct(e, true, "custom"))); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -4973,7 +5057,7 @@ private static void _loadExtensionBundles(ConfigServerImpl cs, ConfigImpl config if (!installedFiles.contains(r)) { // is maybe a diff version installed? - RHExtension ext = new RHExtension(config, r); + RHExtension ext = RHExtension.getInstance(config, r); if (!installedIds.contains(ext.getId())) { if (log != null) log.info("extension", "Found the extension [" + ext + "] in the installed folder that is not present in the configuration in any version, so we will uninstall it"); @@ -4998,6 +5082,43 @@ private static void _loadExtensionBundles(ConfigServerImpl cs, ConfigImpl config } } + private static void _loadExtensionDefinition(ConfigServerImpl cs, ConfigImpl config, Struct root, Log log) { + if (!(config instanceof ConfigServer)) return; + + try { + Log deployLog = config.getLog("deploy"); + if (deployLog != null) log = deployLog; + Array children = ConfigWebUtil.getAsArray("extensions", root); + // set + Map child; + Struct childSct; + String id; + Iterator it = children.valueIterator(); + List extensions = null; + while (it.hasNext()) { + childSct = Caster.toStruct(it.next(), null); + if (childSct == null) continue; + child = Caster.toStringMap(childSct, null); + + if (child == null) continue; + id = Caster.toString(childSct.get(KeyConstants._id, null), null); + + try { + if (extensions == null) extensions = new ArrayList<>(); + extensions.add(RHExtension.toExtensionDefinition(config, id, child)); + } + catch (Exception e) { + log(config, log, e); + } + } + if (extensions != null) config.setExtensionDefinitions(extensions); + } + catch (Throwable t) { + ExceptionUtil.rethrowIfNecessary(t); + log(config, log, t); + } + } + private static void _loadExtensionProviders(ConfigServerImpl configServer, ConfigImpl config, Struct root, Log log) { try { diff --git a/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java b/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java index da6e320f27..54d2580e0a 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java +++ b/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java @@ -1,6 +1,7 @@ package lucee.runtime.config; import java.io.IOException; +import java.util.Collection; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -13,8 +14,11 @@ import lucee.commons.io.res.Resource; import lucee.commons.io.res.ResourcesImpl.ResourceProviderFactory; import lucee.commons.lang.PhysicalClassLoader; +import lucee.runtime.ai.AIEngineFactory; +import lucee.runtime.ai.AIEnginePool; import lucee.runtime.config.gateway.GatewayMap; import lucee.runtime.exp.PageException; +import lucee.runtime.listener.JavaSettings; import lucee.runtime.writer.CFMLWriter; public class ConfigWebImpl implements ConfigWebPro { @@ -775,7 +779,7 @@ public lucee.runtime.config.ConfigServer getConfigServer(java.lang.String arg0, } @Override - public java.lang.ClassLoader getRPCClassLoader(boolean arg0, java.lang.ClassLoader[] arg1) throws java.io.IOException { + public ClassLoader getRPCClassLoader(boolean arg0, JavaSettings arg1) throws java.io.IOException { return instance.getRPCClassLoader(arg0, arg1); } @@ -1156,11 +1160,6 @@ public boolean isMailSendPartial() { return instance.isMailSendPartial(); } - @Override - public lucee.commons.io.res.util.ResourceClassLoader getResourceClassLoader(lucee.commons.io.res.util.ResourceClassLoader arg0) { - return instance.getResourceClassLoader(arg0); - } - public lucee.runtime.config.ConfigServerImpl getConfigServerImpl() { if (instance instanceof MultiContextConfigWeb) return ((MultiContextConfigWeb) instance).getConfigServerImpl(); return ((SingleContextConfigWeb) instance).getConfigServerImpl(); @@ -1393,11 +1392,6 @@ public int getQueryVarUsage() { return instance.getQueryVarUsage(); } - @Override - public lucee.commons.io.res.util.ResourceClassLoader getResourceClassLoader() { - return instance.getResourceClassLoader(); - } - @Override public ResourceProviderFactory[] getResourceProviderFactories() { return instance.getResourceProviderFactories(); @@ -1921,4 +1915,49 @@ public boolean getFormUrlAsStruct() { public int getReturnFormat() { return instance.getReturnFormat(); } + + @Override + public JavaSettings getJavaSettings(String id) { + return instance.getJavaSettings(id); + } + + @Override + public void setJavaSettings(String id, JavaSettings js) { + instance.setJavaSettings(id, js); + } + + @Override + public Resource getMavenDir() { + return instance.getMavenDir(); + } + + @Override + public JavaSettings getJavaSettings() { + return instance.getJavaSettings(); + } + + @Override + public Resource getExtensionInstalledDir() { + return instance.getExtensionInstalledDir(); + } + + @Override + public Resource getExtensionAvailableDir() { + return instance.getExtensionAvailableDir(); + } + + @Override + public Collection getAIEngineFactoryNames() { + return instance.getAIEngineFactoryNames(); + } + + @Override + public AIEngineFactory getAIEngineFactory(String name) { + return instance.getAIEngineFactory(name); + } + + @Override + public AIEnginePool getAIEnginePool() { + return instance.getAIEnginePool(); + } } diff --git a/core/src/main/java/lucee/runtime/config/ConfigWebUtil.java b/core/src/main/java/lucee/runtime/config/ConfigWebUtil.java index baabe40159..d79322ed39 100755 --- a/core/src/main/java/lucee/runtime/config/ConfigWebUtil.java +++ b/core/src/main/java/lucee/runtime/config/ConfigWebUtil.java @@ -31,8 +31,6 @@ import javax.servlet.ServletConfig; import javax.servlet.ServletContext; -import org.osgi.framework.BundleContext; - import lucee.commons.digest.MD5; import lucee.commons.io.IOUtil; import lucee.commons.io.SystemUtil; @@ -40,13 +38,11 @@ import lucee.commons.io.log.LogUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.ResourcesImpl; -import lucee.commons.io.res.filter.ExtensionResourceFilter; import lucee.commons.io.res.type.compress.CompressResource; import lucee.commons.io.res.type.compress.CompressResourceProvider; -import lucee.commons.io.res.util.ResourceClassLoader; import lucee.commons.io.res.util.ResourceUtil; -import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; +import lucee.commons.net.URLDecoder; import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; import lucee.runtime.Mapping; @@ -69,9 +65,6 @@ import lucee.runtime.monitor.Monitor; import lucee.runtime.net.http.ReqRspUtil; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.BundleBuilderFactory; -import lucee.runtime.osgi.BundleFile; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.security.SecurityManager; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; @@ -80,6 +73,7 @@ import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; import lucee.runtime.type.util.ArrayUtil; +import lucee.runtime.type.util.ListUtil; import lucee.transformer.library.function.FunctionLib; import lucee.transformer.library.tag.TagLib; @@ -185,57 +179,6 @@ private static void _deploy(ConfigWeb cw, Resource src, Resource trg) throws IOE } } - public static void reloadLib(Config config) throws IOException { - if (config instanceof ConfigWeb) loadLib(((ConfigWebImpl) config).getConfigServerImpl(), (ConfigPro) config); - else loadLib(null, (ConfigPro) config); - } - - static void loadLib(ConfigServer configServer, ConfigPro config) throws IOException { - // get lib and classes resources - Resource lib = config.getLibraryDirectory(); - Resource[] libs = lib.listResources(ExtensionResourceFilter.EXTENSION_JAR_NO_DIR); - - // get resources from server config and merge - if (configServer != null) { - ResourceClassLoader rcl = ((ConfigPro) configServer).getResourceClassLoader(); - libs = ResourceUtil.merge(libs, rcl.getResources()); - } - - CFMLEngine engine = ConfigWebUtil.getCFMLEngine(config); - BundleContext bc = engine.getBundleContext(); - Log log = ThreadLocalPageContext.getLog(config, "application"); - BundleFile bf; - List list = new ArrayList(); - for (int i = 0; i < libs.length; i++) { - try { - bf = BundleFile.getInstance(libs[i], true); - // jar is not a bundle - if (bf == null) { - // convert to a bundle - BundleBuilderFactory factory = new BundleBuilderFactory(libs[i]); - factory.setVersion("0.0.0.0"); - Resource tmp = SystemUtil.getTempFile("jar", false); - factory.build(tmp); - IOUtil.copy(tmp, libs[i]); - bf = BundleFile.getInstance(libs[i], true); - } - - OSGiUtil.start(OSGiUtil.installBundle(bc, libs[i], true)); - - } - catch (Throwable t) { - ExceptionUtil.rethrowIfNecessary(t); - list.add(libs[i]); - log.log(Log.LEVEL_ERROR, "OSGi", t); - } - } - - // set classloader - - ClassLoader parent = SystemUtil.getCoreClassLoader(); - ((ConfigImpl) config).setResourceClassLoader(new ResourceClassLoader(list.toArray(new Resource[list.size()]), parent)); - } - /** * touch a file object by the string definition * @@ -329,6 +272,7 @@ public static String replacePlaceholder(String str, Config config) { if (str.startsWith("}", 13)) str = checkResult(str, config.getConfigDir().getReal(str.substring(14))); else if (str.startsWith("-dir}", 13)) str = checkResult(str, config.getConfigDir().getReal(str.substring(18))); else if (str.startsWith("-directory}", 13)) str = checkResult(str, config.getConfigDir().getReal(str.substring(24))); + else if (str.startsWith("-file}", 13)) str = checkResult(str, config.getConfigFile().getReal(str.substring(19))); } else if (config != null && str.startsWith("{lucee-server")) { @@ -826,8 +770,10 @@ public static Array getAsArray(String parent, String child, Struct sct) { return getAsArray(child, getAsStruct(parent, sct)); } - public static Struct getAsStruct(Struct input, String... names) { + public static Struct getAsStruct(Struct input, boolean allowCSSString, String... names) { Struct sct = null; + if (input == null) return sct; + Object obj; for (String name: names) { obj = input.get(name, null); @@ -836,6 +782,17 @@ public static Struct getAsStruct(Struct input, String... names) { } } + if (allowCSSString && sct == null) { + for (String name: names) { + obj = input.get(name, null); + if (obj instanceof CharSequence && !StringUtil.isEmpty(obj.toString(), true)) { + sct = toStruct(obj.toString().trim()); + if (!sct.isEmpty()) break; + } + } + + } + if (sct == null) { sct = new StructImpl(Struct.TYPE_LINKED); input.put(names[0], sct); @@ -844,6 +801,25 @@ public static Struct getAsStruct(Struct input, String... names) { return sct; } + public static Struct toStruct(String str) { + + Struct sct = new StructImpl(StructImpl.TYPE_LINKED); + try { + String[] arr = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(str, '&')); + + String[] item; + for (int i = 0; i < arr.length; i++) { + item = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(arr[i], '=')); + if (item.length == 2) sct.setEL(KeyImpl.init(URLDecoder.decode(item[0], true).trim()), URLDecoder.decode(item[1], true)); + else if (item.length == 1) sct.setEL(KeyImpl.init(URLDecoder.decode(item[0], true).trim()), ""); + } + } + catch (PageException ee) { + } + + return sct; + } + // TODO /** * @deprecated use instead getAsStruct(Struct input, String... names) diff --git a/core/src/main/java/lucee/runtime/config/DeployHandler.java b/core/src/main/java/lucee/runtime/config/DeployHandler.java index 5984cd3951..31d7f1c527 100644 --- a/core/src/main/java/lucee/runtime/config/DeployHandler.java +++ b/core/src/main/java/lucee/runtime/config/DeployHandler.java @@ -61,7 +61,7 @@ public class DeployHandler { - private static final ResourceFilter ALL_EXT = new ExtensionResourceFilter(new String[] { ".lex", ".lar", ".lco" }); + private static final ResourceFilter ALL_EXT = new ExtensionResourceFilter(new String[] { ".lex", ".lar", ".lco", ".json" }); /** * deploys all files found @@ -99,6 +99,19 @@ public static void deploy(Config config, Log log, boolean force) { // Lucee core else if (config instanceof ConfigServer && "lco".equalsIgnoreCase(ext)) ConfigAdmin.updateCore((ConfigServerImpl) config, child, true); + // CFConfig + if ("json".equalsIgnoreCase(ext)) { + try { + CFConfigImport ci = new CFConfigImport(config, child, config.getResourceCharset(), null, "server", null, false, false, false); + ci.execute(true); + child.delete(); + } + catch (Exception e) { + DeployHandler.moveToFailedFolder(config.getDeployDirectory(), child); + throw Caster.toPageException(e); + } + } + } catch (Exception e) { log.log(Log.LEVEL_ERROR, "deploy handler", e); @@ -374,7 +387,7 @@ public static RHExtension deployExtension(Config config, ExtensionDefintion ed, // if not we try to download it if (log != null) log.info("extension", "Installing extension [" + ed + "] from remote extension provider"); - Resource res = downloadExtension(ci, ed, log); + Resource res = downloadExtension(ci, ed, log, throwOnError); if (res != null) { try { RHExtension _ext = ConfigAdmin._updateRHExtension((ConfigPro) config, res, reload, force, RHExtension.ACTION_MOVE); @@ -386,17 +399,16 @@ public static RHExtension deployExtension(Config config, ExtensionDefintion ed, else throw Caster.toPageException(e); } } - String name = StringUtil.emptyIfNull(ed.getId()).equals(ed.getSymbolicName()) ? ed.getSymbolicName() : (ed.getSymbolicName() + "(" + ed.getId() + ")"); - throw new ApplicationException("Failed to download extension [" + name + ":" + ed.getVersion() + "]"); + return null; } - public static Resource downloadExtension(Config config, ExtensionDefintion ed, Log log) { + public static Resource downloadExtension(Config config, ExtensionDefintion ed, Log log, boolean throwOnError) throws ApplicationException { Identification id = config.getIdentification(); String apiKey = id == null ? null : id.getApiKey(); URL url; RHExtensionProvider[] providers = ((ConfigPro) config).getRHExtensionProviders(); - + ApplicationException exp = null; for (int i = 0; i < providers.length; i++) { HTTPResponse rsp = null; try { @@ -407,7 +419,6 @@ public static Resource downloadExtension(Config config, ExtensionDefintion ed, L url = new URL(url, "/rest/extension/provider/full/" + ed.getId() + qs); if (log != null) log.info("main", "Check for extension at [" + url + "]"); - rsp = HTTPEngine4Impl.get(url, null, null, 5000, true, "UTF-8", "", null, new Header[] { new HeaderImpl("accept", "application/cfml") }); // If status code indicates success @@ -421,9 +432,9 @@ public static Resource downloadExtension(Config config, ExtensionDefintion ed, L return res; } - else { - if (log != null) log.warn("main", "Failed (" + rsp.getStatusCode() + ") to load extension: [" + ed + "] from [" + url + "]"); - } + // we want the first + if (throwOnError && exp == null) exp = new ApplicationException("Failed (" + rsp.getStatusCode() + ") to load extension: [" + ed + "] from [" + url + "]"); + if (log != null) log.warn("main", "Failed (" + rsp.getStatusCode() + ") to load extension: [" + ed + "] from [" + url + "]"); } catch (Exception e) { if (log != null) log.error("extension", e); @@ -432,6 +443,10 @@ public static Resource downloadExtension(Config config, ExtensionDefintion ed, L HTTPEngine.closeEL(rsp); } } + if (exp != null) { + throw exp; + } + return null; } @@ -457,7 +472,12 @@ public static Resource getExtension(Config config, ExtensionDefintion ed, Log lo } } // remote - return downloadExtension(config, ed, log); + try { + return downloadExtension(config, ed, log, false); + } + catch (ApplicationException e) { + return null; + } } public static ExtensionDefintion getLocalExtension(Config config, ExtensionDefintion ed, ExtensionDefintion defaultValue) { diff --git a/core/src/main/java/lucee/runtime/config/MultiContextConfigWeb.java b/core/src/main/java/lucee/runtime/config/MultiContextConfigWeb.java index 17ec6f2530..a35222cf7c 100644 --- a/core/src/main/java/lucee/runtime/config/MultiContextConfigWeb.java +++ b/core/src/main/java/lucee/runtime/config/MultiContextConfigWeb.java @@ -39,6 +39,8 @@ import lucee.runtime.CIPage; import lucee.runtime.Mapping; import lucee.runtime.PageContext; +import lucee.runtime.ai.AIEngineFactory; +import lucee.runtime.ai.AIEnginePool; import lucee.runtime.cache.tag.CacheHandlerCollection; import lucee.runtime.cfx.CFXTagPool; import lucee.runtime.compiler.CFMLCompilerImpl; @@ -610,4 +612,24 @@ public Resource getWebConfigDir() { public ServletConfig getServletConfig() { return config; } + + @Override + public Resource getMavenDir() { + return configServer.getMavenDir(); + } + + @Override + public Collection getAIEngineFactoryNames() { + return configServer.getAIEngineFactoryNames(); + } + + @Override + public AIEngineFactory getAIEngineFactory(String name) { + return configServer.getAIEngineFactory(name); + } + + @Override + public AIEnginePool getAIEnginePool() { + return configServer.getAIEnginePool(); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/config/SingleContextConfigWeb.java b/core/src/main/java/lucee/runtime/config/SingleContextConfigWeb.java index af7b123e03..6b3fdc0519 100644 --- a/core/src/main/java/lucee/runtime/config/SingleContextConfigWeb.java +++ b/core/src/main/java/lucee/runtime/config/SingleContextConfigWeb.java @@ -33,7 +33,6 @@ import lucee.commons.io.res.ResourcesImpl; import lucee.commons.io.res.ResourcesImpl.ResourceProviderFactory; import lucee.commons.io.res.type.compress.Compress; -import lucee.commons.io.res.util.ResourceClassLoader; import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.CharSet; import lucee.commons.lang.ClassException; @@ -47,6 +46,8 @@ import lucee.runtime.MappingImpl; import lucee.runtime.PageContext; import lucee.runtime.PageSource; +import lucee.runtime.ai.AIEngineFactory; +import lucee.runtime.ai.AIEnginePool; import lucee.runtime.cache.CacheConnection; import lucee.runtime.cache.tag.CacheHandler; import lucee.runtime.cache.tag.CacheHandlerCollection; @@ -77,6 +78,7 @@ import lucee.runtime.extension.RHExtensionProvider; import lucee.runtime.gateway.GatewayEngine; import lucee.runtime.listener.ApplicationListener; +import lucee.runtime.listener.JavaSettings; import lucee.runtime.lock.LockManager; import lucee.runtime.monitor.ActionMonitor; import lucee.runtime.monitor.ActionMonitorCollector; @@ -291,16 +293,6 @@ public ClassLoader getClassLoaderCore() { return cs.getClassLoaderCore(); } - @Override - public ResourceClassLoader getResourceClassLoader() { - return cs.getResourceClassLoader(); - } - - @Override - public ResourceClassLoader getResourceClassLoader(ResourceClassLoader defaultValue) { - return cs.getResourceClassLoader(defaultValue); - } - @Override public Locale getLocale() { return cs.getLocale(); @@ -730,8 +722,8 @@ public ClassLoader getRPCClassLoader(boolean reload) throws IOException { } @Override - public ClassLoader getRPCClassLoader(boolean reload, ClassLoader[] parents) throws IOException { - return cs.getRPCClassLoader(reload, parents); + public ClassLoader getRPCClassLoader(boolean reload, JavaSettings js) throws IOException { + return cs.getRPCClassLoader(reload, js); } @Override @@ -2101,4 +2093,48 @@ public int getReturnFormat() { return cs.getReturnFormat(); } + @Override + public JavaSettings getJavaSettings(String id) { + return cs.getJavaSettings(id); + } + + @Override + public void setJavaSettings(String id, JavaSettings js) { + cs.setJavaSettings(id, js); + } + + @Override + public Resource getMavenDir() { + return cs.getMavenDir(); + } + + @Override + public JavaSettings getJavaSettings() { + return cs.getJavaSettings(); + } + + @Override + public Resource getExtensionInstalledDir() { + return cs.getExtensionInstalledDir(); + } + + @Override + public Resource getExtensionAvailableDir() { + return cs.getExtensionAvailableDir(); + } + + @Override + public Collection getAIEngineFactoryNames() { + return cs.getAIEngineFactoryNames(); + } + + @Override + public AIEngineFactory getAIEngineFactory(String name) { + return cs.getAIEngineFactory(name); + } + + @Override + public AIEnginePool getAIEnginePool() { + return cs.getAIEnginePool(); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/db/HSQLDBHandler.java b/core/src/main/java/lucee/runtime/db/HSQLDBHandler.java index afaf7cc9ea..b75ec6ad30 100644 --- a/core/src/main/java/lucee/runtime/db/HSQLDBHandler.java +++ b/core/src/main/java/lucee/runtime/db/HSQLDBHandler.java @@ -301,7 +301,6 @@ private static String toUsableType(int type) { * @throws DatabaseException */ private static void removeTable(Connection conn, String name) throws SQLException { - name = name.replace('.', '_'); Statement stat = conn.createStatement(); stat.execute("DROP TABLE " + name); DBUtil.commitEL(conn); @@ -362,11 +361,8 @@ private static void _executeStatement(Connection conn, String sql) throws SQLExc * @throws DatabaseException */ private static Struct getUsedColumnsForQuery(Connection conn, SQL sql) { - - // TODO this could be potentially cached against the sql text - - Stopwatch stopwatch = new Stopwatch(Stopwatch.UNIT_MILLI); - stopwatch.start(); + // Stopwatch stopwatch = new Stopwatch(Stopwatch.UNIT_MILLI); + // stopwatch.start(); ResultSet rs = null; ResultSetMetaData rsmd = null; String view = "V_QOQ_TEMP"; @@ -467,7 +463,6 @@ public QueryImpl execute(PageContext pc, final SQL sql, int maxrows, int fetchsi } catch (Exception ex) { } - } catch (Exception e) { qoqException = e; @@ -476,7 +471,6 @@ public QueryImpl execute(PageContext pc, final SQL sql, int maxrows, int fetchsi // If our first pass at the QoQ failed, lets look at the exception to see what we want to do with // it. if (qoqException != null) { - // Track the root cause Exception rootCause = qoqException; @@ -557,31 +551,30 @@ public static QueryImpl __execute(PageContext pc, SQL sql, int maxrows, int fetc ConfigPro config = (ConfigPro) pc.getConfig(); DatasourceConnection dc = null; Connection conn = null; - try { - DatasourceConnPool pool = config.getDatasourceConnectionPool(config.getDataSource(QOQ_DATASOURCE_NAME), "sa", ""); - dc = pool.borrowObject(); - conn = dc.getConnection(); - - // executeStatement(conn, "CONNECT"); // create a new HSQLDB session for temp tables - DBUtil.setAutoCommitEL(conn, false); - - // sql.setSQLString(HSQLUtil.sqlToZQL(sql.getSQLString(),false)); + // TODO this is currently single threaded + synchronized (lock) { try { - // we now only lock the data loading, not the execution of the query, but should this be done via - // cflock by the developer? - synchronized (lock) { + DatasourceConnPool pool = config.getDatasourceConnectionPool(config.getDataSource(QOQ_DATASOURCE_NAME), "sa", ""); + dc = pool.borrowObject(); + conn = dc.getConnection(); + // executeStatement(conn, "CONNECT"); // TODO create a new HSQLDB session for temp tables + DBUtil.setAutoCommitEL(conn, false); + + try { Iterator it = tables.iterator(); - String cfQueryName = null; // name of the source query variable - String dbTableName = null; // name of the table in the database + String cfQueryName = null; // name of the source cfml query variable + String dbTableName = null; // name of the target table in the database String modSql = null; // int len=tables.size(); while (it.hasNext()) { cfQueryName = it.next().toString();// tables.get(i).toString(); dbTableName = cfQueryName.replace('.', '_'); - // this could match the wrong strings?? - modSql = StringUtil.replace(sql.getSQLString(), cfQueryName, dbTableName, false); - sql.setSQLString(modSql); + if (!cfQueryName.toLowerCase().equals(dbTableName.toLowerCase())){ + // TODO this could match the wrong strings?? + modSql = StringUtil.replace(sql.getSQLString(), cfQueryName, dbTableName, false); + sql.setSQLString(modSql); + } if (sql.getItems() != null && sql.getItems().length > 0) sql = new SQLImpl(sql.toString()); // temp tables still get created will all the source columns, // only populateTables is driven by the required columns calculated from the view @@ -630,32 +623,27 @@ public static QueryImpl __execute(PageContext pc, SQL sql, int maxrows, int fetc } } + catch (SQLException e) { + throw (IllegalQoQException) (new IllegalQoQException("QoQ HSQLDB: error executing sql statement on query.", e.getMessage(), sql, null).initCause(e)); + } } - catch (SQLException e) { - throw (IllegalQoQException) (new IllegalQoQException("QoQ HSQLDB: error executing sql statement on query.", e.getMessage(), sql, null).initCause(e)); - // DatabaseException de = new DatabaseException("QoQ HSQLDB: error executing sql statement on query - // [" + e.getMessage() + "]", null , sql, null); - // throw de; + catch (Exception ee) { + throw (IllegalQoQException) (new IllegalQoQException("QoQ HSQLDB: error executing sql statement on query.", ee.getMessage(), sql, null).initCause(ee)); } - } - catch (Exception ee) { - throw (IllegalQoQException) (new IllegalQoQException("QoQ HSQLDB: error executing sql statement on query.", ee.getMessage(), sql, null).initCause(ee)); - // DatabaseException de = new DatabaseException("QoQ HSQLDB: error executing sql statement on query - // [" + ee.getMessage() + "]", null , sql, null); - // throw ee; - } - finally { - if (conn != null) { - removeAll(conn, qoqTables); - // executeStatement(conn, "DISCONNECT"); // close HSQLDB session with temp tables - DBUtil.setAutoCommitEL(conn, true); - } - if (dc != null) ((DatasourceConnectionPro) dc).release(); + finally { + if (conn != null) { + removeAll(conn, qoqTables); + //executeStatement(conn, "DISCONNECT"); // close HSQLDB session with temp tables + DBUtil.setAutoCommitEL(conn, true); + } + if (dc != null) ((DatasourceConnectionPro) dc).release(); - // manager.releaseConnection(dc); + // manager.releaseConnection(dc); + } + // TODO we are swallowing errors, shouldn't be passing a null value back + if (nqr != null) nqr.setExecutionTime(stopwatch.time()); + return nqr; } - // TOOD we are swalloing errors, shouldn't be passing a null value bacl - if (nqr != null) nqr.setExecutionTime(stopwatch.time()); - return nqr; + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/db/QoQ.java b/core/src/main/java/lucee/runtime/db/QoQ.java index 1dedfc1fbd..703c4fc674 100644 --- a/core/src/main/java/lucee/runtime/db/QoQ.java +++ b/core/src/main/java/lucee/runtime/db/QoQ.java @@ -168,7 +168,7 @@ public QueryImpl execute(PageContext pc, SQL sql, Selects selects, int maxrows) /** * Order the rows in a query * - * @param target Query to order + * @param target Query to order * @param columns Column expressions to order on * @param isUnion Is this a union * @param sql @@ -188,16 +188,16 @@ private static void order(PageContext pc, QueryImpl target, Expression[] columns * Process a single select statement. If this is a union, append it to the incoming "previous" Query * and return the new, combined query with all rows * - * @param pc PageContext - * @param select Select instance - * @param source Source query to pull data from - * @param previous Previous query in case of union. May be empty if this is the first select in the - * union - * @param maxrows max rows from cfquery tag. Not necessarily the same as TOP - * @param sql SQL object + * @param pc PageContext + * @param select Select instance + * @param source Source query to pull data from + * @param previous Previous query in case of union. May be empty if this is the first select in the + * union + * @param maxrows max rows from cfquery tag. Not necessarily the same as TOP + * @param sql SQL object * @param hasOrders Is this overall Selects instance ordered? This affects whether we can optimize - * maxrows or not - * @param isUnion Is this select part of a union of several selects + * maxrows or not + * @param isUnion Is this select part of a union of several selects * @return * @throws PageException */ @@ -273,7 +273,7 @@ else if (isUnion && select.isUnionDistinct()) { * Combine two queries while retaining all rows. * * @param previous Query from previous select to union - * @param target New query to add into the previous + * @param target New query to add into the previous * @return Combined Query with potential duplicate rows * @throws PageException */ @@ -306,10 +306,10 @@ private QueryImpl doUnionAll(QueryImpl previous, QueryImpl target, SQL sql) thro /** * Combine two queries while removing duplicate rows * - * @param pc PageContext + * @param pc PageContext * @param previous Query from previous select to union - * @param target New query to add into the previous - * @param sql SQL instance + * @param target New query to add into the previous + * @param sql SQL instance * @return Combined Query with no duplicate rows * @throws PageException */ @@ -332,7 +332,7 @@ private QueryImpl doUnionDistinct(PageContext pc, QueryImpl previous, QueryImpl } // Initialize our object to track the partitions - QueryPartitions queryPartitions = new QueryPartitions(sql, selectExpressions, new Expression[0], newTarget, new HashSet(), this); + QueryPartitions queryPartitions = new QueryPartitions(sql, selectExpressions, new Expression[0], newTarget, new HashSet(), this, false); // Add in all the rows from our previous work getStream(previous).forEach(throwingIntConsumer(row -> { @@ -360,17 +360,17 @@ private QueryImpl doUnionDistinct(PageContext pc, QueryImpl previous, QueryImpl /** * Process a single select that is not partitioned (grouped or distinct) * - * @param pc PageContext - * @param select Select instance - * @param source Query we're select from - * @param target Query object we're adding rows into. (passed back by reference) - * @param maxrows Max rows from cfquery. + * @param pc PageContext + * @param select Select instance + * @param source Query we're select from + * @param target Query object we're adding rows into. (passed back by reference) + * @param maxrows Max rows from cfquery. * @param sql - * @param hasOrders Is this overall query ordered? - * @param isUnion Is this part of a union? + * @param hasOrders Is this overall query ordered? + * @param isUnion Is this part of a union? * @param trgColumns Lookup array of column - * @param trgValues Lookup array of expressions - * @param headers Select lists + * @param trgValues Lookup array of expressions + * @param headers Select lists * @throws PageException */ private void executeSingleNonPartitioned(PageContext pc, Select select, QueryImpl source, QueryImpl target, int maxrows, SQL sql, boolean hasOrders, boolean isUnion, @@ -450,17 +450,17 @@ public static Stream> getStream(QueryPartitions que /** * Process a single select that is partitioned (grouped) * - * @param pc PageContext - * @param select Select instance - * @param source Query we're selecting from - * @param target Query object we're adding rows into. (passed back by reference) + * @param pc PageContext + * @param select Select instance + * @param source Query we're selecting from + * @param target Query object we're adding rows into. (passed back by reference) * @param maxrows * @param sql - * @param hasOrders Is this overall query ordered? - * @param isUnion Is this part of a union? + * @param hasOrders Is this overall query ordered? + * @param isUnion Is this part of a union? * @param trgColumns Lookup array of column - * @param trgValues Lookup array of expressions - * @param headers select columns + * @param trgValues Lookup array of expressions + * @param headers select columns * @throws PageException */ private void executeSinglePartitioned(PageContext pc, Select select, QueryImpl source, QueryImpl target, int maxrows, SQL sql, boolean hasOrders, boolean isUnion, @@ -482,7 +482,7 @@ private void executeSinglePartitioned(PageContext pc, Select select, QueryImpl s Operation where = select.getWhere(); // Initialize object to track our partitioned data - QueryPartitions queryPartitions = new QueryPartitions(sql, select.getSelects(), select.getGroupbys(), target, select.getAdditionalColumns(), this); + QueryPartitions queryPartitions = new QueryPartitions(sql, select.getSelects(), select.getGroupbys(), target, select.getAdditionalColumns(), this, hasAggregateSelect); IntStream stream = getStream(source); if (where != null) { @@ -600,7 +600,7 @@ public Object getValue(PageContext pc, SQL sql, QueryImpl querySource, int row, } /** - * @param pc Page Context of the Request + * @param pc Page Context of the Request * @param table ZQLQuery * @return Query * @throws PageException @@ -614,8 +614,8 @@ private QueryImpl getSingleTable(PageContext pc, Column table) throws PageExcept * * @param sql * @param source Query Result - * @param exp expression to execute - * @param row current row of resultset + * @param exp expression to execute + * @param row current row of resultset * @return result * @throws PageException */ @@ -1091,9 +1091,9 @@ private Object executeBracked(PageContext pc, SQL sql, QueryImpl source, Bracket * execute an and operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1121,9 +1121,9 @@ private Object executeXor(PageContext pc, SQL sql, QueryImpl source, Operation2 * execute an equal operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1136,9 +1136,9 @@ private Object executeEQ(PageContext pc, SQL sql, QueryImpl source, Operation2 e * execute a not equal operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1151,9 +1151,9 @@ private Object executeNEQ(PageContext pc, SQL sql, QueryImpl source, Operation2 * execute a less than operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1166,9 +1166,9 @@ private Object executeLT(PageContext pc, SQL sql, QueryImpl source, Operation2 e * execute a less than or equal operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1181,9 +1181,9 @@ private Object executeLTE(PageContext pc, SQL sql, QueryImpl source, Operation2 * execute a greater than operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1196,9 +1196,9 @@ private Object executeGT(PageContext pc, SQL sql, QueryImpl source, Operation2 e * execute a greater than or equal operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1213,7 +1213,7 @@ private Object executeGTE(PageContext pc, SQL sql, QueryImpl source, Operation2 * @param sql * @param source QueryResult to execute on it * @param op - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1244,9 +1244,9 @@ private Object executeMod(PageContext pc, SQL sql, QueryImpl source, Operation2 * execute a greater than or equal operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1291,9 +1291,9 @@ private Integer castForMathInt(Object value) throws PageException { * execute a minus operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1314,9 +1314,9 @@ private Object executeMinus(PageContext pc, SQL sql, QueryImpl source, Operation * execute a divide operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1342,9 +1342,9 @@ private Object executeDivide(PageContext pc, SQL sql, QueryImpl source, Operatio * execute a multiply operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1365,9 +1365,9 @@ private Object executeMultiply(PageContext pc, SQL sql, QueryImpl source, Operat * execute a bitwise operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1388,9 +1388,9 @@ private Object executeBitwise(PageContext pc, SQL sql, QueryImpl source, Operati * execute a plus operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ @@ -1430,9 +1430,9 @@ private Object executePlus(PageContext pc, SQL sql, QueryImpl source, Operation2 * execute a between operation * * @param sql - * @param source QueryResult to execute on it + * @param source QueryResult to execute on it * @param expression - * @param row row of resultset to execute + * @param row row of resultset to execute * @return result * @throws PageException */ diff --git a/core/src/main/java/lucee/runtime/db/SQLCaster.java b/core/src/main/java/lucee/runtime/db/SQLCaster.java index 32be389e53..07430a4c08 100755 --- a/core/src/main/java/lucee/runtime/db/SQLCaster.java +++ b/core/src/main/java/lucee/runtime/db/SQLCaster.java @@ -35,6 +35,9 @@ import lucee.commons.date.JREDateTimeUtil; import lucee.commons.io.IOUtil; +import lucee.commons.io.SystemUtil; +import lucee.commons.io.log.Log; +import lucee.commons.io.log.LogUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.commons.sql.SQLUtil; @@ -56,6 +59,10 @@ * SQL Caster */ public final class SQLCaster { + private static final boolean allowEmptyAsNull; + static { + allowEmptyAsNull = Caster.toBooleanValue(SystemUtil.getSystemPropOrEnvVar("lucee.query.allowemptyasnull", null), false); + } private SQLCaster() { } @@ -155,23 +162,57 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, * case Types.ARRAY: stat.setArray(parameterIndex,toArray(item.getValue())); return; */ case Types.BIGINT: - stat.setLong(parameterIndex, Caster.toLongValue(value)); + try { + stat.setLong(parameterIndex, Caster.toLongValue(value)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type BIGINT. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.BIT: - stat.setBoolean(parameterIndex, Caster.toBooleanValue(value)); + try { + stat.setBoolean(parameterIndex, Caster.toBooleanValue(value)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type BIT. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.BLOB: - stat.setBlob(parameterIndex, SQLUtil.toBlob(stat.getConnection(), value)); + try { + stat.setBlob(parameterIndex, SQLUtil.toBlob(stat.getConnection(), value)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type BLOB. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } + return; case Types.CLOB: - stat.setClob(parameterIndex, SQLUtil.toClob(stat.getConnection(), value)); - /* - * if(value instanceof String) { try{ stat.setString(parameterIndex,Caster.toString(value)); } - * catch(Throwable t){ExceptionUtil.rethrowIfNecessary(t); - * stat.setClob(parameterIndex,SQLUtil.toClob(stat.getConnection(),value)); } - * - * } else stat.setClob(parameterIndex,SQLUtil.toClob(stat.getConnection(),value)); - */ + try { + stat.setClob(parameterIndex, SQLUtil.toClob(stat.getConnection(), value)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type CLOB. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.CHAR: String str = Caster.toString(value); @@ -181,36 +222,101 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, return; case Types.DECIMAL: case Types.NUMERIC: - stat.setDouble(parameterIndex, (Caster.toDoubleValue(value))); + try { + stat.setDouble(parameterIndex, (Caster.toDoubleValue(value))); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type NUMERIC|DECIMAL. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.DOUBLE: case Types.FLOAT: - if (type == Types.FLOAT) stat.setFloat(parameterIndex, Caster.toFloatValue(value)); - else if (type == Types.DOUBLE) stat.setDouble(parameterIndex, Caster.toDoubleValue(value)); - else stat.setObject(parameterIndex, Caster.toDouble(value), type); + try { + if (type == Types.FLOAT) stat.setFloat(parameterIndex, Caster.toFloatValue(value)); + else if (type == Types.DOUBLE) stat.setDouble(parameterIndex, Caster.toDoubleValue(value)); + else stat.setObject(parameterIndex, Caster.toDouble(value), type); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type DOUBLE|FLOAT. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.VARBINARY: case Types.LONGVARBINARY: case Types.BINARY: - stat.setObject(parameterIndex, Caster.toBinary(value), type); - //// stat.setBytes(parameterIndex,Caster.toBinary(value)); + try { + stat.setObject(parameterIndex, Caster.toBinary(value), type); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type VARBINARY|LONGVARBINARY|BINARY. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.REAL: - stat.setObject(parameterIndex, Caster.toFloat(value), type); - //// stat.setFloat(parameterIndex,Caster.toFloatValue(value)); + try { + stat.setObject(parameterIndex, Caster.toFloat(value), type); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type REAL. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.TINYINT: - stat.setObject(parameterIndex, Caster.toByte(value), type); - //// stat.setByte(parameterIndex,Caster.toByteValue(value)); + try { + stat.setObject(parameterIndex, Caster.toByte(value), type); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type TINYINT. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.SMALLINT: - stat.setObject(parameterIndex, Caster.toShort(value), type); - //// stat.setShort(parameterIndex,Caster.toShortValue(value)); + try { + stat.setObject(parameterIndex, Caster.toShort(value), type); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type SMALLINT. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.INTEGER: - stat.setObject(parameterIndex, Caster.toInteger(value), type); - //// stat.setInt(parameterIndex,Caster.toIntValue(value)); + try { + stat.setObject(parameterIndex, Caster.toInteger(value), type); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type INTEGER. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.SQLXML: @@ -239,23 +345,45 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, case Types.NVARCHAR: case CFTypes.VARCHAR2: stat.setObject(parameterIndex, Caster.toString(value), type); - //// stat.setString(parameterIndex,Caster.toString(value)); return; case Types.DATE: - - stat.setDate(parameterIndex, new Date(Caster.toDate(value, tz).getTime()), JREDateTimeUtil.getThreadCalendar(tz)); - // stat.setDate(parameterIndex,new Date((Caster.toDate(value,null).getTime()))); + try { + stat.setDate(parameterIndex, new Date(Caster.toDate(value, tz).getTime()), JREDateTimeUtil.getThreadCalendar(tz)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type DATE. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.TIME: - // stat.setObject(parameterIndex, new Time((Caster.toDate(value,null).getTime())), - // type); - stat.setTime(parameterIndex, new Time(Caster.toDate(value, tz).getTime()), JREDateTimeUtil.getThreadCalendar(tz)); + try { + stat.setTime(parameterIndex, new Time(Caster.toDate(value, tz).getTime()), JREDateTimeUtil.getThreadCalendar(tz)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type TIME. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.TIMESTAMP: - // stat.setObject(parameterIndex, new - // Timestamp((Caster.toDate(value,null).getTime())), type); - // stat.setObject(parameterIndex, value, type); - stat.setTimestamp(parameterIndex, new Timestamp(Caster.toDate(value, tz).getTime()), JREDateTimeUtil.getThreadCalendar(tz)); + try { + stat.setTimestamp(parameterIndex, new Timestamp(Caster.toDate(value, tz).getTime()), JREDateTimeUtil.getThreadCalendar(tz)); + } + catch (PageException pe) { + if (allowEmptyAsNull && !NullSupportHelper.full(pc) && value instanceof String && StringUtil.isEmpty((String) value)) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", "Deprecated functionality used at [" + LogUtil.caller(pc, "") + + "]. An empty string was passed as a value for type TIMESTAMP. Currently, this is treated as null, but it will be rejected in future releases."); + stat.setNull(parameterIndex, item.getType()); + } + else throw pe; + } return; case Types.OTHER: stat.setObject(parameterIndex, value, Types.OTHER); diff --git a/core/src/main/java/lucee/runtime/debug/DebuggerImpl.java b/core/src/main/java/lucee/runtime/debug/DebuggerImpl.java index 6885b660c4..5cd6c2ac8b 100644 --- a/core/src/main/java/lucee/runtime/debug/DebuggerImpl.java +++ b/core/src/main/java/lucee/runtime/debug/DebuggerImpl.java @@ -277,7 +277,7 @@ private ArrayList toArray() { } public static boolean debugQueryUsage(PageContext pageContext, QueryResult qr) { - if (pageContext.getConfig().debug() && qr instanceof Query) { + if (qr instanceof Query) { if (((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_QUERY_USAGE)) { ((Query) qr).enableShowQueryUsage(); return true; @@ -1010,7 +1010,7 @@ public Map>> getGenericData() { } public static void deprecated(PageContext pc, String key, String msg) { - if (pc.getConfig().debug()) { + if (PageContextUtil.show(pc)) { // do we already have set? boolean exists = false; Map>> gd = pc.getDebugger().getGenericData(); diff --git a/core/src/main/java/lucee/runtime/debug/DebuggerPool.java b/core/src/main/java/lucee/runtime/debug/DebuggerPool.java index 49f478901f..8c4d73e3d7 100644 --- a/core/src/main/java/lucee/runtime/debug/DebuggerPool.java +++ b/core/src/main/java/lucee/runtime/debug/DebuggerPool.java @@ -26,7 +26,6 @@ import lucee.runtime.config.ConfigWebPro; import lucee.runtime.exp.PageException; import lucee.runtime.net.http.ReqRspUtil; -import lucee.runtime.op.Duplicator; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; import lucee.runtime.type.Struct; @@ -43,16 +42,15 @@ public DebuggerPool(Resource storage) { public void store(PageContext pc, Debugger debugger) { if (ReqRspUtil.getScriptName(pc, pc.getHttpServletRequest()).indexOf("/lucee/") == 0) return; - synchronized (queue) { - try { - queue.add((Struct) Duplicator.duplicate(debugger.getDebuggingData(pc, true), true)); - } - catch (PageException e) { - } - - while (queue.size() > ((ConfigWebPro) pc.getConfig()).getDebugMaxRecordsLogged()) - queue.poll(); + try { + queue.add(debugger.getDebuggingData(pc, true)); + } + catch (PageException e) { } + + while (queue.size() > ((ConfigWebPro) pc.getConfig()).getDebugMaxRecordsLogged()) + queue.poll(); + } public Array getData(PageContext pc) { diff --git a/core/src/main/java/lucee/runtime/debug/DebuggerUtil.java b/core/src/main/java/lucee/runtime/debug/DebuggerUtil.java index d04d1cf8e4..3366c8eaca 100644 --- a/core/src/main/java/lucee/runtime/debug/DebuggerUtil.java +++ b/core/src/main/java/lucee/runtime/debug/DebuggerUtil.java @@ -65,11 +65,9 @@ private Struct _pointOutClosuresInPersistentScopes(PageContext pc, Struct sct, S } public static boolean debugQueryUsage(PageContext pageContext, Query query) { - if (pageContext.getConfig().debug() && query != null) { - if (((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_QUERY_USAGE)) { - query.enableShowQueryUsage(); - return true; - } + if (((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_QUERY_USAGE)) { + query.enableShowQueryUsage(); + return true; } return false; } diff --git a/core/src/main/java/lucee/runtime/dump/DumpUtil.java b/core/src/main/java/lucee/runtime/dump/DumpUtil.java index 0a8daced96..f8b5499b86 100644 --- a/core/src/main/java/lucee/runtime/dump/DumpUtil.java +++ b/core/src/main/java/lucee/runtime/dump/DumpUtil.java @@ -48,12 +48,15 @@ import lucee.commons.date.TimeZoneUtil; import lucee.commons.io.res.Resource; +import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.CharSet; +import lucee.commons.lang.ClassUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.IDGenerator; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; import lucee.runtime.coder.Base64Coder; +import lucee.runtime.config.ConfigPro; import lucee.runtime.converter.WDDXConverter; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.PageException; @@ -72,11 +75,9 @@ import lucee.runtime.type.Pojo; import lucee.runtime.type.QueryImpl; import lucee.runtime.type.Struct; -import lucee.runtime.type.StructImpl; import lucee.runtime.type.UDF; import lucee.runtime.type.dt.DateTimeImpl; import lucee.runtime.type.scope.CookieImpl; -import lucee.runtime.type.util.KeyConstants; import lucee.runtime.type.util.UDFUtil; public class DumpUtil { @@ -643,14 +644,8 @@ public static DumpData toDumpData(Object o, PageContext pageContext, int maxleve BundleClassLoader bcl = (BundleClassLoader) cl; Bundle b = bcl.getBundle(); if (b != null) { - Struct sct = new StructImpl(); - sct.setEL(KeyConstants._id, b.getBundleId()); - sct.setEL(KeyConstants._name, b.getSymbolicName()); - sct.setEL(KeyConstants._location, b.getLocation()); - sct.setEL(KeyConstants._version, b.getVersion().toString()); - DumpTable bd = new DumpTable("#d6ccc2", "#f5ebe0", "#000000"); - bd.setTitle("Bundle Info"); + bd.setTitle("OSGi Bundle Info"); bd.appendRow(0, new SimpleDumpData("id: " + b.getBundleId())); bd.appendRow(0, new SimpleDumpData("symbolic-name: " + b.getSymbolicName())); bd.appendRow(0, new SimpleDumpData("version: " + b.getVersion().toString())); @@ -662,6 +657,59 @@ public static DumpData toDumpData(Object o, PageContext pageContext, int maxleve catch (NoSuchMethodError e) { } } + else { + String path = ClassUtil.getSourcePathForClass(clazz, null); + if (path != null) { + Resource res = ResourceUtil.toResourceExisting(pageContext.getConfig(), path, null); + boolean printed = false; + if (res != null) { + // is Maven? + Resource mvnDir = ((ConfigPro) pageContext.getConfig()).getMavenDir(); + if (ResourceUtil.isChildOf(res, mvnDir)) { + String name = res.getName(); + if (name.endsWith(".jar")) { + + String pomName = name.substring(0, name.length() - 4) + ".pom"; + Resource pom = res.getParentResource().getRealResource(pomName); + if (pom.isFile()) { + try { + + Resource parent = res.getParentResource(); + String v = parent.getName(); + + parent = parent.getParentResource(); + String a = parent.getName(); + + parent = parent.getParentResource(); + String g = parent.getName(); + while (!mvnDir.equals(parent = parent.getParentResource())) { + g = parent.getName() + "." + g; + + } + + DumpTable bd = new DumpTable("#d6ccc2", "#f5ebe0", "#000000"); + bd.setTitle("Maven Info"); + bd.appendRow(0, new SimpleDumpData("groupId: " + g)); + bd.appendRow(0, new SimpleDumpData("artifactId: " + a)); + bd.appendRow(0, new SimpleDumpData("version: " + v)); + bd.appendRow(0, new SimpleDumpData("location: " + res.getAbsolutePath())); + table.appendRow(0, bd); + printed = true; + } + catch (Exception e) { + } + } + } + } + } + if (!printed) { + DumpTable bd = new DumpTable("#d6ccc2", "#f5ebe0", "#000000"); + bd.setTitle("Jar Info"); + bd.appendRow(0, new SimpleDumpData("location: " + path)); + table.appendRow(0, bd); + } + } + } return setId(id, table); // } diff --git a/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java b/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java index b01929ade2..15c4a24167 100644 --- a/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java +++ b/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java @@ -352,7 +352,7 @@ else if (installExtensions && (updateInfo.updateType == ConfigFactory.NEW_MINOR extensions = new HashSet(); } - // install extension defined + // install extension defined in env var /sys prop String extensionIds = StringUtil.unwrap(SystemUtil.getSystemPropOrEnvVar("lucee-extensions", null)); // old no longer used if (StringUtil.isEmpty(extensionIds, true)) extensionIds = StringUtil.unwrap(SystemUtil.getSystemPropOrEnvVar("lucee.extensions", null)); @@ -360,9 +360,12 @@ else if (installExtensions && (updateInfo.updateType == ConfigFactory.NEW_MINOR if (!StringUtil.isEmpty(extensionIds, true)) { this.envExt = extensionIds; LogUtil.log(Log.LEVEL_INFO, "deploy", "controller", "Extensions to install defined in env variable or system property:" + extensionIds); - List _extensions = RHExtension.toExtensionDefinitions(extensionIds); - extensions = toSet(extensions, _extensions); + extensions = toSet(extensions, RHExtension.toExtensionDefinitions(extensionIds)); + } + // install extension defined in .CFConfig.json + if (installExtensions && (updateInfo.updateType == ConfigFactory.NEW_FRESH || updateInfo.updateType == ConfigFactory.NEW_FROM4)) { + extensions = toSet(extensions, cs.getExtensionDefinitions()); } if (extensions.size() > 0) { @@ -371,7 +374,7 @@ else if (installExtensions && (updateInfo.updateType == ConfigFactory.NEW_MINOR StringBuilder failedSB = new StringBuilder(); boolean sucess = true; try { - results = DeployHandler.deployExtensions(cs, extensions.toArray(new ExtensionDefintion[extensions.size()]), null, false, false); + results = DeployHandler.deployExtensions(cs, extensions.toArray(new ExtensionDefintion[extensions.size()]), null, true, false); for (Entry e: results.entrySet()) { // failed if (!Boolean.TRUE.equals(e.getValue())) { @@ -517,7 +520,7 @@ public static Set toSet(Set set, List it = set.iterator(); while (it.hasNext()) { ed = it.next(); - map.put(ed.toString(), ed); + map.put(ed.getId() + "", ed); } } @@ -526,10 +529,11 @@ public static Set toSet(Set set, List it = list.iterator(); while (it.hasNext()) { ed = it.next(); - map.put(ed.toString(), ed); + map.put(ed.getId() + "", ed); } } + // TODO remove this and simply return a collection // to Set LinkedHashSet rtn = new LinkedHashSet(); Iterator it = map.values().iterator(); @@ -624,8 +628,8 @@ private int deployBundledExtension(ConfigServerImpl cs, boolean validate) { log.info("extract-extension", "Copy extension [" + name + "] to temp directory [" + temp + "]"); ResourceUtil.touch(temp); Util.copy(is, temp.getOutputStream(), false, true); - rhe = new RHExtension(cs, temp); - rhe.validate(); + rhe = RHExtension.getInstance(cs, temp); + rhe.validate(cs); ExtensionDefintion alreadyExists = null; it = existing.iterator(); while (it.hasNext()) { diff --git a/core/src/main/java/lucee/runtime/engine/Controler.java b/core/src/main/java/lucee/runtime/engine/Controler.java index cf97021a8d..c3a916fb34 100755 --- a/core/src/main/java/lucee/runtime/engine/Controler.java +++ b/core/src/main/java/lucee/runtime/engine/Controler.java @@ -211,24 +211,6 @@ private void control(CFMLFactoryImpl[] factories, boolean firstRun, Log log) { if (log != null) log.error("controler", t); } - if (firstRun) { - - try { - RHExtension.correctExtensions(configServer); - } - catch (Exception e) { - if (log != null) log.error("controler", e); - } - - // loading all versions from Maven (if it can be reached) - try { - new MavenUpdateProvider().list(); - } - catch (Exception e) { - if (log != null) log.error("controler", e); - } - } - // every 10 seconds if (do10Seconds) { // deploy extensions, archives ... @@ -271,6 +253,24 @@ private void control(CFMLFactoryImpl[] factories, boolean firstRun, Log log) { for (int i = 0; i < factories.length; i++) { control(factories[i], do10Seconds, doMinute, doHour, firstRun, log); } + + if (firstRun) { + + try { + RHExtension.correctExtensions(configServer); + } + catch (Exception e) { + if (log != null) log.error("controler", e); + } + + // loading all versions from Maven (if it can be reached) + try { + new MavenUpdateProvider().list(); + } + catch (Exception e) { + if (log != null) log.error("controler", e); + } + } } private void control(CFMLFactoryImpl cfmlFactory, boolean do10Seconds, boolean doMinute, boolean doHour, boolean firstRun, Log log) { @@ -287,13 +287,6 @@ private void control(CFMLFactoryImpl cfmlFactory, boolean do10Seconds, boolean d checkOldClientFile(config, log); - try { - RHExtension.correctExtensions(config); - } - catch (Exception e) { - if (log != null) log.error("controler", e); - } - try { checkTempDirectorySize(config); } diff --git a/core/src/main/java/lucee/runtime/engine/DebugExecutionLog.java b/core/src/main/java/lucee/runtime/engine/DebugExecutionLog.java index 76ef702876..cd4e517d73 100644 --- a/core/src/main/java/lucee/runtime/engine/DebugExecutionLog.java +++ b/core/src/main/java/lucee/runtime/engine/DebugExecutionLog.java @@ -22,6 +22,7 @@ import lucee.runtime.PageContext; import lucee.runtime.debug.DebugEntry; +import lucee.runtime.util.PageContextUtil; public class DebugExecutionLog extends ExecutionLogSupport { @@ -35,7 +36,7 @@ protected void _init(PageContext pc, Map arguments) { @Override protected void _log(int startPos, int endPos, long startTime, long endTime) { - if (!pc.getConfig().debug()) return; + if (!PageContextUtil.debug(pc)) return; long diff = endTime - startTime; if (unit == UNIT_MICRO) diff /= 1000; diff --git a/core/src/main/java/lucee/runtime/engine/ThreadLocalPageContext.java b/core/src/main/java/lucee/runtime/engine/ThreadLocalPageContext.java index cc61254484..57181c14ab 100644 --- a/core/src/main/java/lucee/runtime/engine/ThreadLocalPageContext.java +++ b/core/src/main/java/lucee/runtime/engine/ThreadLocalPageContext.java @@ -76,7 +76,7 @@ public static PageContext get(boolean cloneParentIfNotExist) { if (cloneParentIfNotExist && pc == null) { PageContext pci = pcThreadLocalInheritable.get(); // we have one from parent - if (pci != null) { + if (pci != null && pci.getRequest() != null) { try { // this is needed because clone below call this method a lot if (Boolean.TRUE.equals(insideInheritableRegistration.get())) return pci; diff --git a/core/src/main/java/lucee/runtime/extension/ExtensionDefintion.java b/core/src/main/java/lucee/runtime/extension/ExtensionDefintion.java index 0307f0cdbe..f838a8ac3a 100644 --- a/core/src/main/java/lucee/runtime/extension/ExtensionDefintion.java +++ b/core/src/main/java/lucee/runtime/extension/ExtensionDefintion.java @@ -134,7 +134,7 @@ public RHExtension toRHExtension() throws PageException, IOException, BundleExce // MUST try to load the Extension throw new ApplicationException("ExtensionDefinition does not contain the necessary data to create the requested object."); } - rhe = new RHExtension(config, source); + rhe = RHExtension.getInstance(config, source); return rhe; } diff --git a/core/src/main/java/lucee/runtime/extension/RHExtension.java b/core/src/main/java/lucee/runtime/extension/RHExtension.java index 92d98f8866..a96d32222c 100644 --- a/core/src/main/java/lucee/runtime/extension/RHExtension.java +++ b/core/src/main/java/lucee/runtime/extension/RHExtension.java @@ -34,6 +34,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; @@ -73,7 +74,6 @@ import lucee.runtime.converter.ConverterException; import lucee.runtime.converter.JSONConverter; import lucee.runtime.converter.JSONDateFormat; -import lucee.runtime.db.ClassDefinition; import lucee.runtime.engine.ThreadLocalConfig; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.ApplicationException; @@ -198,26 +198,43 @@ public class RHExtension implements Serializable { private String eventGatewayInstancesJson; - private boolean loaded; + public boolean softLoaded = false; - private final Config config; + private static Map instances = new ConcurrentHashMap<>(); - public boolean softLoaded = false; + public static RHExtension getInstance(Config config, Resource ext) throws PageException, IOException, BundleException, ConverterException { + RHExtension instance = instances.get(ext.getAbsolutePath()); + if (instance == null) { + instance = new RHExtension(config, ext); + instances.put(instance.getId() + ":" + instance.getVersion(), instance); + instances.put(ext.getAbsolutePath(), instance); + } + return instance; + } + + private RHExtension(Config config, Resource ext) throws PageException, IOException, BundleException, ConverterException { + init(config, ext); + } + + public static RHExtension getInstance(Config config, String id, String version) throws PageException, IOException, BundleException, ConverterException { + RHExtension instance = instances.get(id + ":" + version); + if (instance == null) { + instance = new RHExtension(config, id, version); + instances.put(instance.getId() + ":" + instance.getVersion(), instance); + instances.put(instance.extensionFile.getAbsolutePath(), instance); + } + return instance; - public RHExtension(Config config, Resource ext) throws PageException, IOException, BundleException, ConverterException { - this.config = config; - init(ext); } public RHExtension(Config config, String id, String version) throws PageException, IOException, BundleException, ConverterException { - this.config = config; Struct data = getMetaData(config, id, version, (Struct) null); this.extensionFile = getExtensionInstalledFile(config, id, version, false); // do we have usefull meta data? if (data != null && data.containsKey("startBundles")) { try { - readManifestConfig(id, data, extensionFile.getAbsolutePath(), null); + readManifestConfig(config, id, data, extensionFile.getAbsolutePath(), null); softLoaded = true; return; } @@ -228,18 +245,18 @@ public RHExtension(Config config, String id, String version) throws PageExceptio } } - init(this.extensionFile); + init(config, this.extensionFile); softLoaded = false; } - private void init(Resource ext) throws PageException, IOException, BundleException, ConverterException { + private void init(Config config, Resource ext) throws PageException, IOException, BundleException, ConverterException { // make sure the config is registerd with the thread if (ThreadLocalPageContext.getConfig() == null) ThreadLocalConfig.register(config); // is it a web or server context? this.type = config instanceof ConfigWeb ? "web" : "server"; this.extensionFile = ext; - load(ext); + load(config, ext); // write metadata to XML Resource mdf = getMetaDataFile(config, id, version); if (!metadataFilesChecked.contains(mdf.getAbsolutePath()) && !mdf.isFile()) { @@ -289,12 +306,12 @@ public static boolean isInstalled(Config config, String id, String version) thro * @throws ConverterException * @throws IOException */ - public Resource copyToInstalled() throws PageException, ConverterException, IOException { + public Resource copyToInstalled(Config config) throws PageException, ConverterException, IOException { if (extensionFile == null) throw new IOException("no extension file defined"); if (!extensionFile.isFile()) throw new IOException("given extension file [" + extensionFile + "] does not exist"); - addToAvailable(extensionFile); - return act(extensionFile, RHExtension.ACTION_COPY); + addToAvailable(config, extensionFile); + return act(config, extensionFile, RHExtension.ACTION_COPY); } /** @@ -306,12 +323,12 @@ public Resource copyToInstalled() throws PageException, ConverterException, IOEx * @throws ConverterException * @throws IOException */ - public Resource moveToInstalled() throws PageException, ConverterException, IOException { + public Resource moveToInstalled(Config config) throws PageException, ConverterException, IOException { if (extensionFile == null) throw new IOException("no extension file defined"); if (!extensionFile.isFile()) throw new IOException("given extension file [" + extensionFile + "] does not exist"); - addToAvailable(extensionFile); - return act(extensionFile, RHExtension.ACTION_MOVE); + addToAvailable(config, extensionFile); + return act(config, extensionFile, RHExtension.ACTION_MOVE); } public static void storeMetaData(Config config, String id, String version, Struct data) throws ConverterException, IOException { @@ -327,7 +344,7 @@ private static void storeMetaData(Resource file, Struct data) throws ConverterEx } // copy the file to extension dir if it is not already there - private Resource act(Resource ext, short action) throws PageException { + private Resource act(Config config, Resource ext, short action) throws PageException { Resource trg; Resource trgDir; try { @@ -351,14 +368,14 @@ else if (action == ACTION_MOVE) { return trg; } - public void addToAvailable() { - addToAvailable(getExtensionFile()); + public void addToAvailable(Config config) { + addToAvailable(config, getExtensionFile()); } - private void addToAvailable(Resource ext) { + private void addToAvailable(Config config, Resource ext) { if (id == null) { try { - load(ext); + load(config, ext); } catch (Exception e) { LogUtil.log("deploy", "extension", e); @@ -423,9 +440,7 @@ public static Manifest getManifestFromFile(Config config, Resource file) throws } - private void load(Resource ext) throws IOException, BundleException, ApplicationException { - // print.ds(ext.getAbsolutePath()); - loaded = true; + private void load(Config config, Resource ext) throws IOException, BundleException, ApplicationException { // no we read the content of the zip ZipInputStream zis = new ZipInputStream(IOUtil.toBufferedInputStream(ext.getInputStream())); ZipEntry entry; @@ -524,7 +539,7 @@ else if (!entry.isDirectory() && (startsWith(path, type, "web.applications") || // read the manifest if (manifest == null) throw new ApplicationException("The Extension [" + ext + "] is invalid,no Manifest file was found at [META-INF/MANIFEST.MF]."); - readManifestConfig(manifest, ext.getAbsolutePath(), _img); + readManifestConfig(config, manifest, ext.getAbsolutePath(), _img); this.jars = jars.toArray(new String[jars.size()]); this.flds = flds.toArray(new String[flds.size()]); @@ -544,7 +559,7 @@ else if (!entry.isDirectory() && (startsWith(path, type, "web.applications") || } - private void readManifestConfig(Manifest manifest, String label, String _img) throws ApplicationException { + private void readManifestConfig(Config config, Manifest manifest, String label, String _img) throws ApplicationException { boolean isWeb = config instanceof ConfigWeb; type = isWeb ? "web" : "server"; Log logger = ThreadLocalPageContext.getLog(config, "deploy"); @@ -558,7 +573,7 @@ private void readManifestConfig(Manifest manifest, String label, String _img) th readVersion(label, StringUtil.unwrap(attr.getValue("version"))); label += " : " + version; readId(label, StringUtil.unwrap(attr.getValue("id"))); - readReleaseType(label, StringUtil.unwrap(attr.getValue("release-type")), isWeb); + readReleaseType(config, label, StringUtil.unwrap(attr.getValue("release-type")), isWeb); description = StringUtil.unwrap(attr.getValue("description")); trial = Caster.toBooleanValue(StringUtil.unwrap(attr.getValue("trial")), false); if (_img == null) _img = StringUtil.unwrap(attr.getValue("image")); @@ -584,7 +599,7 @@ private void readManifestConfig(Manifest manifest, String label, String _img) th readEventGatewayInstances(label, StringUtil.unwrap(attr.getValue("event-gateway-instance")), logger); } - private void readManifestConfig(String id, Struct data, String label, String _img) throws ApplicationException { + private void readManifestConfig(Config config, String id, Struct data, String label, String _img) throws ApplicationException { boolean isWeb = config instanceof ConfigWeb; type = isWeb ? "web" : "server"; @@ -597,7 +612,7 @@ private void readManifestConfig(String id, Struct data, String label, String _im readVersion(label, ConfigWebFactory.getAttr(data, "version")); label += " : " + version; readId(label, StringUtil.isEmpty(id) ? ConfigWebFactory.getAttr(data, "id") : id); - readReleaseType(label, ConfigWebFactory.getAttr(data, "releaseType", "release-type"), isWeb); + readReleaseType(config, label, ConfigWebFactory.getAttr(data, "releaseType", "release-type"), isWeb); description = ConfigWebFactory.getAttr(data, "description"); trial = Caster.toBooleanValue(ConfigWebFactory.getAttr(data, "trial"), false); if (_img == null) _img = ConfigWebFactory.getAttr(data, "image"); @@ -739,7 +754,7 @@ private void readCoreVersion(String label, String str, Info info) { */ } - public void validate() throws ApplicationException { + public void validate(Config config) throws ApplicationException { validate(ConfigWebUtil.getEngine(config).getInfo()); } @@ -772,7 +787,7 @@ private void readCategories(String label, String cat) { else categories = null; } - private void readReleaseType(String label, String str, boolean isWeb) throws ApplicationException { + private void readReleaseType(Config config, String label, String str, boolean isWeb) throws ApplicationException { if (((ConfigPro) ThreadLocalPageContext.getConfig(config)).getAdminMode() == ConfigImpl.ADMINMODE_SINGLE) return; // release type int rt = RELEASE_TYPE_ALL; @@ -875,7 +890,7 @@ public static String toHash(String id, String version, String ext) { } public static Resource getExtensionInstalledDir(Config config) { - return config.getConfigDir().getRealResource("extensions/installed"); + return ((ConfigPro) config).getExtensionInstalledDir(); } private static int getPhysicalExtensionCount(Config config) { @@ -893,9 +908,10 @@ public boolean accept(Resource res, String name) { public static void correctExtensions(Config config) throws PageException, IOException, BundleException, ConverterException { // reduce the amount of extension stored in available { - int max = 5; - Resource dir = config.getConfigDir().getRealResource("extensions/available"); + int max = 2; + Resource dir = ((ConfigPro) config).getExtensionAvailableDir(); Resource[] resources = dir.listResources(LEX_FILTER); + if (resources.length < 60) return; Map>> map = new HashMap<>(); RHExtension ext; List> versions; @@ -1136,7 +1152,7 @@ private static Query createQuery() throws DatabaseException { 0, "Extensions"); } - private void populate(Query qry) throws PageException, IOException, BundleException { + private void populate(Query qry) throws PageException { int row = qry.addRow(); qry.setAt(KeyConstants._id, row, getId()); qry.setAt(KeyConstants._name, row, getName()); @@ -1259,22 +1275,6 @@ private static String toBase64(InputStream is, String defaultValue) { } } - public static ClassDefinition toClassDefinition(Config config, Map map, ClassDefinition defaultValue) { - String _class = Caster.toString(map.get("class"), null); - - String _name = Caster.toString(map.get("bundle-name"), null); - if (StringUtil.isEmpty(_name)) _name = Caster.toString(map.get("bundleName"), null); - if (StringUtil.isEmpty(_name)) _name = Caster.toString(map.get("bundlename"), null); - if (StringUtil.isEmpty(_name)) _name = Caster.toString(map.get("name"), null); - - String _version = Caster.toString(map.get("bundle-version"), null); - if (StringUtil.isEmpty(_version)) _version = Caster.toString(map.get("bundleVersion"), null); - if (StringUtil.isEmpty(_version)) _version = Caster.toString(map.get("bundleversion"), null); - if (StringUtil.isEmpty(_version)) _version = Caster.toString(map.get("version"), null); - if (StringUtil.isEmpty(_class)) return defaultValue; - return new lucee.transformer.library.ClassDefinitionImpl(_class, _name, _version, config.getIdentification()); - } - private static List> toSettings(Log log, String str) { List> list = new ArrayList<>(); _toSettings(list, log, str, true); @@ -1363,85 +1363,63 @@ public int getReleaseType() { return releaseType; } - public BundleInfo[] getBundles() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public BundleInfo[] getBundles() { return bundles; } public BundleInfo[] getBundles(BundleInfo[] defaultValue) { - if (!loaded) { - try { - load(extensionFile); - } - catch (Exception e) { - return defaultValue; - } - } return bundles; } - public String[] getFlds() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getFlds() { return flds == null ? EMPTY : flds; } - public String[] getJars() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getJars() { return jars == null ? EMPTY : jars; } - public String[] getTlds() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getTlds() { return tlds == null ? EMPTY : tlds; } - public String[] getFunctions() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getFunctions() { return functions == null ? EMPTY : functions; } - public String[] getArchives() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getArchives() { return archives == null ? EMPTY : archives; } - public String[] getTags() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getTags() { return tags == null ? EMPTY : tags; } - public String[] getEventGateways() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getEventGateways() { return gateways == null ? EMPTY : gateways; } - public String[] getApplications() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getApplications() { return applications == null ? EMPTY : applications; } - public String[] getComponents() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getComponents() { return components == null ? EMPTY : components; } - public String[] getPlugins() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getPlugins() { return plugins == null ? EMPTY : plugins; } - public String[] getContexts() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getContexts() { return contexts == null ? EMPTY : contexts; } - public String[] getConfigs() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getConfigs() { return configs == null ? EMPTY : configs; } - public String[] getWebContexts() throws ApplicationException, IOException, BundleException { - if (!loaded) load(extensionFile); + public String[] getWebContexts() { return webContexts == null ? EMPTY : webContexts; } @@ -1567,6 +1545,8 @@ public static List toExtensionDefinitions(String str) { return rtn; } + // TODO call public static ExtensionDefintion toExtensionDefinition(String id, Map + // data) public static ExtensionDefintion toExtensionDefinition(String s) { if (StringUtil.isEmpty(s, true)) return null; s = s.trim(); @@ -1618,6 +1598,54 @@ else if (ed.getId() == null || Decision.isUUId(ed.getId())) { return ed; } + public static ExtensionDefintion toExtensionDefinition(Config config, String id, Map data) { + if (data == null || data.size() == 0) return null; + + ExtensionDefintion ed = new ExtensionDefintion(); + + // validate id + if (Decision.isUUId(id)) { + ed.setId(id); + } + + String name; + Resource res; + config = ThreadLocalPageContext.getConfig(config); + for (Entry entry: data.entrySet()) { + name = entry.getKey().trim(); + if (!"id".equalsIgnoreCase(name)) ed.setParam(name, entry.getValue().trim()); + if ("path".equalsIgnoreCase(name) || "url".equalsIgnoreCase(name) || "resource".equalsIgnoreCase(name)) { + res = ResourceUtil.toResourceExisting(config, entry.getValue().trim(), null); + + if (ed.getId() == null && res != null && res.isFile()) { + + Resource trgDir = config.getLocalExtensionProviderDirectory(); + Resource trg = trgDir.getRealResource(res.getName()); + if (!res.equals(trg) && !trg.isFile()) { + try { + IOUtil.copy(res, trg); + } + catch (IOException e) { + e.printStackTrace(); + } + } + if (!trg.isFile()) continue; + + try { + return new RHExtension(config, trg).toExtensionDefinition(); + } + catch (Exception e) { + e.printStackTrace(); + } + } + } + + } + if (ed.getId() == null) return null; + return ed; + + } + public static List toRHExtensions(List eds) throws PageException { try { final List rtn = new ArrayList(); diff --git a/core/src/main/java/lucee/runtime/functions/FunctionHandlerPool.java b/core/src/main/java/lucee/runtime/functions/FunctionHandlerPool.java index 22a096e7f5..730d5e7a4a 100644 --- a/core/src/main/java/lucee/runtime/functions/FunctionHandlerPool.java +++ b/core/src/main/java/lucee/runtime/functions/FunctionHandlerPool.java @@ -62,7 +62,7 @@ public static BIF use(PageContext pc, String className, String bundleName, Strin if (!StringUtil.isEmpty(bundleName)) clazz = ClassUtil.loadClassByBundle(className, bundleName, bundleVersion, pc.getConfig().getIdentification(), JavaSettingsImpl.getBundleDirectories(pc), true); // JAR - else clazz = ClassUtil.loadClass(className); + else clazz = ClassUtil.loadClass(pc, className); if (Reflector.isInstaneOf(clazz, BIF.class, false)) bif = (BIF) ClassUtil.newInstance(clazz); else bif = new BIFProxy(clazz); @@ -79,7 +79,7 @@ public static BIF use(PageContext pc, String className) throws PageException { if (bif != null) return bif; try { - Class clazz = ClassUtil.loadClass(className); + Class clazz = ClassUtil.loadClass(pc, className); if (Reflector.isInstaneOf(clazz, BIF.class, false)) bif = (BIF) ClassUtil.newInstance(clazz); else bif = new BIFProxy(clazz); diff --git a/core/src/main/java/lucee/runtime/functions/ai/AIGetMetaData.java b/core/src/main/java/lucee/runtime/functions/ai/AIGetMetaData.java new file mode 100644 index 0000000000..08c7d62cb5 --- /dev/null +++ b/core/src/main/java/lucee/runtime/functions/ai/AIGetMetaData.java @@ -0,0 +1,29 @@ +package lucee.runtime.functions.ai; + +import lucee.runtime.PageContext; +import lucee.runtime.PageContextImpl; +import lucee.runtime.ai.AIEngine; +import lucee.runtime.ai.AIUtil; +import lucee.runtime.exp.FunctionException; +import lucee.runtime.exp.PageException; +import lucee.runtime.ext.function.BIF; +import lucee.runtime.op.Caster; +import lucee.runtime.type.Struct; + +public final class AIGetMetaData extends BIF { + + private static final long serialVersionUID = 6532201888958323478L; + + public static Struct call(PageContext pc, String nameAI) throws PageException { + if (nameAI.startsWith("default:")) nameAI = ((PageContextImpl) pc).getNameFromDefault(nameAI.substring(8)); + AIEngine aie = ((PageContextImpl) pc).getAIEngine(nameAI); + return AIUtil.getMetaData(aie); + } + + @Override + public Object invoke(PageContext pc, Object[] args) throws PageException { + if (args.length == 1) return call(pc, Caster.toString(args[0])); + throw new FunctionException(pc, "AIListModels", 1, 1, args.length); + } + +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/ai/AIHas.java b/core/src/main/java/lucee/runtime/functions/ai/AIHas.java new file mode 100644 index 0000000000..6023bcc9aa --- /dev/null +++ b/core/src/main/java/lucee/runtime/functions/ai/AIHas.java @@ -0,0 +1,30 @@ +package lucee.runtime.functions.ai; + +import lucee.runtime.PageContext; +import lucee.runtime.PageContextImpl; +import lucee.runtime.exp.FunctionException; +import lucee.runtime.exp.PageException; +import lucee.runtime.ext.function.BIF; +import lucee.runtime.op.Caster; + +public final class AIHas extends BIF { + + private static final long serialVersionUID = 6532201888958323478L; + + public static boolean call(PageContext pc, String nameAI) { + PageContextImpl pci = (PageContextImpl) pc; + + if (nameAI.startsWith("default:")) { + nameAI = pci.getNameFromDefault(nameAI.substring(8), null); + if (nameAI == null) return false; + } + return pci.getAIEngine(nameAI, null) != null; + } + + @Override + public Object invoke(PageContext pc, Object[] args) throws PageException { + if (args.length == 1) return call(pc, Caster.toString(args[0])); + throw new FunctionException(pc, "AIHas", 1, 1, args.length); + } + +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/ai/LuceeGetAIEngine.java b/core/src/main/java/lucee/runtime/functions/ai/LuceeGetAIEngine.java new file mode 100644 index 0000000000..6bb6ae2d57 --- /dev/null +++ b/core/src/main/java/lucee/runtime/functions/ai/LuceeGetAIEngine.java @@ -0,0 +1,27 @@ +package lucee.runtime.functions.ai; + +import lucee.runtime.PageContext; +import lucee.runtime.PageContextImpl; +import lucee.runtime.exp.FunctionException; +import lucee.runtime.exp.PageException; +import lucee.runtime.ext.function.BIF; +import lucee.runtime.op.Caster; + +/** + * implementation of the Function arrayAppend + */ +public final class LuceeGetAIEngine extends BIF { + + private static final long serialVersionUID = 5632258425708603692L; + + public static Object call(PageContext pc, String nameAI) throws PageException { + if (nameAI.startsWith("default:")) nameAI = ((PageContextImpl) pc).getNameFromDefault(nameAI.substring(8)); + return ((PageContextImpl) pc).createAISession(nameAI, null); + } + + @Override + public Object invoke(PageContext pc, Object[] args) throws PageException { + if (args.length == 1) return call(pc, Caster.toString(args[0])); + throw new FunctionException(pc, "GetAIEngine", 1, 1, args.length); + } +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/closure/Each.java b/core/src/main/java/lucee/runtime/functions/closure/Each.java index 5daf8ccefa..ccc26687b5 100644 --- a/core/src/main/java/lucee/runtime/functions/closure/Each.java +++ b/core/src/main/java/lucee/runtime/functions/closure/Each.java @@ -29,7 +29,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import lucee.runtime.PageContext; @@ -40,6 +39,7 @@ import lucee.runtime.exp.ParentException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; +import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayPro; import lucee.runtime.type.Collection.Key; @@ -77,7 +77,7 @@ private static String _call(PageContext pc, Object obj, UDF udf, boolean paralle else if (maxThreads == 1) parallel = false; if (parallel) { - execute = Executors.newFixedThreadPool(maxThreads); + execute = ThreadUtil.createExecutorService(maxThreads); futures = new ArrayList>>(); } diff --git a/core/src/main/java/lucee/runtime/functions/closure/Every.java b/core/src/main/java/lucee/runtime/functions/closure/Every.java index 635b4cf9db..e71c00b000 100644 --- a/core/src/main/java/lucee/runtime/functions/closure/Every.java +++ b/core/src/main/java/lucee/runtime/functions/closure/Every.java @@ -25,7 +25,6 @@ import java.util.ListIterator; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import lucee.runtime.PageContext; @@ -37,6 +36,7 @@ import lucee.runtime.exp.ParentException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; +import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayPro; import lucee.runtime.type.Collection.Key; @@ -80,7 +80,7 @@ private static boolean _call(PageContext pc, Object obj, UDF udf, boolean parall else if (maxThreads == 1) parallel = false; if (parallel) { - execute = Executors.newFixedThreadPool(maxThreads); + execute = ThreadUtil.createExecutorService(maxThreads); futures = new ArrayList>>(); } diff --git a/core/src/main/java/lucee/runtime/functions/closure/Filter.java b/core/src/main/java/lucee/runtime/functions/closure/Filter.java index 4f29311408..653a23721a 100644 --- a/core/src/main/java/lucee/runtime/functions/closure/Filter.java +++ b/core/src/main/java/lucee/runtime/functions/closure/Filter.java @@ -25,7 +25,6 @@ import java.util.ListIterator; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import lucee.commons.lang.Pair; @@ -38,6 +37,7 @@ import lucee.runtime.exp.ParentException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; +import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; import lucee.runtime.type.ArrayPro; @@ -85,7 +85,7 @@ private static Collection _call(PageContext pc, Object obj, UDF udf, boolean par // 1 == not parallel else if (maxThreads == 1) parallel = false; if (parallel) { - execute = Executors.newFixedThreadPool(maxThreads); + execute = ThreadUtil.createExecutorService(maxThreads); futures = new ArrayList>>>(); } diff --git a/core/src/main/java/lucee/runtime/functions/closure/Map.java b/core/src/main/java/lucee/runtime/functions/closure/Map.java index b3694c3e3d..d8970cb573 100644 --- a/core/src/main/java/lucee/runtime/functions/closure/Map.java +++ b/core/src/main/java/lucee/runtime/functions/closure/Map.java @@ -26,7 +26,6 @@ import java.util.ListIterator; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import lucee.runtime.PageContext; @@ -38,6 +37,7 @@ import lucee.runtime.exp.ParentException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; +import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; import lucee.runtime.type.ArrayPro; @@ -85,7 +85,7 @@ private static Collection _call(PageContext pc, Object obj, UDF udf, boolean par // 1 == not parallel else if (maxThreads == 1) parallel = false; if (parallel) { - execute = Executors.newFixedThreadPool(maxThreads); + execute = ThreadUtil.createExecutorService(maxThreads); futures = new ArrayList>>(); } diff --git a/core/src/main/java/lucee/runtime/functions/closure/Some.java b/core/src/main/java/lucee/runtime/functions/closure/Some.java index 592fa9d118..1d64c63119 100644 --- a/core/src/main/java/lucee/runtime/functions/closure/Some.java +++ b/core/src/main/java/lucee/runtime/functions/closure/Some.java @@ -25,7 +25,6 @@ import java.util.ListIterator; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import lucee.runtime.PageContext; @@ -37,6 +36,7 @@ import lucee.runtime.exp.ParentException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; +import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayPro; import lucee.runtime.type.Collection.Key; @@ -79,7 +79,7 @@ private static boolean _call(PageContext pc, Object obj, UDF udf, boolean parall // 1 == not parallel else if (maxThreads == 1) parallel = false; if (parallel) { - execute = Executors.newFixedThreadPool(maxThreads); + execute = ThreadUtil.createExecutorService(maxThreads); futures = new ArrayList>>(); } diff --git a/core/src/main/java/lucee/runtime/functions/conversion/DeserializeJSON.java b/core/src/main/java/lucee/runtime/functions/conversion/DeserializeJSON.java index ca385aa840..29e01ed62d 100644 --- a/core/src/main/java/lucee/runtime/functions/conversion/DeserializeJSON.java +++ b/core/src/main/java/lucee/runtime/functions/conversion/DeserializeJSON.java @@ -20,6 +20,9 @@ import java.util.Iterator; +import lucee.commons.io.SystemUtil; +import lucee.commons.io.log.Log; +import lucee.commons.io.log.LogUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; import lucee.runtime.exp.FunctionException; @@ -45,24 +48,44 @@ public final class DeserializeJSON extends BIF implements Function { private static final long serialVersionUID = -4847186239512149277L; + private static final boolean allowEmpty; + static { + allowEmpty = Caster.toBooleanValue(SystemUtil.getSystemPropOrEnvVar("lucee.deserializejson.allowempty", null), false); + } private static final Key ROWCOUNT = KeyConstants._ROWCOUNT; public static Object call(PageContext pc, String JSONVar) throws PageException { - return call(pc, JSONVar, true); + return _call(pc, JSONVar, true, JSONExpressionInterpreter.FORMAT_JSON5); // for backward compatibility we need to allow json5 (most comments are allowed in Lucee 5) } public static Object call(PageContext pc, String JSONVar, boolean strictMapping) throws PageException { - if (StringUtil.isEmpty(JSONVar, true)) + return _call(pc, JSONVar, strictMapping, JSONExpressionInterpreter.FORMAT_JSON5);// for backward compatibility we need to allow json5 (most comments are allowed in Lucee 5) + } + + public static Object call(PageContext pc, String JSONVar, boolean strictMapping, String strFormat) throws PageException { + int format = StringUtil.isEmpty(strFormat, true) ? JSONExpressionInterpreter.FORMAT_JSON : JSONExpressionInterpreter.toFormat(strFormat); + return _call(pc, JSONVar, strictMapping, format); + } + + private static Object _call(PageContext pc, String JSONVar, boolean strictMapping, int format) throws PageException { + if (StringUtil.isEmpty(JSONVar, true)) { + if (allowEmpty) { + LogUtil.log(Log.LEVEL_WARN, "datasource", "conversion", + "Deprecated functionality used at [" + LogUtil.caller(pc, "") + "]. An empty string was passed as a value to the function DeserializeJSON."); + return ""; + } throw new FunctionException(pc, "DeserializeJSON", 1, "JSONVar", "input value cannot be empty string.", "Must be the valid JSON string"); - Object result = new JSONExpressionInterpreter().interpret(pc, JSONVar); + } + Object result = new JSONExpressionInterpreter(false, format).interpret(pc, JSONVar); if (!strictMapping) return toQuery(result); return result; } @Override public Object invoke(PageContext pc, Object[] args) throws PageException { - if (args.length == 2) return call(pc, Caster.toString(args[0]), Caster.toBooleanValue(args[1])); - if (args.length == 1) return call(pc, Caster.toString(args[0])); + if (args.length == 3) return call(pc, Caster.toString(args[0]), Caster.toBooleanValue(args[1]), Caster.toString(args[2])); + if (args.length == 2) return _call(pc, Caster.toString(args[0]), Caster.toBooleanValue(args[1]), JSONExpressionInterpreter.FORMAT_JSON5); + if (args.length == 1) return _call(pc, Caster.toString(args[0]), true, JSONExpressionInterpreter.FORMAT_JSON5); throw new FunctionException(pc, "DeserializeJSON", 1, 2, args.length); } diff --git a/core/src/main/java/lucee/runtime/functions/conversion/IsJSON.java b/core/src/main/java/lucee/runtime/functions/conversion/IsJSON.java index a70a446662..efcd9c7c05 100644 --- a/core/src/main/java/lucee/runtime/functions/conversion/IsJSON.java +++ b/core/src/main/java/lucee/runtime/functions/conversion/IsJSON.java @@ -20,17 +20,35 @@ import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; +import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; import lucee.runtime.interpreter.JSONExpressionInterpreter; import lucee.runtime.op.Caster; public class IsJSON { + public static boolean call(PageContext pc, Object obj) { String str = Caster.toString(obj, null); if (StringUtil.isEmpty(str, true)) return false; try { - new JSONExpressionInterpreter().interpret(pc, str); + new JSONExpressionInterpreter(false, JSONExpressionInterpreter.FORMAT_JSON5).interpret(pc, str); + return true; + } + catch (PageException e) { + return false; + } + } + + public static boolean call(PageContext pc, Object obj, String strFormat) throws ApplicationException { + + int format = StringUtil.isEmpty(strFormat, true) ? JSONExpressionInterpreter.FORMAT_JSON5 : JSONExpressionInterpreter.toFormat(strFormat); + + String str = Caster.toString(obj, null); + if (StringUtil.isEmpty(str, true)) return false; + + try { + new JSONExpressionInterpreter(false, format).interpret(pc, str); return true; } catch (PageException e) { diff --git a/core/src/main/java/lucee/runtime/functions/decision/IsDebugMode.java b/core/src/main/java/lucee/runtime/functions/decision/IsDebugMode.java index 1b90df40cb..43014f0867 100644 --- a/core/src/main/java/lucee/runtime/functions/decision/IsDebugMode.java +++ b/core/src/main/java/lucee/runtime/functions/decision/IsDebugMode.java @@ -24,10 +24,11 @@ import lucee.runtime.PageContext; import lucee.runtime.debug.DebuggerImpl; import lucee.runtime.ext.function.Function; +import lucee.runtime.util.PageContextUtil; public final class IsDebugMode implements Function { public static boolean call(PageContext pc) { - return pc.getConfig().debug() && (DebuggerImpl.getDebugEntry(pc) != null); + return PageContextUtil.debug(pc) && (DebuggerImpl.getDebugEntry(pc) != null); } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/decision/IsInstanceOf.java b/core/src/main/java/lucee/runtime/functions/decision/IsInstanceOf.java index 042c36af0c..2a848a9d59 100644 --- a/core/src/main/java/lucee/runtime/functions/decision/IsInstanceOf.java +++ b/core/src/main/java/lucee/runtime/functions/decision/IsInstanceOf.java @@ -38,7 +38,7 @@ public static boolean call(PageContext pc, Object obj, String typeName) throws P if (obj instanceof Component) return ((Component) obj).instanceOf(typeName); if (obj instanceof JavaObject) { try { - return Reflector.isInstaneOf(((PageContextImpl) pc).getClassLoader(), ((JavaObject) obj).getClazz(), typeName); + return Reflector.isInstaneOf(((PageContextImpl) pc).getClassLoader(null), ((JavaObject) obj).getClazz(), typeName); } catch (IOException ioe) { throw Caster.toPageException(ioe); @@ -47,7 +47,7 @@ public static boolean call(PageContext pc, Object obj, String typeName) throws P if (obj instanceof ObjectWrap) return call(pc, ((ObjectWrap) obj).getEmbededObject(), typeName); try { - return Reflector.isInstaneOf(((PageContextImpl) pc).getClassLoader(), obj.getClass(), typeName); + return Reflector.isInstaneOf(((PageContextImpl) pc).getClassLoader(null), obj.getClass(), typeName); } catch (IOException ioe) { throw Caster.toPageException(ioe); diff --git a/core/src/main/java/lucee/runtime/functions/other/CreateDynamicProxy.java b/core/src/main/java/lucee/runtime/functions/other/CreateDynamicProxy.java index 14e621a213..08f6f33092 100644 --- a/core/src/main/java/lucee/runtime/functions/other/CreateDynamicProxy.java +++ b/core/src/main/java/lucee/runtime/functions/other/CreateDynamicProxy.java @@ -75,7 +75,7 @@ public static Object _call(PageContext pc, Object oCFC, Object oInterfaces) thro if (Decision.isArray(oInterfaces)) { Object[] arr = Caster.toNativeArray(oInterfaces); - ClassLoader cl = ((PageContextImpl) pc).getClassLoader(); + ClassLoader cl = ((PageContextImpl) pc).getClassLoader(null); interfaces = new Class[arr.length]; for (int i = 0; i < arr.length; i++) { if (arr[i] instanceof JavaObject) interfaces[i] = ((JavaObject) arr[i]).getClazz(); @@ -88,7 +88,7 @@ else if (oInterfaces instanceof JavaObject) { interfaces = new Class[] { ((JavaObject) oInterfaces).getClazz() }; } else if (oInterfaces instanceof Struct) { - ClassLoader cl = ((PageContextImpl) pc).getClassLoader(); + ClassLoader cl = ((PageContextImpl) pc).getClassLoader(null); interfaces = new Class[] { toClass(pc, cl, (Struct) oInterfaces) }; } else throw new FunctionException(pc, "CreateDynamicProxy", 2, "interfaces", "invalid type [" + Caster.toClassName(oInterfaces) + "] for class definition"); @@ -119,7 +119,7 @@ private static Class toClass(PageContext pc, ClassLoader cl, Struct sct) throws if (StringUtil.isEmpty(bundleName)) { return ClassUtil.loadClass(cl, className); } - return ClassUtil.loadClass(className, bundleName, bundleVersion, pc.getConfig().getIdentification(), JavaSettingsImpl.getBundleDirectories(pc)); + return ClassUtil.loadClass(pc, className, bundleName, bundleVersion, pc.getConfig().getIdentification(), JavaSettingsImpl.getBundleDirectories(pc)); } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/other/JavaProxy.java b/core/src/main/java/lucee/runtime/functions/other/JavaProxy.java index 73148896b0..a2b825fdbb 100644 --- a/core/src/main/java/lucee/runtime/functions/other/JavaProxy.java +++ b/core/src/main/java/lucee/runtime/functions/other/JavaProxy.java @@ -21,6 +21,7 @@ */ package lucee.runtime.functions.other; +import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -76,7 +77,6 @@ public static Object call(PageContext pc, String className, Object pathOrName, S public static Class loadClass(PageContext pc, String className, Object pathOrName, String delimiterOrVersion, Array aRelatedBundles) throws PageException { if (StringUtil.isEmpty(pathOrName)) return loadClassByPath(pc, className, null); - String str = Caster.toString(pathOrName, null); BundleDefinition[] relatedBundles = null; if (aRelatedBundles != null) { @@ -126,6 +126,15 @@ public static Class loadClass(PageContext pc, String className, Object pathOr return loadClassByPath(pc, className, arrPaths); } + else if (Decision.isStruct(pathOrName)) { + JavaSettingsImpl js = (JavaSettingsImpl) JavaSettingsImpl.getInstance(pc.getConfig(), Caster.toStruct(pathOrName), null); + try { + return ClassUtil.loadClass(((PageContextImpl) pc).getClassLoader(js), className); + } + catch (IOException e) { + throw Caster.toPageException(e); + } + } return loadClassByPath(pc, className, ListUtil.toStringArray(Caster.toArray(pathOrName))); } @@ -162,8 +171,11 @@ private static Class loadClassByPath(PageContext pc, String className, String // load class try { - - ClassLoader cl = resources.isEmpty() ? pci.getClassLoader() : pci.getClassLoader(resources.toArray(new Resource[resources.size()])); + JavaSettingsImpl js = null; + if (resources != null && !resources.isEmpty()) { + js = (JavaSettingsImpl) JavaSettingsImpl.getInstance(pc.getConfig(), null, resources); + } + ClassLoader cl = pci.getClassLoader(js); Class clazz = null; try { diff --git a/core/src/main/java/lucee/runtime/functions/string/JavaCast.java b/core/src/main/java/lucee/runtime/functions/string/JavaCast.java index 841fc07daf..016bab3ec6 100644 --- a/core/src/main/java/lucee/runtime/functions/string/JavaCast.java +++ b/core/src/main/java/lucee/runtime/functions/string/JavaCast.java @@ -21,18 +21,21 @@ */ package lucee.runtime.functions.string; +import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; -import lucee.commons.lang.ClassException; import lucee.commons.lang.ClassUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; +import lucee.runtime.PageContextImpl; import lucee.runtime.exp.ExpressionException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.function.Function; +import lucee.runtime.listener.JavaSettingsImpl; import lucee.runtime.op.Caster; import lucee.runtime.op.Decision; +import lucee.runtime.type.Struct; public final class JavaCast implements Function { @@ -43,19 +46,23 @@ public static Object calls(PageContext pc, String string, Object object) throws } public static Object call(PageContext pc, String type, Object obj) throws PageException { + return call(pc, type, obj, null); + } + + public static Object call(PageContext pc, String type, Object obj, Struct javaSettings) throws PageException { type = type.trim(); String lcType = StringUtil.toLowerCase(type); if (type.endsWith("[]")) { - return toArray(pc, type, lcType, obj); + return toArray(pc, type, lcType, obj, javaSettings); } - Class clazz = toClass(pc, lcType, type); + Class clazz = toClass(pc, lcType, type, javaSettings); return to(pc, obj, clazz); } - public static Object toArray(PageContext pc, String type, String lcType, Object obj) throws PageException { + public static Object toArray(PageContext pc, String type, String lcType, Object obj, Struct javaSettings) throws PageException { // byte if ("byte[]".equals(lcType)) { if (obj instanceof byte[]) return obj; @@ -68,20 +75,20 @@ else if ("char[]".equals(lcType)) { if (obj instanceof CharSequence) return obj.toString().toCharArray(); } - return _toArray(pc, type, lcType, obj); + return _toArray(pc, type, lcType, obj, javaSettings); } - public static Object _toArray(PageContext pc, String type, String lcType, Object obj) throws PageException { + public static Object _toArray(PageContext pc, String type, String lcType, Object obj, Struct javaSettings) throws PageException { lcType = lcType.substring(0, lcType.length() - 2); type = type.substring(0, type.length() - 2); // other Object[] arr = Caster.toList(obj).toArray(); - Class clazz = toClass(pc, lcType, type); + Class clazz = toClass(pc, lcType, type, javaSettings); Object trg = java.lang.reflect.Array.newInstance(clazz, arr.length); for (int i = arr.length - 1; i >= 0; i--) { - java.lang.reflect.Array.set(trg, i, type.endsWith("[]") ? _toArray(pc, type, lcType, arr[i]) : to(pc, arr[i], clazz)); + java.lang.reflect.Array.set(trg, i, type.endsWith("[]") ? _toArray(pc, type, lcType, arr[i], javaSettings) : to(pc, arr[i], clazz)); } return trg; } @@ -95,7 +102,7 @@ private static Object to(PageContext pc, Object obj, Class trgClass) throws P // float ,double ,boolean ,string,null ), "+lcType+" is invalid"); } - private static Class toClass(PageContext pc, String lcType, String type) throws PageException { + private static Class toClass(PageContext pc, String lcType, String type, Struct javaSettings) throws PageException { if (lcType.equals("null")) { return null; @@ -106,10 +113,15 @@ private static Class toClass(PageContext pc, String lcType, String type) thro if (lcType.equals("bigdecimal")) { return BigDecimal.class; } + try { - return ClassUtil.toClass(type); + if (javaSettings != null) { + JavaSettingsImpl js = (JavaSettingsImpl) JavaSettingsImpl.getInstance(pc.getConfig(), Caster.toStruct(javaSettings), null); + return ClassUtil.loadClass(((PageContextImpl) pc).getClassLoader(js), type); + } + return ClassUtil.loadClass(pc, type); } - catch (ClassException e) { + catch (IOException e) { throw Caster.toPageException(e); } } diff --git a/core/src/main/java/lucee/runtime/functions/system/GetApplicationSettings.java b/core/src/main/java/lucee/runtime/functions/system/GetApplicationSettings.java index 1342e509a5..e70f561d62 100644 --- a/core/src/main/java/lucee/runtime/functions/system/GetApplicationSettings.java +++ b/core/src/main/java/lucee/runtime/functions/system/GetApplicationSettings.java @@ -50,9 +50,11 @@ import lucee.runtime.listener.ApplicationContextSupport; import lucee.runtime.listener.ClassicApplicationContext; import lucee.runtime.listener.JavaSettings; +import lucee.runtime.listener.JavaSettingsImpl; import lucee.runtime.listener.ModernApplicationContext; import lucee.runtime.listener.SessionCookieData; import lucee.runtime.listener.SessionCookieDataImpl; +import lucee.runtime.mvn.POM; import lucee.runtime.net.mail.Server; import lucee.runtime.net.mail.ServerImpl; import lucee.runtime.net.proxy.ProxyData; @@ -412,6 +414,26 @@ public static Struct call(PageContext pc, boolean suppressFunctions, boolean onl sb.append(reses[i].getAbsolutePath()); } jsSct.put("loadCFMLClassPath", sb.toString()); + + // maven + if (js instanceof JavaSettingsImpl) { + JavaSettingsImpl jsi = (JavaSettingsImpl) js; + java.util.Collection poms = jsi.getPoms(); + if (poms != null) { + Array arr = new ArrayImpl(); + Struct s; + for (POM pom: poms) { + s = new StructImpl(); + // TODO add more columns + s.set(KeyConstants._groupId, pom.getGroupId()); + s.set(KeyConstants._artifactId, pom.getArtifactId()); + s.set(KeyConstants._version, pom.getVersion()); + arr.append(s); + } + jsSct.put("maven", arr); + } + } + sct.put("javaSettings", jsSct); // REST Settings // MUST diff --git a/core/src/main/java/lucee/runtime/functions/system/InternalRequest.java b/core/src/main/java/lucee/runtime/functions/system/InternalRequest.java index 86417bbe3f..859101a4b6 100644 --- a/core/src/main/java/lucee/runtime/functions/system/InternalRequest.java +++ b/core/src/main/java/lucee/runtime/functions/system/InternalRequest.java @@ -162,7 +162,7 @@ else if (body != null) { name = KeyImpl.init(it.next()); values = rsp.getHeaders(name.getString()); if (values == null || values.size() == 0) continue; - if (name.equals("Set-Cookie")) { + if (name.equals(KeyImpl.getInstance("Set-Cookie"))) { String cs = _pc.getWebCharset().name(); for (String v: values) { diff --git a/core/src/main/java/lucee/runtime/interpreter/CFMLExpressionInterpreter.java b/core/src/main/java/lucee/runtime/interpreter/CFMLExpressionInterpreter.java index fc7924fe4a..e4940c5c59 100755 --- a/core/src/main/java/lucee/runtime/interpreter/CFMLExpressionInterpreter.java +++ b/core/src/main/java/lucee/runtime/interpreter/CFMLExpressionInterpreter.java @@ -172,6 +172,9 @@ public class CFMLExpressionInterpreter { protected PageContext pc; private FunctionLib fld; protected boolean allowNullConstant = false; + + protected boolean allowComments = true; + protected boolean allowAllowUnquotedNames = true; private boolean preciseMath; private final boolean isJson; private final boolean limited; @@ -317,7 +320,11 @@ private Ref functionArgDeclarationVarString() throws PageException { */ private Ref functionArgDeclaration() throws PageException { Ref ref = impOp(); + if (cfml.forwardIfCurrent(':') || cfml.forwardIfCurrent('=')) { + if (!allowAllowUnquotedNames && !(ref instanceof Literal)) throw new InterpreterException( + "Unquoted name found. Keys in structures must be enclosed in quotes. Example of incorrect format: {susi:1}. Correct format: {\"susi\":1}"); + comments(); ref = new LFunctionValue(ref, assignOp()); } @@ -1026,7 +1033,7 @@ else if ((str.length() - pos) <= 10) { throw new InterpreterException("Syntax Error, Invalid Construct", " at position " + (pos + 1) + " in [" + str + "]"); } - protected Ref json(FunctionLibFunction flf, char start, char end) throws PageException { +protected Ref json(FunctionLibFunction flf, char start, char end) throws PageException { if (!cfml.isCurrent(start)) return null; /* * String[] str = cfml.toString().split(","); if(cfml.getCurrent() == '{' && cfml.getNext() != '}' @@ -1039,27 +1046,30 @@ protected Ref json(FunctionLibFunction flf, char start, char end) throws PageExc * TemplateException("Invalid json value" +cfml); } } } */ - if (cfml.forwardIfCurrent('[', ':', ']') || cfml.forwardIfCurrent('[', '=', ']')) { + if (!isJson && cfml.forwardIfCurrent('[', ':', ']') || cfml.forwardIfCurrent('[', '=', ']')) { return new BIFCall(LITERAL_ORDERED_STRUCT, new Ref[0]); } - Ref[] args = flf == null ? null : functionArg(flf.getName(), false, flf, end); - if (args != null && args.length > 0 && flf == LITERAL_ARRAY) { - if (args[0] instanceof LFunctionValue) { - for (int i = 1; i < args.length; i++) { - if (!(args[i] instanceof LFunctionValue)) - throw new TemplateException("Invalid argument for literal ordered struct, only named arguments are allowed like {name:\"value\",name2:\"value2\"} " + getExceptionPosition(), getExceptionSnippet()); - } - flf = LITERAL_ORDERED_STRUCT; + if (args != null && args.length > 0) { + if (isJson && start == '[' && args[0] instanceof LFunctionValue) { + throw new TemplateException("invalid syntax, json does not allow ordered structs"); } - else { - for (int i = 1; i < args.length; i++) { - if (args[i] instanceof LFunctionValue) throw new TemplateException("Invalid argument for literal array, no named arguments are allowed " + getExceptionPosition(), getExceptionSnippet()); + if (flf == LITERAL_ARRAY) { + if (args[0] instanceof LFunctionValue) { + for (int i = 1; i < args.length; i++) { + if (!(args[i] instanceof LFunctionValue)) + throw new TemplateException("invalid argument for literal ordered struct, only named arguments are allowed like {name:\"value\",name2:\"value2\"}"); + } + flf = LITERAL_ORDERED_STRUCT; } + else { + for (int i = 1; i < args.length; i++) { + if (args[i] instanceof LFunctionValue) throw new TemplateException("invalid argument for literal array, no named arguments are allowed"); + } + } } } - return new BIFCall(flf, args); } @@ -1553,8 +1563,10 @@ private Ref sharp() throws PageException { protected void comments() throws InterpreterException { cfml.removeSpace(); - while (comment()) { - cfml.removeSpace(); + if (allowComments) { + while (comment()) { + cfml.removeSpace(); + } } } diff --git a/core/src/main/java/lucee/runtime/interpreter/JSONExpressionInterpreter.java b/core/src/main/java/lucee/runtime/interpreter/JSONExpressionInterpreter.java index 074011f715..0f09f2256d 100644 --- a/core/src/main/java/lucee/runtime/interpreter/JSONExpressionInterpreter.java +++ b/core/src/main/java/lucee/runtime/interpreter/JSONExpressionInterpreter.java @@ -19,17 +19,34 @@ package lucee.runtime.interpreter; import lucee.commons.lang.NumberUtil; +import lucee.commons.lang.StringUtil; +import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; import lucee.runtime.interpreter.ref.Ref; import lucee.runtime.interpreter.ref.literal.LStringBuffer; public class JSONExpressionInterpreter extends CFMLExpressionInterpreter { + + public static final int FORMAT_JSON = 1; + public static final int FORMAT_JSON5 = 2; + + public static void main(String[] args) throws PageException { + new JSONExpressionInterpreter(false, FORMAT_JSON).interpret(null, "{\"b\":2}"); + new JSONExpressionInterpreter(false, FORMAT_JSON5).interpret(null, "{a:1,\"b\":2}"); + } + public JSONExpressionInterpreter() { - this(false); + this(false, FORMAT_JSON5); } public JSONExpressionInterpreter(boolean strict) {// strict is set to true, it should not be compatible with CFMLExpressionInterpreter - allowNullConstant = true; + this(strict, FORMAT_JSON5); + } + + public JSONExpressionInterpreter(boolean strict, int format) {// strict is set to true, it should not be compatible with CFMLExpressionInterpreter + this.allowNullConstant = true; + this.allowComments = format == FORMAT_JSON5; + this.allowAllowUnquotedNames = format == FORMAT_JSON5; } @Override @@ -119,8 +136,7 @@ else if (cfml.isCurrent(quoter)) { } } if (!cfml.forwardIfCurrent(quoter)) throw new InterpreterException("Invalid String Literal Syntax Closing [" + quoter + "] not found"); - - cfml.removeSpace(); + comments(); mode = STATIC; /* * Ref value=null; if(value!=null) { if(str.isEmpty()) return value; return new @@ -129,4 +145,21 @@ else if (cfml.isCurrent(quoter)) { return str; } + public static int toFormat(String format) throws ApplicationException { + int f = toFormat(format, 0); + if (f != 0) return f; + + if (!StringUtil.isEmpty(format, true)) throw new ApplicationException("invalid format defintion [" + format + "], valid format names are [json, json5]."); + throw new ApplicationException("invalid format defintion, an empty string or null is not allowed."); + } + + public static int toFormat(String format, int defaultValue) { + if (!StringUtil.isEmpty(format, true)) { + format = format.trim().toLowerCase(); + if ("json".equals(format)) return FORMAT_JSON; + if ("json5".equals(format)) return FORMAT_JSON5; + } + return defaultValue; + } + } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/listener/AppListenerUtil.java b/core/src/main/java/lucee/runtime/listener/AppListenerUtil.java index 9a0b8f369e..a674a8e7fa 100755 --- a/core/src/main/java/lucee/runtime/listener/AppListenerUtil.java +++ b/core/src/main/java/lucee/runtime/listener/AppListenerUtil.java @@ -24,8 +24,6 @@ import java.util.Map.Entry; import java.util.TimeZone; -import org.osgi.framework.Version; - import lucee.commons.io.SystemUtil; import lucee.commons.io.log.Log; import lucee.commons.io.res.Resource; @@ -61,7 +59,6 @@ import lucee.runtime.op.Caster; import lucee.runtime.op.Decision; import lucee.runtime.orm.ORMConfigurationImpl; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.tag.Query; import lucee.runtime.tag.listener.TagListener; import lucee.runtime.type.Array; @@ -221,12 +218,12 @@ public static DataSource toDataSource(Config config, String name, Struct data, L // first check for {class:... , connectionString:...} Object oConnStr = data.get(CONNECTION_STRING, null); if (oConnStr != null) { + // FUTURE remove this String className = Caster.toString(data.get(KeyConstants._class)); if ("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(className)) { - className = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + data.set(KeyConstants._class, "com.microsoft.sqlserver.jdbc.SQLServerDriver"); } - ClassDefinition cd = new ClassDefinitionImpl(className, Caster.toString(data.get(KeyConstants._bundleName, null), null), - Caster.toString(data.get(KeyConstants._bundleVersion, null), null), ThreadLocalPageContext.getConfig().getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(data, null, true, ThreadLocalPageContext.getConfig().getIdentification()); try { int idle = Caster.toIntValue(data.get(IDLE_TIMEOUT, null), -1); @@ -957,29 +954,4 @@ public static String toVariableUsage(int i, String defaultValue) { return defaultValue; } - public static String toClassName(Struct sct) { - if (sct == null) return null; - String className = Caster.toString(sct.get("class", null), null); - if (StringUtil.isEmpty(className)) className = Caster.toString(sct.get("classname", null), null); - if (StringUtil.isEmpty(className)) className = Caster.toString(sct.get("class-name", null), null); - if (StringUtil.isEmpty(className)) return null; - return className; - } - - public static String toBundleName(Struct sct) { - if (sct == null) return null; - String name = Caster.toString(sct.get("bundlename", null), null); - if (StringUtil.isEmpty(name)) name = Caster.toString(sct.get("bundle-name", null), null); - if (StringUtil.isEmpty(name)) name = Caster.toString(sct.get("name", null), null); - if (StringUtil.isEmpty(name)) return null; - return name; - } - - public static Version toBundleVersion(Struct sct) { - if (sct == null) return null; - Version version = OSGiUtil.toVersion(Caster.toString(sct.get("bundleversion", null), null), null); - if (version == null) version = OSGiUtil.toVersion(Caster.toString(sct.get("bundle-version", null), null), null); - if (version == null) version = OSGiUtil.toVersion(Caster.toString(sct.get("version", null), null), null); - return version; - } } diff --git a/core/src/main/java/lucee/runtime/listener/ApplicationContextSupport.java b/core/src/main/java/lucee/runtime/listener/ApplicationContextSupport.java index b065bd39a5..0e566fa04a 100644 --- a/core/src/main/java/lucee/runtime/listener/ApplicationContextSupport.java +++ b/core/src/main/java/lucee/runtime/listener/ApplicationContextSupport.java @@ -25,8 +25,6 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; -import org.osgi.framework.Version; - import lucee.commons.io.log.Log; import lucee.commons.io.log.LogUtil; import lucee.commons.io.log.LoggerAndSourceData; @@ -318,22 +316,14 @@ public static Map> initLog(Config config, Stru if (v == null) continue; // appender - ClassDefinition cdApp; Struct sctApp = Caster.toStruct(v.get("appender", null), null); - String ac = AppListenerUtil.toClassName(sctApp); - String abn = AppListenerUtil.toBundleName(sctApp); - Version abv = AppListenerUtil.toBundleVersion(sctApp); - if (StringUtil.isEmpty(abn)) cdApp = ((ConfigPro) config).getLogEngine().appenderClassDefintion(ac); - else cdApp = new ClassDefinitionImpl<>(config.getIdentification(), ac, abn, abv); + ClassDefinition cdApp = ClassDefinitionImpl.toClassDefinitionImpl(sctApp, null, false, config.getIdentification()); + if (!cdApp.isBundle()) cdApp = ((ConfigPro) config).getLogEngine().appenderClassDefintion(cdApp.getClassName()); // layout - ClassDefinition cdLay; Struct sctLay = Caster.toStruct(v.get("layout", null), null); - String lc = AppListenerUtil.toClassName(sctLay); - String lbn = AppListenerUtil.toBundleName(sctLay); - Version lbv = AppListenerUtil.toBundleVersion(sctLay); - if (StringUtil.isEmpty(lbn)) cdLay = ((ConfigPro) config).getLogEngine().layoutClassDefintion(lc); - else cdLay = new ClassDefinitionImpl<>(config.getIdentification(), lc, lbn, lbv); + ClassDefinition cdLay = ClassDefinitionImpl.toClassDefinitionImpl(sctLay, null, false, config.getIdentification()); + if (!cdLay.isBundle()) cdLay = ((ConfigPro) config).getLogEngine().appenderClassDefintion(cdLay.getClassName()); if (cdApp != null && cdApp.hasClass()) { // level @@ -511,6 +501,8 @@ private static LoggerAndSourceData addLogger(Collection.Key name, int level, Cla public abstract void setShowTest(boolean b); + public abstract int getDebugOptions(); + public abstract boolean hasDebugOptions(int option); public abstract void setDebugOptions(int option); diff --git a/core/src/main/java/lucee/runtime/listener/ClassicAppListener.java b/core/src/main/java/lucee/runtime/listener/ClassicAppListener.java index b8d8407fc8..832e27b63a 100755 --- a/core/src/main/java/lucee/runtime/listener/ClassicAppListener.java +++ b/core/src/main/java/lucee/runtime/listener/ClassicAppListener.java @@ -32,6 +32,7 @@ import lucee.runtime.type.UDF; import lucee.runtime.type.scope.Application; import lucee.runtime.type.scope.Session; +import lucee.runtime.util.PageContextUtil; public final class ClassicAppListener extends AppListenerSupport { @@ -121,7 +122,7 @@ public void onDebug(PageContext pc) throws PageException { public static void _onDebug(PageContext pc) throws PageException { try { - if (pc.getConfig().debug()) pc.getDebugger().writeOut(pc); + if (PageContextUtil.show(pc)) pc.getDebugger().writeOut(pc); } catch (IOException e) { throw Caster.toPageException(e); diff --git a/core/src/main/java/lucee/runtime/listener/ClassicApplicationContext.java b/core/src/main/java/lucee/runtime/listener/ClassicApplicationContext.java index 908690a3e6..00a97cb4b7 100755 --- a/core/src/main/java/lucee/runtime/listener/ClassicApplicationContext.java +++ b/core/src/main/java/lucee/runtime/listener/ClassicApplicationContext.java @@ -218,7 +218,6 @@ public ClassicApplicationContext(ConfigWeb config, String name, boolean isDefaul this.source = source; this.triggerComponentDataMember = config.getTriggerComponentDataMember(); this.restSettings = config.getRestSetting(); - this.javaSettings = new JavaSettingsImpl(); this.wstype = WS_TYPE_AXIS1; cgiScopeReadonly = cp.getCGIScopeReadonly(); this.antiSamyPolicy = ((ConfigPro) config).getAntiSamyPolicy(); @@ -879,6 +878,7 @@ public Resource[] getRestCFCLocations() { @Override public JavaSettings getJavaSettings() { + if (javaSettings == null) javaSettings = ModernApplicationContext.getDefaultJavaSettings(config); return javaSettings; } @@ -1240,6 +1240,11 @@ public boolean hasDebugOptions(int option) { return (debugging & option) > 0; } + @Override + public int getDebugOptions() { + return debugging; + } + @Override public void setDebugOptions(int option) { if (!hasDebugOptions(option)) debugging += option; diff --git a/core/src/main/java/lucee/runtime/listener/JavaSettingsImpl.java b/core/src/main/java/lucee/runtime/listener/JavaSettingsImpl.java index 7fed733d11..11a1483860 100644 --- a/core/src/main/java/lucee/runtime/listener/JavaSettingsImpl.java +++ b/core/src/main/java/lucee/runtime/listener/JavaSettingsImpl.java @@ -18,17 +18,28 @@ **/ package lucee.runtime.listener; +import java.io.IOException; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import lucee.commons.digest.HashUtil; +import lucee.commons.io.log.Log; import lucee.commons.io.log.LogUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; +import lucee.runtime.config.Config; +import lucee.runtime.config.ConfigPro; import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.PageException; +import lucee.runtime.mvn.MavenUtil; +import lucee.runtime.mvn.POM; import lucee.runtime.op.Caster; import lucee.runtime.op.Decision; import lucee.runtime.osgi.BundleFile; @@ -38,10 +49,13 @@ import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.KeyConstants; import lucee.runtime.type.util.ListUtil; -import lucee.transformer.bytecode.util.SystemExitScanner; public class JavaSettingsImpl implements JavaSettings { + private static final int DEFAULT_WATCH_INTERVAL = 60; + + private Collection poms; + private Collection osgis; private final Resource[] resources; private Resource[] resourcesTranslated; private final Resource[] bundles; @@ -51,25 +65,178 @@ public class JavaSettingsImpl implements JavaSettings { private final int watchInterval; private final String[] watchedExtensions; private boolean hasBundlesTranslated; + private Config config; - public JavaSettingsImpl() { - this.resources = new Resource[0]; - this.bundles = new Resource[0]; - this.loadCFMLClassPath = false; - this.reloadOnChange = false; - this.watchInterval = 60; - this.watchedExtensions = new String[] { "jar", "class" }; - } + private String id; - public JavaSettingsImpl(Resource[] resources, Resource[] bundles, Boolean loadCFMLClassPath, boolean reloadOnChange, int watchInterval, String[] watchedExtensions) - throws PageException { - this.resources = resources; - this.bundles = bundles; - this.loadCFMLClassPath = loadCFMLClassPath; + private JavaSettingsImpl(String id, Config config, Collection poms, Collection osgis, Resource[] resources, Resource[] bundles, Boolean loadCFMLClassPath, + boolean reloadOnChange, int watchInterval, String[] watchedExtensions) { + this.config = config == null ? ThreadLocalPageContext.getConfig() : config; + this.id = id; + this.poms = poms; + this.osgis = osgis; + this.resources = resources == null ? new Resource[0] : resources; + this.bundles = bundles == null ? new Resource[0] : bundles; + this.loadCFMLClassPath = Boolean.TRUE.equals(loadCFMLClassPath); this.reloadOnChange = reloadOnChange; this.watchInterval = watchInterval; - this.watchedExtensions = watchedExtensions; - SystemExitScanner.validate(resources); + this.watchedExtensions = watchedExtensions == null ? new String[] { "jar", "class" } : watchedExtensions; + // TODO needed? SystemExitScanner.validate(resources); + } + + public static JavaSettings merge(Config config, JavaSettings l, JavaSettings r) { + JavaSettingsImpl li = (JavaSettingsImpl) l; + JavaSettingsImpl ri = (JavaSettingsImpl) r; + + String id = HashUtil.create64BitHashAsString(li.id + ":" + ri.id); + + JavaSettings js = ((ConfigPro) config).getJavaSettings(id); + if (js != null) { + return js; + } + boolean lEmpty = true; + boolean rEmpty = true; + // poms + Map mapPOMs = new HashMap<>(); + if (ri.getPoms() != null) { + for (POM pom: ri.getPoms()) { + mapPOMs.put(pom.id(), pom); + rEmpty = false; + } + } + if (li.getPoms() != null) { + for (POM pom: li.getPoms()) { + mapPOMs.put(pom.id(), pom); + lEmpty = false; + } + } + + // osgis + Map mapOSGIs = new HashMap<>(); + if (ri.osgis != null) { + for (BD bd: ri.osgis) { + mapOSGIs.put(bd.toString(), bd); + rEmpty = false; + } + } + if (li.osgis != null) { + for (BD bd: li.osgis) { + mapOSGIs.put(bd.toString(), bd); + lEmpty = false; + } + } + + // resources + Map mapResources = new HashMap<>(); + for (Resource res: ri.getResources()) { + mapResources.put(res.getAbsolutePath(), res); + rEmpty = false; + } + for (Resource res: li.getResources()) { + mapResources.put(res.getAbsolutePath(), res); + lEmpty = false; + } + + // bundles + Map mapBundles = new HashMap<>(); + for (Resource res: ri.getBundles()) { + mapBundles.put(res.getAbsolutePath(), res); + rEmpty = false; + } + for (Resource res: li.getBundles()) { + mapBundles.put(res.getAbsolutePath(), res); + lEmpty = false; + } + + // watched extensions + Map mapWatched = new HashMap<>(); + if (ri.watchedExtensions != null) { + for (String str: ri.watchedExtensions) { + mapWatched.put(str, ""); + } + } + if (li.watchedExtensions != null) { + for (String str: li.watchedExtensions) { + mapWatched.put(str, ""); + } + } + + if (lEmpty) { + ((ConfigPro) config).setJavaSettings(id, r); + return r; + } + if (rEmpty) { + ((ConfigPro) config).setJavaSettings(id, l); + return l; + } + js = new JavaSettingsImpl(id, config, mapPOMs.values(), mapOSGIs.values(), mapResources.values().toArray(new Resource[mapResources.size()]), + mapBundles.values().toArray(new Resource[mapBundles.size()]), ri.loadCFMLClassPath, ri.reloadOnChange, ri.watchInterval, + mapWatched.keySet().toArray(new String[mapWatched.size()])); + + ((ConfigPro) config).setJavaSettings(id, js); + return js; + } + + public boolean hasPoms() { + return poms != null && poms.size() > 0; + } + + public String id() { + return id; + } + + public Collection getPoms() { + return poms; + } + + public boolean hasOSGis() { + return osgis != null && osgis.size() > 0; + } + + /* + * private ResourceClassLoader getClassLoader(boolean reload) throws IOException { if (classLoader + * == null || reload) { ClassLoader parent = SystemUtil.getCombinedClassLoader(); + * Collection allResources = getAllResources((ClassLoader) null); return classLoader = + * ResourceClassLoader.getInstance(allResources, parent); } return classLoader; } + */ + + public static Collection getAllResources(JavaSettings js) throws IOException { + return ((JavaSettingsImpl) js).getAllResources((ClassLoader) null); + } + + public Collection getAllResources(ClassLoader parent) throws IOException { + Map mapJars = new HashMap<>(); + + Resource[] tmp; + + // maven + if (poms != null) { + for (POM pom: poms) { + tmp = pom.getJars(); + if (tmp != null) { + for (Resource r: tmp) { + mapJars.put(r.getAbsolutePath(), r); + } + } + } + } + + // TODO OSGi + + // jars + Resource[] jars = getResourcesTranslated(); + if (jars != null && jars.length > 0) { + for (Resource r: jars) { + mapJars.put(r.getAbsolutePath(), r); + } + } + + // resources passed in + /* + * if (resources != null && resources.length > 0) { for (Resource r: resources) { + * mapJars.put(r.getAbsolutePath(), r); } } + */ + return mapJars.values(); } @Override @@ -157,42 +324,162 @@ public String[] watchedExtensions() { return watchedExtensions; } - public static JavaSettingsImpl newInstance(JavaSettings base, Struct sct) throws PageException { + public static JavaSettings getInstance(Config config, Struct data, Object addionalResources) { + + List names = new ArrayList<>(); + + // maven + List poms = null; + { + Object obj = data == null ? null : data.get(KeyConstants._maven, null); + if (obj == null) obj = data == null ? null : data.get(KeyConstants._mvn, null); + if (obj != null) { + Array arr = Caster.toArray(obj, null); + if (arr == null) { + Struct tmp = Caster.toStruct(obj, null); + if (tmp != null) { + arr = new ArrayImpl(); + arr.appendEL(tmp); + } + } + + if (arr != null) { + Iterator it = arr.valueIterator(); + String g, a, v, s; + // TODO add method getMavenDir to config + Resource dir = ((ConfigPro) config).getMavenDir(); + dir.mkdirs(); + Log log = config.getLog("application"); + while (it.hasNext()) { + Struct el = Caster.toStruct(it.next(), null); + if (el != null) { + g = Caster.toString(el.get(KeyConstants._groupId, null), null); + a = Caster.toString(el.get(KeyConstants._artifactId, null), null); + v = Caster.toString(el.get(KeyConstants._version, null), null); + s = Caster.toString(el.get(KeyConstants._scope, null), null); + if (!StringUtil.isEmpty(g) && !StringUtil.isEmpty(a)) { + if (poms == null) poms = new ArrayList<>(); + POM tmp = POM.getInstance(dir, g, a, v, MavenUtil.toScopes(s, POM.SCOPE_COMPILE), log); + poms.add(tmp); + names.add("maven:" + tmp.getGroupId() + ":" + tmp.getArtifactId() + ":" + tmp.getVersion()); + } + } + } + } + } + } + + // osgi + List osgis = null; + { + Object obj = data == null ? null : data.get(KeyConstants._osgi, null); + if (obj != null) { + Array arr = Caster.toArray(obj, null); + if (arr == null) { + Struct tmp = Caster.toStruct(obj, null); + if (tmp != null) { + arr = new ArrayImpl(); + arr.appendEL(tmp); + } + } + + if (arr != null) { + Iterator it = arr.valueIterator(); + String n, v; + BD tmp; + while (it.hasNext()) { + Struct el = Caster.toStruct(it.next(), null); + if (el != null) { + n = Caster.toString(el.get(KeyConstants._name, null), null); + if (StringUtil.isEmpty(n, true)) n = Caster.toString(el.get(KeyConstants._bundleName, null), null); + v = Caster.toString(el.get(KeyConstants._version, null), null); + if (StringUtil.isEmpty(v, true)) v = Caster.toString(el.get(KeyConstants._bundleVersion, null), null); + + if (!StringUtil.isEmpty(n, true)) { + if (osgis == null) osgis = new ArrayList<>(); + tmp = new BD(n.trim(), v == null ? v : v.trim()); + osgis.add(tmp); + names.add("osgi:" + tmp.name + ":" + tmp.version); + } + } + } + } + } + } + // load paths - List paths; + Collection paths; { - Object obj = sct.get(KeyConstants._loadPaths, null); + Object obj = data == null ? null : data.get(KeyConstants._loadPaths, null); if (obj != null) { paths = loadPaths(ThreadLocalPageContext.get(), obj); + for (Resource p: paths) { + names.add("paths:" + p.getAbsolutePath()); + } } else paths = new ArrayList(); } + // addional resources + if (addionalResources != null) { + Map map = new HashMap<>(); + for (Resource r: paths) { + map.put(r.getAbsolutePath(), r); + } + if (addionalResources instanceof Resource[]) { + for (Resource r: (Resource[]) addionalResources) { + map.put(r.getAbsolutePath(), r); + names.add("addional:" + r.getAbsolutePath()); + } + } + else if (addionalResources instanceof List) { + for (Resource r: (List) addionalResources) { + map.put(r.getAbsolutePath(), r); + names.add("addional:" + r.getAbsolutePath()); + } + } + paths = map.values(); + } + // bundles paths List bundles; { - Object obj = sct.get(KeyConstants._bundlePaths, null); - if (obj == null) obj = sct.get(KeyConstants._bundles, null); - if (obj == null) obj = sct.get(KeyConstants._bundleDirectory, null); - if (obj == null) obj = sct.get(KeyConstants._bundleDirectories, null); + Object obj = data == null ? null : data.get(KeyConstants._bundlePaths, null); + if (obj == null) obj = data == null ? null : data.get(KeyConstants._bundles, null); + if (obj == null) obj = data == null ? null : data.get(KeyConstants._bundleDirectory, null); + if (obj == null) obj = data == null ? null : data.get(KeyConstants._bundleDirectories, null); if (obj != null) { bundles = loadPaths(ThreadLocalPageContext.get(), obj); + for (Resource b: bundles) { + names.add("bundles:" + b.getAbsolutePath()); + } } else bundles = new ArrayList(); } + // loadCFMLClassPath - Boolean loadCFMLClassPath = Caster.toBoolean(sct.get(KeyConstants._loadCFMLClassPath, null), null); - if (loadCFMLClassPath == null) loadCFMLClassPath = Caster.toBoolean(sct.get(KeyConstants._loadColdFusionClassPath, null), null); - if (loadCFMLClassPath == null) loadCFMLClassPath = base.loadCFMLClassPath(); + Boolean loadCFMLClassPath = Caster.toBoolean(data == null ? null : data.get(KeyConstants._loadCFMLClassPath, null), null); + if (loadCFMLClassPath == null) { + loadCFMLClassPath = Caster.toBoolean(data == null ? null : data.get(KeyConstants._loadColdFusionClassPath, null), null); + + } + names.add("loadCFMLClassPath:" + loadCFMLClassPath); + //// if (loadCFMLClassPath == null) loadCFMLClassPath = base.loadCFMLClassPath(); // reloadOnChange - boolean reloadOnChange = Caster.toBooleanValue(sct.get(KeyConstants._reloadOnChange, null), base.reloadOnChange()); + //// boolean reloadOnChange = Caster.toBooleanValue(sct.get(KeyConstants._reloadOnChange, null), + // base.reloadOnChange()); + boolean reloadOnChange = Caster.toBooleanValue(data == null ? null : data.get(KeyConstants._reloadOnChange, null), false); + names.add("reloadOnChange:" + reloadOnChange); // watchInterval - int watchInterval = Caster.toIntValue(sct.get(KeyConstants._watchInterval, null), base.watchInterval()); + //// int watchInterval = Caster.toIntValue(sct.get(KeyConstants._watchInterval, null), + // base.watchInterval()); + int watchInterval = Caster.toIntValue(data == null ? null : data.get(KeyConstants._watchInterval, null), DEFAULT_WATCH_INTERVAL); + names.add("watchInterval:" + watchInterval); // watchExtensions - Object obj = sct.get(KeyConstants._watchExtensions, null); + Object obj = data == null ? null : data.get(KeyConstants._watchExtensions, null); List extensions = new ArrayList(); if (obj != null) { Array arr; @@ -215,11 +502,23 @@ public static JavaSettingsImpl newInstance(JavaSettings base, Struct sct) throws ext = ext.trim(); if (ext.startsWith(".")) ext = ext.substring(1); if (ext.startsWith("*.")) ext = ext.substring(2); + names.add("ext:" + ext); extensions.add(ext); } } - return new JavaSettingsImpl(paths.toArray(new Resource[paths.size()]), bundles.toArray(new Resource[bundles.size()]), loadCFMLClassPath, reloadOnChange, watchInterval, - extensions.toArray(new String[extensions.size()])); + + Collections.sort(names); + String id = HashUtil.create64BitHashAsString(names.toString()); + + JavaSettings js = ((ConfigPro) config).getJavaSettings(id); + if (js != null) { + return js; + } + js = new JavaSettingsImpl(id, config, poms, osgis, paths.toArray(new Resource[paths.size()]), bundles.toArray(new Resource[bundles.size()]), loadCFMLClassPath, + reloadOnChange, watchInterval, extensions.toArray(new String[extensions.size()])); + + ((ConfigPro) config).setJavaSettings(id, js); + return js; } private static java.util.List loadPaths(PageContext pc, Object obj) { @@ -242,7 +541,7 @@ private static java.util.List loadPaths(PageContext pc, Object obj) { if (path == null) continue; res = AppListenerUtil.toResourceExisting(pc.getConfig(), pc.getApplicationContext(), path, false); if (res == null || !res.exists()) res = ResourceUtil.toResourceExisting(pc, path, true, null); - if (res != null) list.add(res); + if (res != null) list.add(ResourceUtil.getCanonicalResourceEL(res)); } catch (Exception e) { LogUtil.log(pc, ModernApplicationContext.class.getName(), e); @@ -264,4 +563,21 @@ public static List getBundleDirectories(PageContext pc) { return js.getBundlesTranslated(); } + public static class BD { + + public final String name; + public final String version; + + public BD(String name, String version) { + this.name = name; + this.version = version; + } + + @Override + public String toString() { + return name + ":" + version; + } + + } + } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/listener/ModernAppListener.java b/core/src/main/java/lucee/runtime/listener/ModernAppListener.java index 9f0fbdc245..78bdc25980 100755 --- a/core/src/main/java/lucee/runtime/listener/ModernAppListener.java +++ b/core/src/main/java/lucee/runtime/listener/ModernAppListener.java @@ -75,6 +75,7 @@ import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.KeyConstants; import lucee.runtime.type.util.UDFUtil; +import lucee.runtime.util.PageContextUtil; public class ModernAppListener extends AppListenerSupport { @@ -266,7 +267,7 @@ private PageException handlePageException(PageContextImpl pci, Component app, Pa else return pe; } else { - if (!pci.isGatewayContext() && pci.getConfig().debug()) { + if (!pci.isGatewayContext() && PageContextUtil.show(pci)) { ((DebuggerImpl) pci.getDebugger()).setAbort(ExceptionUtil.getThrowingPosition(pci, _pe)); } goon.setValue(false); @@ -394,7 +395,7 @@ private PageContextImpl createPageContext(CFMLFactory factory, Component app, St @Override public void onDebug(PageContext pc) throws PageException { - if (((PageContextImpl) pc).isGatewayContext() || !pc.getConfig().debug()) return; + if (((PageContextImpl) pc).isGatewayContext() || !PageContextUtil.show(pc)) return; Component app = getComponent(pc); if (app != null && app.contains(pc, KeyConstants._onDebug)) { call(app, pc, KeyConstants._onDebug, new Object[] { pc.getDebugger().getDebuggingData(pc) }, true); diff --git a/core/src/main/java/lucee/runtime/listener/ModernAppListenerException.java b/core/src/main/java/lucee/runtime/listener/ModernAppListenerException.java index 626095f35d..49de06fbff 100644 --- a/core/src/main/java/lucee/runtime/listener/ModernAppListenerException.java +++ b/core/src/main/java/lucee/runtime/listener/ModernAppListenerException.java @@ -157,7 +157,7 @@ public void setTracePointer(int tracePointer) { @Override public boolean typeEqual(String type) { - return rootCause.equals(type); + return rootCause.getTypeAsString().equals(type); } @Override diff --git a/core/src/main/java/lucee/runtime/listener/ModernApplicationContext.java b/core/src/main/java/lucee/runtime/listener/ModernApplicationContext.java index daa3686e69..4ced983fb7 100644 --- a/core/src/main/java/lucee/runtime/listener/ModernApplicationContext.java +++ b/core/src/main/java/lucee/runtime/listener/ModernApplicationContext.java @@ -41,6 +41,7 @@ import lucee.commons.lang.CharSet; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.Pair; +import lucee.commons.lang.SerializableObject; import lucee.commons.lang.StringUtil; import lucee.commons.lang.types.RefBoolean; import lucee.runtime.Component; @@ -54,11 +55,11 @@ import lucee.runtime.component.Member; import lucee.runtime.config.Config; import lucee.runtime.config.ConfigPro; +import lucee.runtime.config.ConfigWeb; import lucee.runtime.config.ConfigWebUtil; import lucee.runtime.db.ClassDefinition; import lucee.runtime.db.DataSource; import lucee.runtime.engine.ThreadLocalPageContext; -import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.DeprecatedException; import lucee.runtime.exp.PageException; import lucee.runtime.exp.PageRuntimeException; @@ -164,6 +165,8 @@ public class ModernApplicationContext extends ApplicationContextSupport { private static final Key USE_JAVA_AS_REGEX_ENGINE = KeyConstants._useJavaAsRegexEngine; private static Map initCacheConnections = new ConcurrentHashMap(); + private static Object token = new SerializableObject(); + private static JavaSettings defaultJavaSettings; private Component component; @@ -269,6 +272,7 @@ public class ModernApplicationContext extends ApplicationContextSupport { private boolean initRestSetting; private RestSettings restSetting; private boolean initJavaSettings; + private boolean initJavaSettingsBefore; private JavaSettings javaSettings; private Object ormDatasource; private Locale locale; @@ -362,7 +366,6 @@ public ModernApplicationContext(PageContext pc, Component cfc, RefBoolean throws this.limitEvaluation = ci.limitEvaluation(); this.triggerComponentDataMember = config.getTriggerComponentDataMember(); this.restSetting = config.getRestSetting(); - this.javaSettings = new JavaSettingsImpl(); this.component = cfc; this.regex = ci.getRegex(); this.preciseMath = ci.getPreciseMath(); @@ -997,12 +1000,9 @@ public static CacheConnection toCacheConnection(Config config, String name, Stru } } - public static CacheConnection toCacheConnection(Config config, String name, Struct data) throws ApplicationException { + public static CacheConnection toCacheConnection(Config config, String name, Struct data) { // class definition - String className = Caster.toString(data.get(KeyConstants._class, null), null); - if (StringUtil.isEmpty(className)) throw new ApplicationException("missing key class in struct the defines a cachec connection"); - ClassDefinition cd = new ClassDefinitionImpl(className, Caster.toString(data.get(KeyConstants._bundleName, null), null), - Caster.toString(data.get(KeyConstants._bundleVersion, null), null), config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(data, null, true, config.getIdentification()); CacheConnectionImpl cc = new CacheConnectionImpl(config, name, cd, Caster.toStruct(data.get(KeyConstants._custom, null), null), Caster.toBooleanValue(data.get(KeyConstants._readonly, null), false), Caster.toBooleanValue(data.get(KeyConstants._storage, null), false)); @@ -1311,6 +1311,12 @@ public void setShowTest(boolean b) { showTest = b; } + @Override + public int getDebugOptions() { + if (!initMonitor) initMonitor(); + return debugging; + } + @Override public boolean hasDebugOptions(int option) { if (!initMonitor) initMonitor(); @@ -1795,24 +1801,41 @@ public void setJavaSettings(JavaSettings javaSettings) { @Override public JavaSettings getJavaSettings() { - initJava(); - return javaSettings; + + return initJava(); } - private void initJava() { + private JavaSettings initJava() { if (!initJavaSettings) { + // PATCH to avoid cycle + if (initJavaSettingsBefore) { + return getDefaultJavaSettings(config); + } + initJavaSettingsBefore = true; + Object o = get(component, JAVA_SETTING, null); if (o != null && Decision.isStruct(o)) { - try { - javaSettings = JavaSettingsImpl.newInstance(javaSettings, Caster.toStruct(o, null)); - } - catch (PageException e) { - throw new PageRuntimeException(e); - } - + javaSettings = JavaSettingsImpl.getInstance(config, Caster.toStruct(o, null), null); + javaSettings = JavaSettingsImpl.merge(config, javaSettings, getDefaultJavaSettings(config)); + } + if (javaSettings == null) { + javaSettings = getDefaultJavaSettings(config); } initJavaSettings = true; + initJavaSettingsBefore = false; + } + return javaSettings; + } + + public static JavaSettings getDefaultJavaSettings(ConfigWeb config) { + if (defaultJavaSettings == null) { + synchronized (token) { + if (defaultJavaSettings == null) { + defaultJavaSettings = ((ConfigPro) config).getJavaSettings();// JavaSettingsImpl.getInstance(config, new StructImpl()); + } + } } + return defaultJavaSettings; } @Override diff --git a/core/src/main/java/lucee/runtime/listener/NoneAppListener.java b/core/src/main/java/lucee/runtime/listener/NoneAppListener.java index d33917b540..629c780e70 100755 --- a/core/src/main/java/lucee/runtime/listener/NoneAppListener.java +++ b/core/src/main/java/lucee/runtime/listener/NoneAppListener.java @@ -27,6 +27,7 @@ import lucee.runtime.op.Caster; import lucee.runtime.type.scope.Application; import lucee.runtime.type.scope.Session; +import lucee.runtime.util.PageContextUtil; public final class NoneAppListener extends AppListenerSupport { @@ -76,7 +77,7 @@ public void onSessionEnd(CFMLFactory cfmlFactory, String applicationName, String @Override public void onDebug(PageContext pc) throws PageException { try { - if (pc.getConfig().debug()) pc.getDebugger().writeOut(pc); + if (PageContextUtil.show(pc)) pc.getDebugger().writeOut(pc); } catch (IOException e) { throw Caster.toPageException(e); diff --git a/core/src/main/java/lucee/runtime/mvn/MavenUtil.java b/core/src/main/java/lucee/runtime/mvn/MavenUtil.java new file mode 100644 index 0000000000..5b03c81df2 --- /dev/null +++ b/core/src/main/java/lucee/runtime/mvn/MavenUtil.java @@ -0,0 +1,415 @@ +package lucee.runtime.mvn; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; + +import lucee.commons.io.IOUtil; +import lucee.commons.io.log.Log; +import lucee.commons.io.res.Resource; +import lucee.commons.lang.ExceptionUtil; +import lucee.commons.lang.SerializableObject; +import lucee.commons.lang.StringUtil; +import lucee.runtime.mvn.POMReader.Dependency; +import lucee.runtime.thread.ThreadUtil; +import lucee.runtime.type.util.ListUtil; + +public class MavenUtil { + private static Map sysprops; + private static Object token = new SerializableObject(); + + public static Map getProperties(Map rawProperties, POM parent) throws IOException { + Map properties = parent != null ? parent.getProperties() : new LinkedHashMap<>(); + + int size = properties == null ? 0 : properties.size(); + if (rawProperties != null) size += rawProperties.size(); + + Map newProperties = new HashMap<>(size); + + // copy data from parent + if (properties != null) { + for (Entry e: properties.entrySet()) { + newProperties.put(e.getKey(), e.getValue()); + } + } + + // add new data + if (rawProperties != null) { + for (Entry e: rawProperties.entrySet()) { + newProperties.put(e.getKey(), e.getValue()); + } + } + return newProperties; + } + + public static Map getSystemProperties() { + if (sysprops == null) { + synchronized (token) { + if (sysprops == null) { + Properties props = System.getProperties(); + sysprops = new HashMap<>(props.size()); + for (String name: props.stringPropertyNames()) { + sysprops.put(name, props.getProperty(name)); + } + } + } + } + return sysprops; + } + + public static Collection getRepositories(List rawRepositories, POM current, POM parent, Map properties, + Repository defaultRepository) throws IOException { + Map repositories = new LinkedHashMap<>(); + repositories.put(defaultRepository.getUrl(), defaultRepository); + if (parent != null) { + Collection reps = parent.getRepositories(); + if (reps != null) { + for (Repository r: reps) { + repositories.put(r.getUrl(), r); // TODO clone? + } + } + } + if (rawRepositories != null) { + for (POMReader.Repository rep: rawRepositories) { + Repository r = new Repository( + + resolvePlaceholders(current, rep.id, properties), + + resolvePlaceholders(current, rep.name, properties), + + resolvePlaceholders(current, rep.url, properties) + + ); + repositories.put(r.getUrl(), r); + } + } + return repositories.values(); + + } + + public static List getDependencies(List rawDependencies, POM current, POM parent, Map properties, Resource localDirectory, + boolean management, Log log) throws IOException { + List dependencies = new ArrayList<>(); + List parentDendencyManagement = null; + + ExecutorService executor = ThreadUtil.createExecutorService(Runtime.getRuntime().availableProcessors()); + + if (parent != null) { + parentDendencyManagement = current.getDependencyManagement(); + List tmp = parent.getDependencies(); + if (tmp != null) { + for (POM pom: tmp) { + dependencies.add(pom); // TODO clone? + } + } + } + if (rawDependencies != null) { + List> futures = new ArrayList<>(); + for (POMReader.Dependency rd: rawDependencies) { + GAVSO gavso = getDependency(rd, parent, current, properties, parentDendencyManagement, management); + if (gavso == null) continue; + + Future future = executor.submit(() -> { + POM p = POM.getInstance(localDirectory, current.getRepositories(), gavso.g, gavso.a, gavso.v, gavso.s, gavso.o, current.getDependencyScope(), + current.getDependencyScopeManagement(), log); + p.initXML(); + return p; + }); + futures.add(future); + } + try { + for (Future future: futures) { + dependencies.add(future.get()); // Wait for init to complete + } + } + catch (Exception e) { + throw ExceptionUtil.toIOException(e); + } + } + executor.shutdown(); + return dependencies; + } + + public static GAVSO getDependency(POMReader.Dependency rd, POM parent, POM current, Map properties, List parentDendencyManagement, boolean management) + throws IOException { + POM pdm = null;// TODO move out of here so multiple loop elements can profit + + String g = resolvePlaceholders(current, rd.groupId, properties); + String a = resolvePlaceholders(current, rd.artifactId, properties); + + // scope + String s = rd.scope; + if (s == null && parentDendencyManagement != null) { + if (pdm == null) pdm = getDendency(parentDendencyManagement, g, a); + if (pdm != null) { + s = pdm.getScopeAsString(); + } + } + if (s != null) s = resolvePlaceholders(current, s, properties); + + // scope allowed? + if (!allowed(management ? current.getDependencyScopeManagement() : current.getDependencyScope(), toScope(s, POM.SCOPE_COMPILE))) { + return null; + } + + // version + String v = rd.version; + if (v == null) { + pdm = getDendency(parentDendencyManagement, g, a); + + if (pdm != null) { + v = pdm.getVersion(); + } + if (v == null) { + throw new IOException("could not find version for dependency [" + g + ":" + a + "] in [" + current + "]"); + } + } + v = resolvePlaceholders(current, v, properties); + // PATCH TODO better solution for this + if (v != null && v.startsWith("[")) { + v = v.substring(1, v.indexOf(',')); + } + + // optional + String o = rd.optional; + if (o == null && parentDendencyManagement != null) { + if (pdm == null) pdm = getDendency(parentDendencyManagement, g, a); + if (pdm != null) { + o = pdm.getOptionalAsString(); + } + } + if (o != null) s = resolvePlaceholders(current, o, properties); + return new GAVSO(g, a, v, s, o); + // p = POM.getInstance(localDirectory, g, a, v, s, o, current.getDependencyScope(), + // current.getDependencyScopeManagement()); + + // dependencies.add(p); + } + + static class GAVSO { + public final String g; + public final String a; + public final String v; + public final String s; + public final String o; + + public GAVSO(String g, String a, String v) { + this.g = g; + this.a = a; + this.v = v; + this.s = null; + this.o = null; + } + + public GAVSO(String g, String a, String v, String s, String o) { + this.g = g; + this.a = a; + this.v = v; + this.s = s; + this.o = o; + } + } + + public static boolean allowed(int allowedScopes, int scope) { + return (allowedScopes & scope) != 0; + } + + private static POM getDendency(List dependencies, String groupId, String artifactId) { + if (dependencies != null) { + for (POM pom: dependencies) { + if (pom.getGroupId().equals(groupId) && pom.getArtifactId().equals(artifactId)) return pom; + } + } + return null; + } + + public static List getDependencyManagement(List rawDependencies, POM current, POM parent, Map properties, Resource localDirectory, + Log log) throws IOException { + + List dependencies = new ArrayList<>(); + + if (parent != null) { + List deps = parent.getDependencyManagement(); + if (deps != null) { + for (POM pom: deps) { + dependencies.add(pom); // TODO clone? + } + } + } + + if (rawDependencies != null) { + for (Dependency rd: rawDependencies) { + GAVSO gavso = getDependency(rd, parent, current, properties, null, true); + if (gavso == null) continue; + POM p = POM.getInstance(localDirectory, current.getRepositories(), gavso.g, gavso.a, gavso.v, gavso.s, gavso.o, current.getDependencyScope(), + current.getDependencyScopeManagement(), log); + dependencies.add(p); + } + } + return dependencies; + } + + public static String resolvePlaceholders(POM pom, String value, Map properties) throws IOException { + boolean modifed; + while (value != null && value.contains("${")) { + modifed = false; + if (pom != null && value != null && value.contains("${project.")) { + String placeholder = value.substring(value.indexOf("${project.") + 10, value.indexOf("}")); + + if ("groupId".equals(placeholder)) { + value = pom.getGroupId(); + modifed = true; + } + else if ("artifactId".equals(placeholder)) { + value = pom.getArtifactId(); + modifed = true; + } + else if ("version".equals(placeholder)) { + value = pom.getVersion(); + modifed = true; + } + else if ("scope".equals(placeholder) && pom.getScopeUnresolved() != null) { + value = pom.getScopeUnresolved(); + modifed = true; + } + else if ("optional".equals(placeholder)) { + value = pom.getOptionaUnresolved(); + modifed = true; + } + // TODO is there more? + } + + // Resolve placeholders using properties + if (value != null && value.contains("${")) { + String placeholder = value.substring(value.indexOf("${") + 2, value.indexOf("}")); + String val = properties.get(placeholder); + if (val != null && !val.equals(value)) { + modifed = true; + value = val; + } + } + + if (value != null && value.contains("${")) { + String placeholder = value.substring(value.indexOf("${") + 2, value.indexOf("}")); + String resolvedValue = MavenUtil.getSystemProperties().get(placeholder); + if (resolvedValue != null && !resolvedValue.equals(value)) { + modifed = true; + value = resolvedValue; + } + } + if (!modifed) break; + } + if (value != null && value.indexOf("${") != -1) { + throw new IOException("cannot resolve [" + value + "] for [" + pom + "], available properties are [" + ListUtil.toList(properties.keySet(), ", ") + "]"); + } + return value; + } + + public static boolean hasPlaceholders(String str) { + return str != null && str.indexOf("${") != -1; + } + + public static void download(POM pom, Collection repositories, String type, Log log) throws IOException { + Resource res = pom.getArtifact(type); + if (!res.isFile()) { + URL url = pom.getArtifact(type, repositories); + if (log != null) log.info("maven", "download [" + url + "]"); + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpGet request = new HttpGet(pom.getArtifact(type, repositories).toExternalForm()); + HttpResponse response = httpClient.execute(request); + HttpEntity entity = response.getEntity(); + int sc = response.getStatusLine().getStatusCode(); + if (sc == 200) { + if (entity != null) { + try (InputStream is = entity.getContent()) { + IOUtil.copy(is, res, false); + } + } + } + else { + EntityUtils.consume(entity); // Ensure the response entity is fully consumed + throw new IOException("Failed to download: " + url + " for [" + pom + "] - " + response.getStatusLine().getStatusCode()); + } + } + } // TODO handle not 200 + } + + public static POM toPOM(Resource localDirectory, Collection repositories, POMReader.Dependency dependency, Map properties, int dependencyScope, + int dependencyScopeManagement, Log log) throws IOException { + + return POM.getInstance(localDirectory, repositories, + + resolvePlaceholders(null, dependency.groupId, properties), + + resolvePlaceholders(null, dependency.artifactId, properties), + + resolvePlaceholders(null, dependency.version, properties), + + null, null, + + dependencyScope, dependencyScopeManagement, + + log + + ); + } + + public static int toScopes(String scopes, int defaultValue) { + if (StringUtil.isEmpty(scopes, true)) return defaultValue; + + int rtn = 0; + for (String scope: ListUtil.listToStringArray(scopes, ',')) { + rtn += toScope(scope, 0); + } + if (rtn > 0) return rtn; + + return defaultValue; + } + + public static int toScope(String scope, int defaultValue) { + if ("compile".equals(scope)) return POM.SCOPE_COMPILE; + if ("test".equals(scope)) return POM.SCOPE_TEST; + if ("provided".equals(scope)) return POM.SCOPE_PROVIDED; + if ("runtime".equals(scope)) return POM.SCOPE_RUNTIME; + if ("system".equals(scope)) return POM.SCOPE_SYSTEM; + if ("import".equals(scope)) return POM.SCOPE_IMPORT; + return defaultValue; + } + + public static String toScope(int scope, String defaultValue) { + switch (scope) { + case POM.SCOPE_COMPILE: + return "compile"; + case POM.SCOPE_TEST: + return "test"; + case POM.SCOPE_PROVIDED: + return "provided"; + case POM.SCOPE_RUNTIME: + return "runtime"; + case POM.SCOPE_SYSTEM: + return "system"; + case POM.SCOPE_IMPORT: + return "import"; + default: + return defaultValue; + } + } + +} diff --git a/core/src/main/java/lucee/runtime/mvn/POM.java b/core/src/main/java/lucee/runtime/mvn/POM.java new file mode 100644 index 0000000000..d339ea747b --- /dev/null +++ b/core/src/main/java/lucee/runtime/mvn/POM.java @@ -0,0 +1,531 @@ +package lucee.runtime.mvn; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.xml.sax.SAXException; + +import lucee.commons.digest.HashUtil; +import lucee.commons.io.log.Log; +import lucee.commons.io.res.Resource; +import lucee.commons.lang.ExceptionUtil; +import lucee.commons.lang.SerializableObject; +import lucee.commons.tree.TreeNode; +import lucee.runtime.mvn.POMReader.Dependency; +import lucee.runtime.op.Caster; + +public class POM { + + public static final Repository DEFAULT_REPOSITORY = new Repository("maven-central", "Maven Central", "https://repo1.maven.org/maven2/"); + + public static final int SCOPE_COMPILE = 1; + public static final int SCOPE_TEST = 2; + public static final int SCOPE_PROVIDED = 4; + public static final int SCOPE_RUNTIME = 8; + public static final int SCOPE_SYSTEM = 16; + public static final int SCOPE_IMPORT = 32; + + public static final int SCOPE_NONE = 0; + public static final int SCOPE_NOT_TEST = SCOPE_COMPILE + SCOPE_PROVIDED + SCOPE_RUNTIME + SCOPE_SYSTEM + SCOPE_IMPORT; + public static final int SCOPE_ALL = SCOPE_NOT_TEST + SCOPE_TEST; + + private Resource localDirectory; + private final String groupId; + private final String artifactId; + private final String version; + private String scope; + private String optional; + private int dependencyScope = SCOPE_ALL; + private int dependencyScopeManagement = SCOPE_ALL; + + private List dependencies; + private List dependencyManagement; + private Collection initRepositories; + private Collection childRepositories; + + private Map properties; + private POM parent; + private boolean isInit = false; + private boolean isInitParent = false; + private boolean isInitRepositories = false; + private boolean isInitProperties = false; + private boolean isInitDependencies = false; + private boolean isInitDependencyManagement = false; + private boolean isInitXML = false; + public static final Map cache = new HashMap<>(); + + private String packaging; + private String name; + private String description; + private String url; + + private Object token = new SerializableObject(); + + private POMReader reader; + + private Log log; + + private String artifactExtension; + + private String hash; + + public static POM getInstanceX(Resource localDirectory, String groupId, String artifactId, String version, Log log) { + return getInstance(localDirectory, null, groupId, artifactId, version, null, null, SCOPE_NOT_TEST, SCOPE_ALL, log); + } + + public static POM getInstance(Resource localDirectory, String groupId, String artifactId, String version, int dependencyScope, Log log) { + return getInstance(localDirectory, null, groupId, artifactId, version, null, null, dependencyScope, SCOPE_ALL, log); + } + + public static POM getInstance(Resource localDirectory, Collection repositories, String groupId, String artifactId, String version, int dependencyScope, + int dependencyScopeManagement, Log log) { + return getInstance(localDirectory, null, groupId, artifactId, version, null, null, dependencyScope, dependencyScopeManagement, log); + } + + static POM getInstance(Resource localDirectory, Collection repositories, String groupId, String artifactId, String version, String scope, String optional, + int dependencyScope, int dependencyScopeManagement, Log log) { + String id = toId(localDirectory, groupId, artifactId, version, scope, optional, dependencyScope, dependencyScopeManagement); + POM pom = cache.get(id); + if (pom != null) { + return pom; + } + + pom = new POM(localDirectory, repositories, groupId, artifactId, version, scope, optional, dependencyScope, dependencyScopeManagement, log); + cache.put(id, pom); + return pom; + } + + private static String toId(Resource localDirectory, String groupId, String artifactId, String version, String scope, String optional, int dependencyScope, + int dependencyScopeManagement) { + // TODO Auto-generated method stub + return localDirectory + ":" + groupId + ":" + artifactId + ":" + version + ":" + scope + ":" + optional + ":" + dependencyScope + ":" + dependencyScopeManagement; + } + + private POM(Resource localDirectory, Collection repositories, String groupId, String artifactId, String version, String scope, String optional, int dependencyScope, + int dependencyScopeManagement, Log log) { + if (groupId == null) throw new IllegalArgumentException("groupId cannot be null"); + if (artifactId == null) throw new IllegalArgumentException("artifactId cannot be null"); + if (version == null) throw new IllegalArgumentException("version cannot be null"); + + this.localDirectory = localDirectory; + + if (repositories == null) { + this.initRepositories = new ArrayList<>(); + this.initRepositories.add(DEFAULT_REPOSITORY); + } + else this.initRepositories = repositories; + this.groupId = groupId.trim(); + this.artifactId = artifactId.trim(); + this.version = version == null ? null : version.trim(); + this.scope = scope == null ? null : scope.trim(); + this.optional = optional == null ? null : optional.trim(); + this.dependencyScopeManagement = dependencyScopeManagement; + this.dependencyScope = dependencyScope; + this.log = log; + + cache.put(id(), this); + } + + void initXML() throws IOException { + if (!isInitXML) { + synchronized (token) { + if (!isInitXML) { + MavenUtil.download(this, initRepositories, "pom", log); + + try { + reader = POMReader.getInstance(getPath()); + } + catch (SAXException e) { + IOException cause = ExceptionUtil.toIOException(e); + IOException ioe = new IOException("failed to load pom file [" + getArtifact("pom", initRepositories) + "]"); + ExceptionUtil.initCauseEL(ioe, cause); + throw ioe; + } + this.packaging = reader.getPackaging(); + this.artifactExtension = this.packaging; + if (artifactExtension == null || "bundle".equalsIgnoreCase(artifactExtension)) this.artifactExtension = "jar"; + this.name = reader.getName(); + this.description = reader.getDescription(); + this.url = reader.getURL(); + + if (this.artifactExtension != null && !"pom".equalsIgnoreCase(this.artifactExtension)) MavenUtil.download(this, initRepositories, artifactExtension, log); + + isInitXML = true; + } + } + } + } + + private void initParent() throws IOException { + if (isInitParent) return; + isInitParent = true; + if (log != null) log.debug("maven", "int parent for " + this); + initXML(); + + Dependency p = reader.getParent(); + if (p != null) { + // chicken egg, because there is no parent yet, this cannot use properties from parent + this.parent = MavenUtil.toPOM(this.localDirectory, initRepositories, p, reader.getProperties(), dependencyScope, dependencyScopeManagement, log); + parent.init(); + } + } + + private void initProperties() throws IOException { + if (isInitProperties) return; + isInitProperties = true; + initParent(); + if (log != null) log.debug("maven", "int properties for " + this); + properties = MavenUtil.getProperties(reader.getProperties(), parent); + + } + + private void initRepositories() throws IOException { + if (isInitRepositories) return; + isInitRepositories = true; + if (log != null) log.debug("maven", "int repositories for " + this); + initProperties(); + childRepositories = MavenUtil.getRepositories(reader.getRepositories(), this, parent, properties, DEFAULT_REPOSITORY); + } + + private void initDependencies() throws IOException { + if (isInitDependencies) return; + isInitDependencies = true; + if (log != null) log.debug("maven", "int dependencies for " + this); + initProperties(); + if (dependencyScope > 0) dependencies = MavenUtil.getDependencies(reader.getDependencies(), this, parent, properties, localDirectory, false, log); + } + + private void initDependencyManagement() throws IOException { + if (isInitDependencyManagement) return; + isInitDependencyManagement = true; + if (log != null) log.debug("maven", "int dependencx management for " + this); + initProperties(); + + if (dependencyScopeManagement > 0) + dependencyManagement = MavenUtil.getDependencyManagement(reader.getDependencyManagements(), this, parent, properties, localDirectory, log); + + } + + private void init() throws IOException { + if (isInit) return; + isInit = true; + if (log != null) log.debug("maven", "int for " + this); + initProperties(); + initRepositories(); + initDependencyManagement(); + initDependencies(); + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public String getVersion() { + return version; + } + + public String getPackaging() throws IOException { + initXML(); + return this.packaging == null ? "jar" : this.packaging; + } + + public String getName() throws IOException { + initXML(); + return this.name; + } + + public String getDescription() throws IOException { + initXML(); + return this.description; + } + + public String getURL() throws IOException { + initXML(); + return this.url; + } + + public int getDependencyScopeManagement() { + return dependencyScopeManagement; + } + + public int getDependencyScope() { + return dependencyScope; + } + + public String getScopeUnresolved() { + return scope; + } + + public String getScopeAsString() throws IOException { + initProperties(); + return scope; + } + + public int getScope() throws IOException { + initProperties(); + return MavenUtil.toScope(getScopeAsString(), SCOPE_COMPILE); + } + + public String getOptionaUnresolved() { + return optional; + } + + public String getOptionalAsString() throws IOException { + initProperties(); + return optional; + } + + public boolean getOptional() throws IOException { + initProperties(); + return Boolean.TRUE.equals(Caster.toBoolean(optional, null)); + } + + public Resource getPath() { + return local(localDirectory, "pom"); + } + + private StringBuilder _hash(StringBuilder sb) throws IOException { + List deps = getDependencies(); + if (deps != null) { + for (POM p: deps) { + p._hash(sb); + } + } + sb.append(groupId).append(';').append(artifactId).append(';').append(version); + return sb; + } + + public String hash() throws IOException { + if (hash == null) { + synchronized (groupId) { + if (hash == null) { + hash = HashUtil.create64BitHashAsString(_hash(new StringBuilder())); + } + } + } + return hash; + } + + Resource getArtifact(String type) { + return local(localDirectory, type); + } + + public Resource getArtifact() throws IOException { + initXML(); + if (artifactExtension == null) return null; + return local(localDirectory, artifactExtension); + } + + public boolean isInit() { + return isInit; + } + + public POM getParent() throws IOException { + initParent(); + return parent; + } + + public Map getProperties() throws IOException { + initProperties(); + return properties; + } + + public List getDependencies() throws IOException { + initDependencies(); + return dependencies; + } + + public List getDependencyManagement() throws IOException { + initDependencyManagement(); + return dependencyManagement; + } + + public Collection getRepositories() throws IOException { + initRepositories(); + return childRepositories; + } + + public Resource getLocalDirectory() { + return localDirectory; + } + + public String id() { + return groupId + ":" + artifactId + ":" + version; + } + + public boolean isOptional() { + return Boolean.TRUE.equals(Caster.toBoolean(optional, null)); + } + + private Resource local(Resource dir, String extension) { + Resource parent = dir.getRealResource(groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/"); + if (!parent.isDirectory()) parent.mkdirs(); + return parent.getRealResource(artifactId + "-" + version + "." + extension); + } + + public URL getArtifact(String type, Collection repositories) throws IOException { + // TODO type check + StringBuilder sb = null; + URL url; + if (repositories == null || repositories.isEmpty()) repositories = getRepositories(); + for (Repository r: repositories) { + url = new URL(r.getUrl() + groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + artifactId + "-" + version + "." + type); + + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("HEAD"); + int responseCode = connection.getResponseCode(); + if (responseCode == 200) { + return url; + } + if (sb == null) sb = new StringBuilder(); + else sb.append(", "); + sb.append(url.toExternalForm()); + } + throw new IOException("could not find a valid endpoint for [" + this + "], possibles endpoint are [" + sb + "]"); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + // sb.append("level:").append(level); + sb.append("groupID:").append(groupId); + sb.append(";artifactId:").append(artifactId); + if (version != null) sb.append(";version:").append(version); + if (scope != null) sb.append(";scope:").append(scope); + if (optional != null) sb.append(";optional:").append(optional); + return sb.toString(); + } + + /* + * =========================================================================================== + * ================================== HELPER METHODS ========================================= + * =========================================================================================== + */ + public List> getAllDependenciesAsTrees() throws IOException { + return getDependencies(this, true, 0, new TreeNode(this)).getChildren(); + } + + public List getAllDependencies() throws IOException { + List list = getDependencies(this, true, 0, new TreeNode(this)).asList(); + list.remove(0); + return list; + } + + private static TreeNode getDependencies(POM pom, boolean recursive, int level, TreeNode node) throws IOException { + try { + List deps = pom.getDependencies(); + if (deps != null) { + for (POM p: deps) { + try { + if (!node.addChild(p)) continue; + if (recursive) getDependencies(p, recursive, level + 1, node); + } + catch (IOException ioe) { + node.removeChild(p); + // if optional we let it go + if (!p.isOptional()) throw ioe; + } + } + } + return node; + } + catch (IOException cause) { + IOException e = new IOException("failed to load dependencies in [" + pom + "]"); + ExceptionUtil.initCauseEL(e, cause); + throw e; + } + } + + public List> getAllDependencyManagementAsTrees() throws IOException { + return getDependencyManagement(this, true, 0, new TreeNode(this)).getChildren(); + } + + public List getAllDependencyManagement() throws IOException { + List list = getDependencyManagement(this, true, 0, new TreeNode(this)).asList(); + list.remove(0); + return list; + } + + private static TreeNode getDependencyManagement(POM pom, boolean recursive, int level, TreeNode node) throws IOException { + try { + List deps = pom.getDependencyManagement(); + if (deps != null) { + for (POM p: deps) { + try { + if (!node.addChild(p)) continue; + if (recursive) getDependencyManagement(p, recursive, level + 1, node); + } + catch (IOException ioe) { + node.removeChild(p); + // if (!p.isOptional()) throw ioe; + } + } + } + return node; + } + catch (IOException cause) { + IOException e = new IOException("failed to load dependency management in [" + pom + "]"); + ExceptionUtil.initCauseEL(e, cause); + throw e; + } + } + + public TreeNode getAllParentsAsTree() throws IOException { + return getParents(this, null); + } + + public List getAllParents() throws IOException { + TreeNode parents = getParents(this, null); + if (parents == null) return new ArrayList(); + return parents.asList(); + } + + private static TreeNode getParents(POM pom, TreeNode parents) throws IOException { + if (pom != null) { + POM parent = pom.getParent(); + if (parent != null) { + if (parents == null) parents = new TreeNode(parent); + else { + if (!parents.addChild(parent)) return parents; + } + getParents(parent, parents); + } + } + return parents; + } + + public Resource[] getJars() throws IOException { + List jars = new ArrayList<>(); + initXML(); + // current + if ("jar".equalsIgnoreCase(this.artifactExtension)) { + Resource r = getArtifact(); + if (r != null) { + jars.add(r); + } + } + + List dependencies = getAllDependencies(); + if (dependencies != null) { + for (POM p: dependencies) { + if ("jar".equalsIgnoreCase(p.artifactExtension)) { + Resource r = p.getArtifact(); + if (r != null) { + jars.add(r); + } + } + } + } + return jars.toArray(new Resource[jars.size()]); + } +} diff --git a/core/src/main/java/lucee/runtime/mvn/POMReader.java b/core/src/main/java/lucee/runtime/mvn/POMReader.java new file mode 100644 index 0000000000..5be28ffd64 --- /dev/null +++ b/core/src/main/java/lucee/runtime/mvn/POMReader.java @@ -0,0 +1,380 @@ +package lucee.runtime.mvn; + +import java.io.IOException; +import java.io.Reader; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +import lucee.print; +import lucee.commons.io.CharsetUtil; +import lucee.commons.io.IOUtil; +import lucee.commons.io.res.Resource; +import lucee.commons.io.res.ResourcesImpl; +import lucee.commons.lang.StringUtil; +import lucee.runtime.text.xml.XMLUtil; +import lucee.transformer.library.function.FunctionLibEntityResolver; +import lucee.transformer.library.function.FunctionLibException; + +public final class POMReader extends DefaultHandler { + + private XMLReader xmlReader; + private StringBuilder content = new StringBuilder(); + private int level = 0; + + private String modelVersion; + private String groupId; + private String artifactId; + private String version; + private String packaging; + private String name; + private String description; + private String url; + + private static boolean debug = false; + + private boolean insideProperties = false; + private Map properties = new LinkedHashMap<>(); + + private boolean insideDependencies = false; + private List dependencies = new ArrayList<>(); + private Dependency dependency; + + private boolean insideDependencyManagements = false; + private boolean insideDependencyManagement = false; + private List dependencyManagements = new ArrayList<>(); + private Dependency dependencyManagement; + + private boolean insideRepositories = false; + private List repositories = new ArrayList<>(); + private Repository repository; + + private boolean insideParent = false; + private Dependency parent; + + private Resource file; + + private static Map> instances = new ConcurrentHashMap<>(); + + public static POMReader getInstance(Resource file) throws IOException, SAXException { + Reference ref = instances.get(file.getAbsolutePath()); + POMReader pr; + if (ref != null) { + pr = ref.get(); + if (pr != null) return pr; + } + pr = new POMReader(file); + pr.read(); + instances.put(file.getAbsolutePath(), new SoftReference(pr)); + return pr; + } + + private POMReader(Resource file) { + this.file = file; + } + + public static void main(String[] args) throws Exception { + Resource file = ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Tmp3/org/lucee/lucee/6.1.0.235-RC/lucee-6.1.0.235-RC.pom"); + file = ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Tmp3/org/apache/apache/31/apache-31.pom"); + file = ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Tmp3/org/lucee/lucee/6.1.0.235-RC/lucee-6.1.0.235-RC.pom"); + + POMReader reader = new POMReader(file); + reader.read(); + + print.e("---- modelVersion ----"); + print.e(reader.getModelVersion()); + + print.e("---- groupId ----"); + print.e(reader.getGroupId()); + + print.e("---- artifactId ----"); + print.e(reader.getArtifactId()); + + print.e("---- version ----"); + print.e(reader.getVersion()); + + print.e("---- packaging ----"); + print.e(reader.getPackaging()); + + print.e("---- name ----"); + print.e(reader.getName()); + + print.e("---- description ----"); + print.e(reader.getDescription()); + + print.e("---- url ----"); + print.e(reader.getURL()); + + print.e("---- properties ----"); + print.e(reader.getProperties()); + + print.e("---- repositories ----"); + print.e(reader.getRepositories()); + + print.e("---- dependencies ----"); + print.e(reader.getDependencies()); + + print.e("---- dependencyManagement ----"); + print.e(reader.getDependencyManagements()); + + print.e("---- parent ----"); + print.e(reader.getParent()); + + } + + /* + * public void readOld() throws IOException, GeneralSecurityException, SAXException, PageException { + * + * HTTPResponse rsp = HTTPEngine4Impl.get(url, null, null, CONNECTION_TIMEOUT, true, null, null, + * null, null); if (rsp != null) { int sc = rsp.getStatusCode(); if (sc < 200 || sc >= 300) throw + * new IOException("unable to invoke [" + url + "], status code [" + sc + "]"); } else { throw new + * IOException("unable to invoke [" + url + "], no response."); } Header[] headers = + * rsp.getAllHeaders(); + * + * Reader r = null; try { init(new InputSource(r = IOUtil.getReader(rsp.getContentAsStream(), + * (Charset) null))); } finally { IOUtil.close(r); } + * + * for (Header h: headers) { if ("Last-Modified".equals(h.getName()) || "Date".equals(h.getName())) + * tmpMeta.put(h.getName(), DateCaster.toDateAdvanced(h.getValue(), null)); else + * tmpMeta.put(h.getName(), h.getValue()); } } + */ + + private void read() throws IOException, SAXException { + + Reader r = null; + try { + init(new InputSource(r = IOUtil.getReader(file.getInputStream(), (Charset) null))); + } + catch (SAXParseException saxe) { + if (saxe.getMessage().indexOf("oslash") != -1) { + IOUtil.closeEL(r); + r = null; + + String str = IOUtil.toString(file, (Charset) null); + // TODO PATCH make a better solution for that + str = StringUtil.replace(str, "ø", "ø", false);// (str, "oslash");// ø + IOUtil.write(file, str.getBytes(CharsetUtil.UTF8), false); + init(new InputSource(r = IOUtil.getReader(file.getInputStream(), (Charset) null))); + + } + else throw saxe; + + } + finally { + IOUtil.closeEL(r); + } + } + + /** + * Generelle Initialisierungsmetode der Konstruktoren. + * + * @param saxParser String Klassenpfad zum Sax Parser. + * @param is InputStream auf die TLD. + * @throws SAXException + * @throws IOException + * @throws FunctionLibException + */ + private void init(InputSource is) throws SAXException, IOException { + xmlReader = XMLUtil.createXMLReader(); + xmlReader.setContentHandler(this); + xmlReader.setErrorHandler(this); + xmlReader.setEntityResolver(new FunctionLibEntityResolver()); + xmlReader.parse(is); + + } + + @Override + public void startElement(String uri, String name, String qName, Attributes atts) { + level++; + + if (level == 2) { + if ("properties".equals(name)) insideProperties = true; + else if ("dependencies".equals(name)) insideDependencies = true; + else if ("parent".equals(name)) insideParent = true; + else if ("dependencyManagement".equals(name)) insideDependencyManagements = true; + else if ("repositories".equals(name)) insideRepositories = true; + } + else if (level == 3) { + if (insideDependencies && "dependency".equals(name)) dependency = new Dependency(); + else if (insideDependencyManagements && "dependencies".equals(name)) insideDependencyManagement = true; + else if (insideRepositories && "repository".equals(name)) repository = new Repository(); + } + else if (level == 4) { + if (insideDependencyManagement && "dependency".equals(name)) dependencyManagement = new Dependency(); + } + + } + + /* + * ,"modelVersion":xml.XmlRoot.modelVersion.XmlText ,"groupId":xml.XmlRoot.groupId.XmlText + * ,"artifactId":xml.XmlRoot.artifactId.XmlText ,"version":xml.XmlRoot.version.XmlText + * ,"name":xml.XmlRoot.name.XmlText ,"description":xml.XmlRoot.description.XmlText + * ,"groupId":xml.XmlRoot.groupId.XmlText + */ + @Override + public void endElement(String uri, String name, String qName) { + if (level == 2) { + if ("properties".equals(name)) insideProperties = false; + else if ("dependencies".equals(name)) insideDependencies = false; + else if ("repositories".equals(name)) insideRepositories = false; + else if ("dependencyManagement".equals(name)) insideDependencyManagements = false; + else if ("parent".equals(name)) insideParent = false; + else if ("groupId".equals(name)) this.groupId = content.toString().trim(); + else if ("artifactId".equals(name)) this.artifactId = content.toString().trim(); + else if ("version".equals(name)) this.version = content.toString().trim(); + else if ("packaging".equals(name)) this.packaging = content.toString().trim(); + else if ("name".equals(name)) this.name = content.toString().trim(); + else if ("description".equals(name)) this.description = content.toString().trim(); + else if ("url".equals(name)) this.url = content.toString().trim(); + else if ("modelVersion".equals(name)) this.modelVersion = content.toString().trim(); + } + else if (level == 3) { + if (insideProperties) properties.put(name.trim(), content.toString().trim()); + else if ("dependencies".equals(name)) insideDependencyManagement = false; + else if ("dependency".equals(name) && dependency != null) { + dependencies.add(dependency); + dependency = null; + } + else if ("repository".equals(name) && repository != null) { + repositories.add(repository); + repository = null; + } + else if (insideParent) { + if (parent == null) parent = new Dependency(); + if ("groupId".equals(name)) parent.groupId = content.toString().trim(); + else if ("artifactId".equals(name)) parent.artifactId = content.toString().trim(); + else if ("version".equals(name)) parent.version = content.toString().trim(); + else if ("scope".equals(name)) parent.scope = content.toString().trim(); + else if ("optional".equals(name)) parent.optional = content.toString().trim(); + else if (debug) print.e("!!!!!!! ==>" + name + ":" + content.toString().trim()); + } + } + else if (level == 4) { + if (insideDependencyManagements && insideDependencyManagement && "dependency".equals(name)) { + dependencyManagements.add(dependencyManagement); + dependencyManagement = null; + } + else if (dependency != null) { + if ("groupId".equals(name)) dependency.groupId = content.toString().trim(); + else if ("artifactId".equals(name)) dependency.artifactId = content.toString().trim(); + else if ("version".equals(name)) dependency.version = content.toString().trim(); + else if ("scope".equals(name)) dependency.scope = content.toString().trim(); + else if ("optional".equals(name)) dependency.optional = content.toString().trim(); + else if (debug) print.e("!!!!!!! ==>" + name + ":" + content.toString().trim()); + } + else if (insideRepositories && repository != null) { + if ("id".equals(name)) repository.id = content.toString().trim(); + else if ("name".equals(name)) repository.name = content.toString().trim(); + else if ("url".equals(name)) repository.url = content.toString().trim(); + else if (debug) print.e("???? ==>" + name + ":" + content.toString().trim()); + } + } + else if (level == 5) { + if (insideDependencyManagements && insideDependencyManagement && dependencyManagement != null) { + if ("groupId".equals(name)) dependencyManagement.groupId = content.toString().trim(); + else if ("artifactId".equals(name)) dependencyManagement.artifactId = content.toString().trim(); + else if ("version".equals(name)) dependencyManagement.version = content.toString().trim(); + else if ("scope".equals(name)) dependencyManagement.scope = content.toString().trim(); + else if ("optional".equals(name)) dependencyManagement.optional = content.toString().trim(); + else if (debug) print.e("xxxxxx ==>" + name + ":" + content.toString().trim()); + } + } + + content.delete(0, content.length()); + level--; + + } + + @Override + public void characters(char ch[], int start, int length) { + content.append(ch, start, length); + } + + public String getModelVersion() { + return modelVersion; + } + + public String getGroupId() { + return groupId; + } + + public String getArtifactId() { + return artifactId; + } + + public String getVersion() { + return version; + } + + public String getPackaging() { + return packaging; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getURL() { + return url; + } + + public Map getProperties() { + return properties; + } + + public List getDependencies() { + return dependencies; + } + + public List getDependencyManagements() { + return dependencyManagements; + } + + public List getRepositories() { + return repositories; + } + + public Dependency getParent() { + return parent; + } + + public static class Dependency { + public String groupId; + public String artifactId; + public String version; + public String scope; + public String optional; + + @Override + public String toString() { + return "groupId:" + groupId + ";artifactId:" + artifactId + ";version:" + version + ";scope:" + scope + ";optional:" + optional; + } + } + + public static class Repository { + public String id; + public String name; + public String url; + + @Override + public String toString() { + return "id:" + id + ";name:" + name + ";url:" + url; + } + } +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/mvn/Repository.java b/core/src/main/java/lucee/runtime/mvn/Repository.java new file mode 100644 index 0000000000..d9b88a27a5 --- /dev/null +++ b/core/src/main/java/lucee/runtime/mvn/Repository.java @@ -0,0 +1,30 @@ +package lucee.runtime.mvn; + +public class Repository { + private String id; + private String name; + private String url; + + public Repository(String id, String name, String url) { + this.id = id; + this.name = name; + this.url = url.endsWith("/") ? url : (url + "/"); + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + @Override + public String toString() { + return url; + } +} diff --git a/core/src/main/java/lucee/runtime/mvn/Test.java b/core/src/main/java/lucee/runtime/mvn/Test.java new file mode 100644 index 0000000000..a1a1c00889 --- /dev/null +++ b/core/src/main/java/lucee/runtime/mvn/Test.java @@ -0,0 +1,136 @@ +package lucee.runtime.mvn; + +import lucee.print; +import lucee.commons.io.res.Resource; +import lucee.commons.io.res.ResourcesImpl; +import lucee.runtime.mvn.MavenUtil.GAVSO; + +public class Test { + public static void main(String[] args) throws Exception { + Resource dir = ResourcesImpl.getFileResourceProvider().getResource("/Users/mic/Tmp3"); + GAVSO[] arr = new GAVSO[] { + + new GAVSO("org.apache.maven", "maven-parent", "40"), + + new GAVSO("org.apache", "apache", "30"), + + new GAVSO("com.puppycrawl.tools", "checkstyle", "7.8"), + + new GAVSO("org.apache.commons", "commons-lang3", "3.12.0"), + + new GAVSO("org.apache.httpcomponents", "httpclient", "4.5.14"), + + new GAVSO("org.apache.httpcomponents", "httpcomponents-client", "4.5.14"), + + new GAVSO("org.apache.commons", "commons-pool2", "2.12.0"), + + new GAVSO("org.apache.commons", "commons-parent", "62"), + + new GAVSO("org.slf4j", "slf4j-api", "1.6.1"), + + new GAVSO("net.bytebuddy", "byte-buddy", "1.14.17"), + + new GAVSO("net.bytebuddy", "byte-buddy-parent", "1.14.17"), + + new GAVSO("commons-beanutils", "commons-beanutils", "1.9.4"), + + new GAVSO("org.apache.maven.resolver", "maven-resolver-impl", "2.0.0"), + + new GAVSO("jakarta.enterprise", "jakarta.enterprise.cdi-api", "4.0.1"), + + new GAVSO("org.lucee", "lucee", "6.1.0.235-RC") + + }; + + arr = new GAVSO[] { new GAVSO("commons-beanutils", "commons-beanutils", "1.9.4") + // new GAVSO("org.apache.commons", "commons-jexl3", "3.4.0") + + }; + + /* + * new Artifact("org.hibernate.orm", "hibernate-core", "6.5.2.Final"), new Artifact("com.amazonaws", + * "aws-java-sdk-s3", "1.12.756"), + * + * new Artifact("org.apache.maven", "maven-plugin-api", "3.9.8"), + * + * new Artifact("org.apache.maven", "maven-core", "3.9.8"), // new Artifact(,,),new Artifact(,,),new + * Artifact(,,),new Artifact(,,), xception in thread "main" java.io.IOException: cannot resolve + * [${sisuVersion}] for [groupID:org.apache.maven;artifactId:maven-parent;version:40], available + * properties are [version.apache-rat-plugin, version.maven-help-plugin, + * version.maven-source-plugin, distMgmtReleasesUrl, version.maven-plugin-tools, + * distMgmtSnapshotsUrl, version.maven-ear-plugin, version.maven-deploy-plugin, surefire.version, + * sourceReleaseAssemblyDescriptor, organization.logo, version.maven-compiler-plugin, twitter, + * version.maven-shade-plugin, version.maven-gpg-plugin, project.build.sourceEncoding, + * version.maven-enforcer-plugin, version.maven-invoker-plugin, distMgmtSnapshotsName, + * assembly.tarLongFileMode, version.maven-scm-publish-plugin, + * version.maven-remote-resources-plugin, version.maven-war-plugin, version.apache-resource-bundles, + * distMgmtReleasesName, minimalJavaBuildVersion, maven.plugin.tools.version, + * version.checksum-maven-plugin, version.maven-fluido-skin, maven.compiler.source, + * version.maven-assembly-plugin, version.maven-resources-plugin, minimalMavenBuildVersion, + * project.reporting.outputEncoding, version.maven-jar-plugin, version.maven-scm-plugin, + * maven.compiler.target, version.maven-dependency-plugin, version.maven-clean-plugin, + * version.maven-javadoc-plugin, version.maven-site-plugin, project.build.outputTimestamp, + * version.maven-release-plugin, gpg.useagent, version.maven-antrun-plugin, version.maven-surefire, + * version.maven-install-plugin, version.maven-project-info-reports-plugin] + * + * }; + */ + /* + * for (Artifact a: examples) { print.e("------------ " + a); print.e(maven.download(a.groupId, + * a.artifactId, a.version, true, false)); } + */ + long start = System.currentTimeMillis(); + for (GAVSO gav: arr) { + POM pom = POM.getInstance(dir, gav.g, gav.a, gav.v, POM.SCOPE_NOT_TEST, null); + print.e("=========================================="); + print.e(pom.getName()); + print.e(pom); + print.e("=========================================="); + + // print.e("--- properties ---"); + // print.e(pom.getAllParentsAsTree()); + // print.e(pom.getProperties()); + print.e("--- packaging ---"); + print.e(pom.getPackaging()); + + print.e("--- path ---"); + print.e(pom.getPath()); + print.e("--- hash ---"); + print.e(pom.hash()); + + print.e("--- artifact ---"); + print.e(pom.getArtifact()); + + print.e("--- parents ---"); + // print.e(pom.getAllParentsAsTree()); + print.e(pom.getAllParents()); + + print.e("--- repositories ---"); + // print.e(pom.getAllParentsAsTree()); + print.e(pom.getRepositories()); + + print.e("--- dependencies management ---"); + print.e(pom.getDependencyManagement()); + + print.e("--- all dependencies management ---"); + print.e(pom.getAllDependencyManagement()); + + print.e("--- dependencies ---"); + // print.e(getDependenciesAsTrees(pom, true)); + print.e(pom.getAllDependencies()); + + print.e("--- jars ---"); + // print.e(getDependenciesAsTrees(pom, true)); + print.e(pom.getJars()); + print.e(System.currentTimeMillis() - start); + + // pom.getScope(); + // print.e(pom.getDependencyManagement()); + + // print.e(maven.getDependencies(groupId, artifactId, version, true, false, true)); + + break; + } + } + +} diff --git a/core/src/main/java/lucee/runtime/net/http/HttpUtil.java b/core/src/main/java/lucee/runtime/net/http/HttpUtil.java index 6f0b4d2e7a..fb7eedb267 100644 --- a/core/src/main/java/lucee/runtime/net/http/HttpUtil.java +++ b/core/src/main/java/lucee/runtime/net/http/HttpUtil.java @@ -86,7 +86,7 @@ public static Pair[] cloneParameters(PageContext pc, HttpServlet while (e.hasMoreElements()) { name = (String) e.nextElement(); - values = req.getParameterValues(name); + values = req instanceof HTTPServletRequestWrap ? ((HTTPServletRequestWrap) req).getParameterValues(pc, name) : req.getParameterValues(name); if (values == null && ReqRspUtil.needEncoding(name, false)) values = req.getParameterValues(ReqRspUtil.encode(name, ReqRspUtil.getCharacterEncoding(null, req))); if (values == null) { if (pc != null && ReqRspUtil.identical(pc.getHttpServletRequest(), req)) { diff --git a/core/src/main/java/lucee/runtime/op/Caster.java b/core/src/main/java/lucee/runtime/op/Caster.java index 99b98fabff..eb23b85ac2 100755 --- a/core/src/main/java/lucee/runtime/op/Caster.java +++ b/core/src/main/java/lucee/runtime/op/Caster.java @@ -46,6 +46,7 @@ import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -2114,7 +2115,7 @@ public static int stringToBooleanValueEL(String str) { * @throws PageException */ public static String toString(Object o) throws PageException { - if (o instanceof String) return (String) o; + if (o instanceof CharSequence) return o.toString(); else if (o instanceof Number) return toString(((Number) o)); else if (o instanceof Boolean) return toString(((Boolean) o).booleanValue()); else if (o instanceof Castable) return ((Castable) o).castToString(); @@ -2227,7 +2228,7 @@ public static String toString(Object o, String defaultValue) { } public static String toString(Object o, boolean executeDefaultToStringMethod, String defaultValue) { - if (o instanceof String) return (String) o; + if (o instanceof CharSequence) return o.toString(); else if (o instanceof Boolean) return toString(((Boolean) o).booleanValue()); else if (o instanceof Number) return toString(((Number) o)); else if (o instanceof Castable) return ((Castable) o).castToString(defaultValue); @@ -3651,7 +3652,7 @@ public static String toClassName(Class clazz) { return clazz.getName(); } - public static Class cfTypeToClass(String type) throws PageException { + public static Class cfTypeToClass(PageContext pc, String type) throws PageException { // TODO weitere typen siehe bytecode.cast.Cast type = type.trim(); @@ -3759,14 +3760,14 @@ else if (lcType.equals("struct")) { } // array if (type.endsWith("[]")) { - Class clazz = cfTypeToClass(type.substring(0, type.length() - 2)); + Class clazz = cfTypeToClass(pc, type.substring(0, type.length() - 2)); clazz = ClassUtil.toArrayClass(clazz); return clazz; } // check for argument Class clazz; try { - clazz = otherTypeToClass(type); + clazz = otherTypeToClass(pc, type); } catch (ClassException e) { throw Caster.toPageException(e); @@ -3774,8 +3775,8 @@ else if (lcType.equals("struct")) { return clazz; } - private static Class otherTypeToClass(String type) throws PageException, ClassException { - PageContext pc = ThreadLocalPageContext.get(); + private static Class otherTypeToClass(PageContext pc, String type) throws PageException, ClassException { + pc = ThreadLocalPageContext.get(pc); PageException pe = null; // try to load as cfc if (pc != null) { @@ -3789,7 +3790,7 @@ private static Class otherTypeToClass(String type) throws PageException, Clas } // try to load as class try { - return ClassUtil.loadClass(type); + return ClassUtil.loadClass(pc, type); } catch (ClassException ce) { if (pe != null) throw pe; @@ -4159,7 +4160,7 @@ private static Object _castTo(PageContext pc, String strType, Object o) throws P if (comp.instanceOf(strType)) return o; try { - Class trgClass = ClassUtil.loadClass(strType); + Class trgClass = ClassUtil.loadClass(pc, strType); if (trgClass.isInterface()) { return Reflector.componentToClass(pc, comp, trgClass); } @@ -4172,7 +4173,7 @@ private static Object _castTo(PageContext pc, String strType, Object o) throws P } if (o instanceof UDF) { try { - Class trgClass = ClassUtil.loadClass(strType); + Class trgClass = ClassUtil.loadClass(pc, strType); if (trgClass.isInterface()) { return Reflector.udfToClass(pc, (UDF) o, trgClass); } @@ -5330,4 +5331,21 @@ public static Number negate(Number n) { if (n instanceof BigDecimal) return ((BigDecimal) n).negate(); return Double.valueOf(-n.doubleValue()); } + + public static Map toStringMap(Struct sct, Map defaultValue) { + if (sct == null) return defaultValue; + try { + Map rtn = new LinkedHashMap<>(); + Iterator> it = sct.entryIterator(); + Entry entry; + while (it.hasNext()) { + entry = it.next(); + rtn.put(entry.getKey().getString(), Caster.toString(entry.getValue())); + } + return rtn; + } + catch (Exception e) { + return defaultValue; + } + } } diff --git a/core/src/main/java/lucee/runtime/op/Decision.java b/core/src/main/java/lucee/runtime/op/Decision.java index fa0f6390ae..3f5836d8e1 100755 --- a/core/src/main/java/lucee/runtime/op/Decision.java +++ b/core/src/main/java/lucee/runtime/op/Decision.java @@ -971,7 +971,7 @@ public static boolean isZipCode(Object value) { } public static boolean isString(Object o) { - if (o instanceof String) return true; + if (o instanceof CharSequence) return true; else if (o instanceof Boolean) return true; else if (o instanceof Number) return true; else if (o instanceof Date) return true; diff --git a/core/src/main/java/lucee/runtime/osgi/EnvClassLoader.java b/core/src/main/java/lucee/runtime/osgi/EnvClassLoader.java index 7ea40adf9d..f1624693fd 100644 --- a/core/src/main/java/lucee/runtime/osgi/EnvClassLoader.java +++ b/core/src/main/java/lucee/runtime/osgi/EnvClassLoader.java @@ -154,7 +154,6 @@ protected synchronized Class loadClass(String name, boolean resolve) throws C } private synchronized Object load(String name, short type, boolean doLog, List listContext, boolean useCache) { - double start = SystemUtil.millis(); StringBuilder id = new StringBuilder(name).append(';').append(type).append(';'); String _id = id.toString(); diff --git a/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java b/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java index c5927019ca..95da845433 100644 --- a/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java +++ b/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java @@ -837,7 +837,6 @@ private static String parentBundleText(String parentBundle) { private static Resource downloadBundle(CFMLEngineFactory factory, final String symbolicName, String symbolicVersion, Identification id) throws IOException, BundleException { resetJarsFromBundleDirectory(factory); - String strDownload = SystemUtil.getSystemPropOrEnvVar("lucee.enable.bundle.download", null); if (!Caster.toBooleanValue(strDownload, true)) { boolean printExceptions = Caster.toBooleanValue(SystemUtil.getSystemPropOrEnvVar("lucee.cli.printExceptions", null), false); @@ -859,7 +858,6 @@ private static Resource downloadBundle(CFMLEngineFactory factory, final String s final URL updateUrl = BundleProvider.getInstance().getBundleAsURL(new BundleDefinition(symbolicName, symbolicVersion), true); log(Logger.LOG_INFO, "Downloading bundle [" + symbolicName + ":" + symbolicVersion + "] from [" + updateUrl + "]"); - int code; HttpURLConnection conn; try { @@ -927,12 +925,12 @@ private static Resource downloadBundle(CFMLEngineFactory factory, final String s temp.delete(); } } - else { - Resource jar = jarDir.getRealResource(symbolicName + "-" + symbolicVersion + ".jar"); - IOUtil.copy((InputStream) conn.getContent(), jar, true); - conn.disconnect(); - return jar; - } + + Resource jar = jarDir.getRealResource(symbolicName + "-" + symbolicVersion + ".jar"); + IOUtil.copy((InputStream) conn.getContent(), jar, true); + conn.disconnect(); + return jar; + } /** @@ -1097,6 +1095,7 @@ private static BundleFile _getBundleFile(CFMLEngineFactory factory, BundleRange if (mbf != null) { return improveFileName(bd, mbf); } + List children = listFiles(dir, addional, JAR_EXT_FILTER); // now we check all jar files @@ -1129,6 +1128,21 @@ private static BundleFile _getBundleFile(CFMLEngineFactory factory, BundleRange } } } + /* + * List children = listFiles(dir, addional, JAR_EXT_FILTER); + * + * // now we check all jar files { + * + * // now we check by Manifest comparsion, name not necessary reflect the correct bundle info + * BundleFile bf; for (boolean checkBundleRange: checkBundleRanges) { mbf = null; for (Resource + * child: children) { if (checkBundleRange && !new Filter(bundleRange).accept(child.getName())) + * continue; match = child; bf = BundleFile.getInstance(child); if (bf.isBundle()) { if + * (bf.getSymbolicName().equals(bundleRange.getName())) { if (bundleRange.matches(bf)) { if (mbf == + * null || OSGiUtil.isNewerThan(bf.getVersion(), mbf.getVersion())) mbf = bf; } else { if + * (versionsFound != null) { if (versionsFound.length() > 0) versionsFound.append(", "); + * versionsFound.append(bf.getVersionAsString()); } } } } } if (mbf != null) { return + * improveFileName(factory.getBundleDirectory(), mbf); } } } + */ } catch (Exception e) { @@ -1154,6 +1168,20 @@ private static BundleFile _getBundleFile(CFMLEngineFactory factory, BundleRange return null; } + public static void correctBundles(CFMLEngineFactory factory) throws IOException, BundleException { + BundleFile bf; + String expName; + for (Resource child: ResourceUtil.toResource(factory.getBundleDirectory()).listResources(JAR_EXT_FILTER)) { + bf = BundleFile.getInstance(child); + if (bf.isBundle()) { + expName = bf.getSymbolicName() + "-" + bf.getVersionAsString() + ".jar"; + if (!child.getName().equals(expName)) { + child.moveTo(child.getParentResource().getRealResource(expName)); + } + } + } + } + /** * rename file to match the Manifest information * diff --git a/core/src/main/java/lucee/runtime/reflection/pairs/MethodInstance.java b/core/src/main/java/lucee/runtime/reflection/pairs/MethodInstance.java index 71045d7934..c26aaa5aa2 100644 --- a/core/src/main/java/lucee/runtime/reflection/pairs/MethodInstance.java +++ b/core/src/main/java/lucee/runtime/reflection/pairs/MethodInstance.java @@ -118,8 +118,9 @@ private Pair getResult() throws PageException { try { result = DynamicInvoker.getInstance(null).createInstance(clazz, methodName, args); } - catch (Exception e) { - throw Caster.toPageException(e); + catch (Throwable t) { + ExceptionUtil.rethrowIfNecessary(t); + throw Caster.toPageException(t); } } return result; diff --git a/core/src/main/java/lucee/runtime/rest/RestRequestListener.java b/core/src/main/java/lucee/runtime/rest/RestRequestListener.java index f28df52424..d6e2f703ea 100644 --- a/core/src/main/java/lucee/runtime/rest/RestRequestListener.java +++ b/core/src/main/java/lucee/runtime/rest/RestRequestListener.java @@ -31,6 +31,7 @@ import lucee.runtime.listener.RequestListener; import lucee.runtime.type.Struct; import lucee.runtime.type.util.ListUtil; +import lucee.runtime.util.PageContextUtil; public class RestRequestListener implements RequestListener { @@ -74,7 +75,7 @@ public PageSource execute(PageContext pc, PageSource requestedPage) throws PageE else addDetail = " in the matching mapping [" + mapping.getVirtual() + "] at [" + mapping.getPhysical().getAbsolutePath() + "], available targets are [" + ListUtil.listToListEL(sources, ", ") + "]"; - if (pc.getConfig().debug()) { + if (PageContextUtil.show(pc)) { RestUtil.setStatus(pc, 404, HTMLEntities.escapeHTML(msg + addDetail)); } diff --git a/core/src/main/java/lucee/runtime/spooler/SpoolerEngineImpl.java b/core/src/main/java/lucee/runtime/spooler/SpoolerEngineImpl.java index 69613b98af..377c16aa30 100755 --- a/core/src/main/java/lucee/runtime/spooler/SpoolerEngineImpl.java +++ b/core/src/main/java/lucee/runtime/spooler/SpoolerEngineImpl.java @@ -568,11 +568,11 @@ public void remove(ConfigWeb config, SpoolerTask task) { } public void removeAll() { - ResourceUtil.removeChildrenEL(openDirectory); - ResourceUtil.removeChildrenEL(closedDirectory); + ResourceUtil.removeChildrenEL(openDirectory, false); + ResourceUtil.removeChildrenEL(closedDirectory, false); SystemUtil.wait(this, 100); - ResourceUtil.removeChildrenEL(openDirectory); - ResourceUtil.removeChildrenEL(closedDirectory); + ResourceUtil.removeChildrenEL(openDirectory, false); + ResourceUtil.removeChildrenEL(closedDirectory, false); } public int adds() { diff --git a/core/src/main/java/lucee/runtime/sql/QueryPartitions.java b/core/src/main/java/lucee/runtime/sql/QueryPartitions.java index 75a70692ff..d3f0208ebb 100644 --- a/core/src/main/java/lucee/runtime/sql/QueryPartitions.java +++ b/core/src/main/java/lucee/runtime/sql/QueryPartitions.java @@ -73,14 +73,15 @@ public class QueryPartitions { * @param qoQ * @throws PageException */ - public QueryPartitions(SQL sql, Expression[] columns, Expression[] groupbys, QueryImpl target, Set additionalColumns, QoQ qoQ) throws PageException { + public QueryPartitions(SQL sql, Expression[] columns, Expression[] groupbys, QueryImpl target, Set additionalColumns, QoQ qoQ, boolean hasAggregateSelect) + throws PageException { this.sql = sql; this.qoQ = qoQ; this.columns = columns; this.groupbys = groupbys; // This happens when using distinct with no group by // Just assume we're grouping on the entire select list - if (this.groupbys.length == 0) { + if (this.groupbys.length == 0 && !hasAggregateSelect) { ArrayList temp = new ArrayList(); for (Expression col: columns) { if (!(col instanceof OperationAggregate)) { @@ -118,11 +119,12 @@ public void addEmptyPartition(QueryImpl source, QueryImpl target) throws PageExc * Call this to add a single row to the proper partition finaizedColumnVals is true when all data in * the source Query is fully realized and there are no expressions left to evaluate * - * @param pc PageContext - * @param source Source query to get data from - * @param row Row to get data from + * @param pc PageContext + * @param source Source query to get data from + * @param row Row to get data from * @param finalizedColumnVals If we're adding finalized data, just copy it across. Easy. This - * applies when distincting a result set after it's already been processed + * applies when distincting a result set after it's already been + * processed * @throws PageException */ public void addRow(PageContext pc, QueryImpl source, int row, boolean finalizedColumnVals) throws PageException { @@ -181,11 +183,13 @@ else if (columns[cell] instanceof ColumnExpression) { /** * Generate a unique string that represents the column data being grouped on * - * @param pc PageContext - * @param source QueryImpl to get data from. Note, operations have not yet been processed - * @param row Row to get data from + * @param pc PageContext + * @param source QueryImpl to get data from. Note, operations have not yet been + * processed + * @param row Row to get data from * @param finalizedColumnVals If we're adding finalized data, just copy it across. Easy. This - * applies when distincting a result set after it's already been processed + * applies when distincting a result set after it's already been + * processed * @return unique string * @throws PageException */ @@ -267,10 +271,11 @@ public Query[] getPartitionArray() { * Create new Query for a partition. Needs to have all ColumnExpressions in the final select as well * as any additional columns required for operation expressions * - * @param target Query for target data (for column refernces) - * @param source source query we're getting data from + * @param target Query for target data (for column refernces) + * @param source source query we're getting data from * @param finalizedColumnVals If we're adding finalized data, just copy it across. Easy. This - * applies when distincting a result set after it's already been processed + * applies when distincting a result set after it's already been + * processed * @return Empty Query with all the needed columns * @throws PageException */ diff --git a/core/src/main/java/lucee/runtime/tag/Admin.java b/core/src/main/java/lucee/runtime/tag/Admin.java index 1d5c648a75..a4d76c8bb5 100755 --- a/core/src/main/java/lucee/runtime/tag/Admin.java +++ b/core/src/main/java/lucee/runtime/tag/Admin.java @@ -192,6 +192,7 @@ import lucee.runtime.type.util.KeyConstants; import lucee.runtime.type.util.ListUtil; import lucee.runtime.type.util.UDFUtil; +import lucee.runtime.util.PageContextUtil; import lucee.transformer.library.ClassDefinitionImpl; import lucee.transformer.library.function.FunctionLib; import lucee.transformer.library.tag.TagLib; @@ -1503,7 +1504,7 @@ private void doGetRegex() throws PageException { * */ private void doGetDebugData() throws PageException { - pageContext.setVariable(getString("admin", action, "returnVariable"), pageContext.getConfig().debug() ? pageContext.getDebugger().getDebuggingData(pageContext) : null); + pageContext.setVariable(getString("admin", action, "returnVariable"), PageContextUtil.debug(pageContext) ? pageContext.getDebugger().getDebuggingData(pageContext) : null); } private void doGetLoggedDebugData() throws PageException { @@ -1860,8 +1861,7 @@ private void doUpdateRegex() throws PageException { private void doUpdateJavaCFX() throws PageException { String name = getString("admin", action, "name"); if (StringUtil.startsWithIgnoreCase(name, "cfx_")) name = name.substring(4); - lucee.runtime.db.ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + lucee.runtime.db.ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateJavaCFX(name, cd); store(); adminSync.broadcast(attributes, config); @@ -1869,8 +1869,7 @@ private void doUpdateJavaCFX() throws PageException { private void doVerifyJavaCFX() throws PageException { String name = getString("admin", action, "name"); - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.verifyJavaCFX(name, cd); } @@ -2199,8 +2198,7 @@ private void doGetResourceProviders() throws PageException { } private void doUpdateAdminSyncClass() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateAdminSyncClass(cd); store(); } @@ -2298,7 +2296,7 @@ private void getLoaderInfo() throws PageException { Struct sct = new StructImpl(); sct.set("LoaderVersion", VersionInfo.getIntVersion().toString()); sct.set("LuceeVersion", pageContext.getConfig().getFactory().getEngine().getInfo().getVersion().toString()); - sct.set("LoaderPath", ClassUtil.getSourcePathForClass("lucee.loader.servlet.CFMLServlet", "")); + sct.set("LoaderPath", ClassUtil.getSourcePathForClass(pageContext, "lucee.loader.servlet.CFMLServlet", "")); pageContext.setVariable(getString("admin", action, "returnVariable"), sct); } catch (Exception e) { @@ -2604,13 +2602,15 @@ private void doGetJars() throws PageException { } private void doUpdateJDBCDriver() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "classname"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); String label = getString("admin", action, "label"); String id = getString("id", null); + String dsn = getString("connectionString", null); + if (dsn == null) getString("dsn", null); - admin.updateJDBCDriver(label, id, cd); + admin.updateJDBCDriver(label, id, cd, dsn); store(); adminSync.broadcast(attributes, config); } @@ -2629,7 +2629,7 @@ private void doUpdateDatasource() throws PageException { String cn = getString("admin", action, "classname"); if ("com.microsoft.jdbc.sqlserver.SQLServerDriver".equals(cn)) { - cn = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; + attributes.set(KeyConstants._classname, "com.microsoft.sqlserver.jdbc.SQLServerDriver"); } String tmp = getString("admin", action, "newName"); @@ -2641,7 +2641,7 @@ private void doUpdateDatasource() throws PageException { + "]: only alphanumeric characters, underscores (_), and hyphens (-) are valid. Please ensure the name conforms to these formats."); } - ClassDefinition cd = new ClassDefinitionImpl(cn, getString("bundleName", null), getString("bundleVersion", null), config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); // customParameterSyntax // Struct sct = getStruct("customParameterSyntax", null); @@ -2699,8 +2699,7 @@ private void doUpdateDatasource() throws PageException { } private void doUpdateCacheConnection() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateCacheConnection(getString("admin", action, "name"), cd, toCacheConstant("default"), getStruct("admin", action, "custom"), getBoolV("readOnly", false), getBoolV("storage", false) @@ -2735,8 +2734,7 @@ private void doUpdateGatewayEntry() throws PageException { * +" ] listener CFC"); } */ - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateGatewayEntry(getString("admin", action, "id"), cd, getString("admin", action, "cfcPath"), getString("admin", action, "listenerCfcPath"), startup, getStruct("admin", action, "custom"), getBoolV("readOnly", false) @@ -2811,8 +2809,7 @@ private void doRemoveResourceProvider() throws PageException { private void doUpdateResourceProvider() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); String scheme = getString("admin", action, "scheme"); @@ -2830,8 +2827,7 @@ private void doUpdateResourceProvider() throws PageException { } private void doUpdateDefaultResourceProvider() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); String arguments = getString("admin", action, "arguments"); @@ -2859,8 +2855,7 @@ private void _doVerifyMailServer(String host, int port, String user, String pass * */ private void doVerifyDatasource() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(Caster.toString(attributes.get("classname", null), null), Caster.toString(attributes.get("bundleName", null), null), - Caster.toString(attributes.get("bundleVersion", null), null), config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); String connStr = (String) attributes.get("connStr", null); if (StringUtil.isEmpty(connStr)) connStr = (String) attributes.get("dsn", null); @@ -4202,8 +4197,7 @@ private void doUpdateAdminMode() throws PageException { } private void doUpdateMonitor() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateMonitor(cd, getString("admin", "updateMonitor", "monitorType"), getString("admin", "updateMonitor", "name"), getBool("admin", "updateMonitor", "logEnabled")); store(); @@ -4211,24 +4205,21 @@ private void doUpdateMonitor() throws PageException { } private void doUpdateORMEngine() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateORMEngine(cd); store(); adminSync.broadcast(attributes, config); } private void doUpdateCacheHandler() throws PageException { - ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateCacheHandler(getString("admin", "updateCacheHandler", "id"), cd); store(); adminSync.broadcast(attributes, config); } private void doUpdateExecutionLog() throws PageException { - lucee.runtime.db.ClassDefinition cd = new ClassDefinitionImpl(getString("admin", action, "class"), getString("bundleName", null), getString("bundleVersion", null), - config.getIdentification()); + lucee.runtime.db.ClassDefinition cd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, null, true, config.getIdentification()); admin.updateExecutionLog(cd, getStruct("admin", "updateExecutionLog", "arguments"), getBool("admin", "updateExecutionLog", "enabled")); store(); adminSync.broadcast(attributes, config); @@ -4652,18 +4643,12 @@ private void doUpdateLogSettings() throws PageException { LogEngine eng = config.getLogEngine(); // appender - String className = getString("admin", action, "appenderClass", true); - String bundleName = getString("appenderBundleName", null); - String bundleVersion = getString("appenderBundleVersion", null); - ClassDefinition acd = StringUtil.isEmpty(bundleName) ? eng.appenderClassDefintion(className) - : new ClassDefinitionImpl(className, bundleName, bundleVersion, config.getIdentification()); + ClassDefinition acd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, "appender", true, config.getIdentification()); + if (!acd.isBundle()) acd = eng.appenderClassDefintion(acd.getClassName()); // layout - className = getString("admin", action, "layoutClass", true); - bundleName = getString("layoutBundleName", null); - bundleVersion = getString("layoutBundleVersion", null); - ClassDefinition lcd = StringUtil.isEmpty(bundleName) ? eng.layoutClassDefintion(className) - : new ClassDefinitionImpl(className, bundleName, bundleVersion, config.getIdentification()); + ClassDefinition lcd = ClassDefinitionImpl.toClassDefinitionImpl(attributes, "layout", true, config.getIdentification()); + if (!lcd.isBundle()) lcd = eng.layoutClassDefintion(lcd.getClassName()); admin.updateLogSettings(getString("admin", "UpdateLogSettings", "name", true), l, acd, Caster.toStruct(getObject("admin", "UpdateLogSettings", "appenderArgs")), lcd, Caster.toStruct(getObject("admin", "UpdateLogSettings", "layoutArgs"))); diff --git a/core/src/main/java/lucee/runtime/tag/Application.java b/core/src/main/java/lucee/runtime/tag/Application.java index 4e943a2de1..4355686d01 100644 --- a/core/src/main/java/lucee/runtime/tag/Application.java +++ b/core/src/main/java/lucee/runtime/tag/Application.java @@ -981,7 +981,7 @@ else if ((sct = Caster.toStruct(e.getValue(), null)) != null) { if (ac instanceof ApplicationContextSupport) { ApplicationContextSupport appContextSup = ((ApplicationContextSupport) ac); - if (javaSettings != null) appContextSup.setJavaSettings(JavaSettingsImpl.newInstance(new JavaSettingsImpl(), javaSettings)); + if (javaSettings != null) appContextSup.setJavaSettings(JavaSettingsImpl.getInstance(pageContext.getConfig(), javaSettings, null)); if (xmlFeatures != null) appContextSup.setXmlFeatures(xmlFeatures); if (searchQueries != null) appContextSup.setAllowImplicidQueryCall(searchQueries.booleanValue()); if (regex != null) appContextSup.setRegex(regex); diff --git a/core/src/main/java/lucee/runtime/tag/Cache.java b/core/src/main/java/lucee/runtime/tag/Cache.java index c73b50f5f2..555078a58c 100755 --- a/core/src/main/java/lucee/runtime/tag/Cache.java +++ b/core/src/main/java/lucee/runtime/tag/Cache.java @@ -50,6 +50,7 @@ import lucee.runtime.type.dt.DateTimeImpl; import lucee.runtime.type.dt.TimeSpan; import lucee.runtime.type.dt.TimeSpanImpl; +import lucee.runtime.util.PageContextUtil; /** * Speeds up page rendering when dynamic content does not have to be retrieved each time a user @@ -335,7 +336,7 @@ private void doServerCache() throws IOException, PageException { if (hasBody) hasBody = !StringUtil.isEmpty(body); // call via cfcache disable debugger output - if (pageContext.getConfig().debug()) pageContext.getDebugger().setOutput(false); + if (PageContextUtil.debug(pageContext)) pageContext.getDebugger().setOutput(false); HttpServletResponse rsp = pageContext.getHttpServletResponse(); diff --git a/core/src/main/java/lucee/runtime/tag/Insert.java b/core/src/main/java/lucee/runtime/tag/Insert.java index e38e5bfd67..84b594ce94 100755 --- a/core/src/main/java/lucee/runtime/tag/Insert.java +++ b/core/src/main/java/lucee/runtime/tag/Insert.java @@ -49,6 +49,7 @@ import lucee.runtime.type.scope.Form; import lucee.runtime.type.util.CollectionUtil; import lucee.runtime.type.util.ListUtil; +import lucee.runtime.util.PageContextUtil; /** * Inserts records in data sources. @@ -196,7 +197,7 @@ public int doEndTag() throws PageException { if (sql != null) { QueryImpl query = new QueryImpl(pageContext, dc, sql, -1, -1, null, "query"); - if (pageContext.getConfig().debug()) { + if (PageContextUtil.debug(pageContext)) { String dsn = ds instanceof DataSource ? ((DataSource) ds).getName() : Caster.toString(ds); boolean logdb = ((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_DATABASE); if (logdb) { @@ -204,7 +205,7 @@ public int doEndTag() throws PageException { DebuggerImpl di = (DebuggerImpl) pageContext.getDebugger(); di.addQuery(debugUsage ? query : null, dsn, "", sql, query.getRecordcount(), - Query.toTemplateLine(pageContext.getConfig(), sourceTemplate, pageContext.getCurrentPageSource()), query.getExecutionTime()); + Query.toTemplateLine(pageContext, sourceTemplate, pageContext.getCurrentPageSource()), query.getExecutionTime()); } } diff --git a/core/src/main/java/lucee/runtime/tag/Location.java b/core/src/main/java/lucee/runtime/tag/Location.java index c01f796b1a..67953847e3 100755 --- a/core/src/main/java/lucee/runtime/tag/Location.java +++ b/core/src/main/java/lucee/runtime/tag/Location.java @@ -35,6 +35,7 @@ import lucee.runtime.listener.ApplicationContext; import lucee.runtime.net.http.ReqRspUtil; import lucee.runtime.op.Caster; +import lucee.runtime.util.PageContextUtil; public final class Location extends TagImpl { @@ -152,11 +153,11 @@ else if (arr.length > 1) { Log log = ThreadLocalPageContext.getLog(pageContext, "application"); if (abort) { - if (log != null) log.log(Log.LEVEL_ERROR, "cftrace", "abort redirect to " + url + " at " + CallStackGet.call(pageContext, "text")); + if (log != null) log.log(Log.LEVEL_ERROR, "cflocation", "abort redirect to " + url + " at " + CallStackGet.call(pageContext, "text")); throw new ExpressionException("abort redirect to " + url); } else { - if (log != null) log.log(Log.LEVEL_TRACE, "cftrace", "redirect to " + url + " at " + CallStackGet.call(pageContext, "text")); + if (log != null) log.log(Log.LEVEL_DEBUG, "cflocation", "redirect to " + url + " at " + CallStackGet.call(pageContext, "text")); } rsp.setHeader("Connection", "close"); // IE unter IIS6, Win2K3 und Resin @@ -172,7 +173,7 @@ else if (arr.length > 1) { catch (IOException e) { throw Caster.toPageException(e); } - if (pageContext.getConfig().debug()) pageContext.getDebugger().setOutput(false); + if (PageContextUtil.debug(pageContext)) pageContext.getDebugger().setOutput(false); throw new Abort(Abort.SCOPE_REQUEST); } diff --git a/core/src/main/java/lucee/runtime/tag/LuceeAI.java b/core/src/main/java/lucee/runtime/tag/LuceeAI.java new file mode 100644 index 0000000000..1fa1152313 --- /dev/null +++ b/core/src/main/java/lucee/runtime/tag/LuceeAI.java @@ -0,0 +1,125 @@ + +package lucee.runtime.tag; + +import lucee.commons.lang.ExceptionUtil; +import lucee.commons.lang.StringUtil; +import lucee.runtime.PageContextImpl; +import lucee.runtime.ai.AISession; +import lucee.runtime.ai.AIUtil; +import lucee.runtime.ai.Response; +import lucee.runtime.exp.ApplicationException; +import lucee.runtime.exp.PageException; +import lucee.runtime.exp.PageRuntimeException; +import lucee.runtime.ext.tag.BodyTagTryCatchFinallyImpl; +import lucee.runtime.op.Caster; +import lucee.runtime.type.dt.TimeSpan; +import lucee.runtime.type.dt.TimeSpanImpl; + +public final class LuceeAI extends BodyTagTryCatchFinallyImpl { + + private String _default; + private String message; + private String name; + private AISession session; + private boolean throwonerror = true; + private long timeout = -1; + private String meta; + + @Override + public void release() { + super.release(); + name = null; + _default = null; + message = null; + meta = null; + session = null; + throwonerror = true; + timeout = -1; + } + + public void setDefault(String _default) { + this._default = _default; + } + + public void setMessage(String message) { + this.message = message; + } + + public void setName(String name) { + this.name = name; + } + + public void setMeta(String meta) { + this.meta = meta; + } + + public void setThrowonerror(boolean throwonerror) { + this.throwonerror = throwonerror; + } + + public void setTimeout(Object timeout) throws PageException { + if (timeout instanceof TimeSpan) this.timeout = ((TimeSpan) timeout).getMillis(); + // seconds + else { + int i = Caster.toIntValue(timeout); + if (i < 0) throw new ApplicationException("invalid value [" + i + "] for attribute timeout, value must be a positive integer greater or equal than 0"); + + this.timeout = new TimeSpanImpl(0, 0, 0, i).getMillis(); + } + } + + @Override + public int doStartTag() throws PageException { + try { + if (!StringUtil.isEmpty(name, true)) { + session = ((PageContextImpl) pageContext).createAISession(name.trim(), message); + setMeta(session); + } + else if (!StringUtil.isEmpty(_default, true)) { + PageContextImpl pci = ((PageContextImpl) pageContext); + String name = throwonerror ? pci.getNameFromDefault(_default.trim()) : pci.getNameFromDefault(_default.trim(), null); + if (!throwonerror && name == null) return SKIP_BODY; + session = ((PageContextImpl) pageContext).createAISession(name.trim(), message, timeout); + setMeta(session); + } + else { + throwonerror = true; + throw new ApplicationException("you need to define the attribute [name] or [default]"); + } + } + catch (Exception e) { + if (throwonerror) throw Caster.toPageException(e); + return SKIP_BODY; + } + return EVAL_BODY_INCLUDE; + } + + private void setMeta(AISession session2) throws PageException { + if (!StringUtil.isEmpty(meta, true)) { + pageContext.setVariable(meta, AIUtil.getMetaData(session.getEngine())); + } + } + + @Override + public void doCatch(Throwable t) throws Throwable { + ExceptionUtil.rethrowIfNecessary(t); + if (throwonerror) throw Caster.toPageException(t); + if (bodyContent != null) bodyContent.clearBody(); + } + + @Override + public void doFinally() { + if (session != null) { + try { + session.release(); + } + catch (PageException e) { + throw new PageRuntimeException(e); + } + } + } + + public Response question(String question) throws PageException { + return session.inquiry(question); + } +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/tag/LuceeAIInquiry.java b/core/src/main/java/lucee/runtime/tag/LuceeAIInquiry.java new file mode 100644 index 0000000000..185a57a2f9 --- /dev/null +++ b/core/src/main/java/lucee/runtime/tag/LuceeAIInquiry.java @@ -0,0 +1,64 @@ + +package lucee.runtime.tag; + +import javax.servlet.jsp.tagext.Tag; + +import lucee.runtime.PageContextImpl; +import lucee.runtime.ai.Response; +import lucee.runtime.exp.ApplicationException; +import lucee.runtime.exp.PageException; +import lucee.runtime.ext.tag.TagImpl; +import lucee.runtime.type.util.KeyConstants; + +public final class LuceeAIInquiry extends TagImpl { + + private String question; + private String answer = null; + + @Override + public void release() { + super.release(); + question = null; + answer = null; + } + + public void setQuestion(String question) { + this.question = question; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + + @Override + public int doStartTag() throws PageException { + Tag parent = getParent(); + while (parent != null && !(parent instanceof LuceeAI)) { + parent = parent.getParent(); + } + + if (parent instanceof LuceeAI) { + Response rsp = ((LuceeAI) parent).question(question); + + if (answer == null) { + PageContextImpl pci = ((PageContextImpl) pageContext); + if (pci.undefinedScope().getCheckArguments()) { + pci.localScope().set(KeyConstants._answer, rsp.getAnswer()); + } + answer = "answer"; + + } + if (answer != null) pageContext.setVariable(answer, rsp.getAnswer()); + } + else { + throw new ApplicationException("the tag [LuceeAIInquiry] need to be insiide the tag [LuceeAI]"); + } + + return SKIP_BODY; + } + + @Override + public int doEndTag() { + return EVAL_PAGE; + } +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/tag/Query.java b/core/src/main/java/lucee/runtime/tag/Query.java index 3486f4ee00..81e4a2d710 100755 --- a/core/src/main/java/lucee/runtime/tag/Query.java +++ b/core/src/main/java/lucee/runtime/tag/Query.java @@ -99,6 +99,7 @@ import lucee.runtime.type.util.CollectionUtil; import lucee.runtime.type.util.KeyConstants; import lucee.runtime.type.util.ListUtil; +import lucee.runtime.util.PageContextUtil; /** * Passes SQL statements to a data source. Not limited to queries. @@ -553,10 +554,10 @@ public int doEndTag() throws PageException { if (data.async) { PageSource ps = getPageSource(); ((SpoolerEngineImpl) ((ConfigPro) pageContext.getConfig()).getSpoolerEngine()).add(pageContext.getConfig(), - new QuerySpoolerTask(pageContext, data, strSQL, toTemplateLine(pageContext.getConfig(), sourceTemplate, ps), ps)); + new QuerySpoolerTask(pageContext, data, strSQL, toTemplateLine(pageContext, sourceTemplate, ps), ps)); } else { - _doEndTag(pageContext, data, strSQL, toTemplateLine(pageContext.getConfig(), sourceTemplate, getPageSource()), true); // when + _doEndTag(pageContext, data, strSQL, toTemplateLine(pageContext, sourceTemplate, getPageSource()), true); // when // sourceTemplate // exists // getPageSource @@ -739,7 +740,7 @@ else if (!StringUtil.isEmpty(data.name)) { queryResult.setCacheType(cacheHandlerId); } - if (pageContext.getConfig().debug() && data.debug) { + if (PageContextUtil.debug(pageContext) && data.debug) { DebuggerImpl di = (DebuggerImpl) pageContext.getDebugger(); boolean logdb = ((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_DATABASE); if (logdb) { @@ -1175,11 +1176,11 @@ public static TagListener toTagListener(Object listener, TagListener defaultValu return defaultValue; } - public static TemplateLine toTemplateLine(Config config, String sourceTemplate, PageSource ps) { + public static TemplateLine toTemplateLine(PageContext pc, String sourceTemplate, PageSource ps) { if (!StringUtil.isEmpty(sourceTemplate)) { return new TemplateLine(sourceTemplate); } - if (config.debug() || ps == null) { + if (PageContextUtil.debug(pc) || ps == null) { TemplateLine rtn = SystemUtil.getCurrentContext(null); if (rtn != null) return rtn; } diff --git a/core/src/main/java/lucee/runtime/tag/StoredProc.java b/core/src/main/java/lucee/runtime/tag/StoredProc.java index f7d12ec3b8..51214200db 100755 --- a/core/src/main/java/lucee/runtime/tag/StoredProc.java +++ b/core/src/main/java/lucee/runtime/tag/StoredProc.java @@ -76,6 +76,7 @@ import lucee.runtime.type.StructImpl; import lucee.runtime.type.dt.DateTime; import lucee.runtime.type.util.KeyConstants; +import lucee.runtime.util.PageContextUtil; public class StoredProc extends BodyTagTryCatchFinallySupport { // private static final int PROCEDURE_CAT=1; @@ -699,7 +700,7 @@ else if (cacheValue instanceof Struct) { res.set(KeyConstants._executionTime, Caster.toDouble(exe = (System.nanoTime() - startNS))); res.set(KeyConstants._cached, Caster.toBoolean(isFromCache)); - if (pageContext.getConfig().debug() && debug) { + if (PageContextUtil.debug(pageContext) && debug) { boolean logdb = ((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_DATABASE); if (logdb) pageContext.getDebugger().addQuery(null, dsn, procedure, _sql, count, pageContext.getCurrentPageSource(), (int) exe); } diff --git a/core/src/main/java/lucee/runtime/tag/TagHandlerPool.java b/core/src/main/java/lucee/runtime/tag/TagHandlerPool.java index cd65d92bcd..eaae5091fc 100755 --- a/core/src/main/java/lucee/runtime/tag/TagHandlerPool.java +++ b/core/src/main/java/lucee/runtime/tag/TagHandlerPool.java @@ -54,17 +54,14 @@ public TagHandlerPool(ConfigWeb config) { */ public Tag use(String className, String tagBundleName, String tagBundleVersion, Identification id) throws PageException { Queue queue = getQueue(toId(className, tagBundleName, tagBundleVersion)); - Tag tag = queue.poll(); + Tag tag = null; + synchronized (queue) { + tag = queue.poll(); + } if (tag != null) return tag; return loadTag(className, tagBundleName, tagBundleVersion, id); } - private String toId(String className, String tagBundleName, String tagBundleVersion) { - if (tagBundleName == null && tagBundleVersion == null) return className; - if (tagBundleVersion == null) return className + ":" + tagBundleName; - return className + ":" + tagBundleName + ":" + tagBundleVersion; - } - /** * free a tag for reusing * @@ -74,13 +71,23 @@ private String toId(String className, String tagBundleName, String tagBundleVers public void reuse(Tag tag) { tag.release(); Queue queue = getQueue(tag.getClass().getName()); - queue.add(tag); + synchronized (queue) { + queue.add(tag); + } } public void reuse(Tag tag, String bundleName, String bundleVersion) { tag.release(); Queue queue = getQueue(toId(tag.getClass().getName(), bundleName, bundleVersion)); - queue.add(tag); + synchronized (queue) { + queue.add(tag); + } + } + + private String toId(String className, String tagBundleName, String tagBundleVersion) { + if (tagBundleName == null && tagBundleVersion == null) return className; + if (tagBundleVersion == null) return className + ":" + tagBundleName; + return className + ":" + tagBundleName + ":" + tagBundleVersion; } private Tag loadTag(String className, String tagBundleName, String tagBundleVersion, Identification id) throws PageException { diff --git a/core/src/main/java/lucee/runtime/tag/Timer.java b/core/src/main/java/lucee/runtime/tag/Timer.java index 3489e4abf1..3f3ac360ae 100644 --- a/core/src/main/java/lucee/runtime/tag/Timer.java +++ b/core/src/main/java/lucee/runtime/tag/Timer.java @@ -28,6 +28,7 @@ import lucee.runtime.ext.tag.BodyTagImpl; import lucee.runtime.op.Caster; //import lucee.runtime.debug.DebuggerPro; +import lucee.runtime.util.PageContextUtil; public final class Timer extends BodyTagImpl { @@ -174,7 +175,7 @@ else if (TYPE_COMMENT == type) { pageContext.write(""); } else if (TYPE_DEBUG == type) { - if (pageContext.getConfig().debug()) { + if (PageContextUtil.debug(pageContext)) { PageSource curr = pageContext.getCurrentTemplatePageSource(); // TODO need to include unitDesc? // ((DebuggerPro) pageContext.getDebugger()).addTimer(label, exe, curr == null ? "unknown template" diff --git a/core/src/main/java/lucee/runtime/tag/Update.java b/core/src/main/java/lucee/runtime/tag/Update.java index d0cd17e1b9..ec7bc7f989 100755 --- a/core/src/main/java/lucee/runtime/tag/Update.java +++ b/core/src/main/java/lucee/runtime/tag/Update.java @@ -48,6 +48,7 @@ import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.CollectionUtil; import lucee.runtime.type.util.ListUtil; +import lucee.runtime.util.PageContextUtil; /** * Updates existing records in data sources. @@ -197,14 +198,14 @@ public int doEndTag() throws PageException { if (sql != null) { QueryImpl query = new QueryImpl(pageContext, dc, sql, -1, -1, null, "query"); - if (pageContext.getConfig().debug()) { + if (PageContextUtil.debug(pageContext)) { String dsn = ds instanceof DataSource ? ((DataSource) ds).getName() : Caster.toString(ds); boolean logdb = ((PageContextImpl) pageContext).hasDebugOptions(ConfigPro.DEBUG_DATABASE); if (logdb) { boolean debugUsage = DebuggerUtil.debugQueryUsage(pageContext, query); DebuggerImpl di = (DebuggerImpl) pageContext.getDebugger(); di.addQuery(debugUsage ? query : null, dsn, "", sql, query.getRecordcount(), - Query.toTemplateLine(pageContext.getConfig(), sourceTemplate, pageContext.getCurrentPageSource()), query.getExecutionTime()); + Query.toTemplateLine(pageContext, sourceTemplate, pageContext.getCurrentPageSource()), query.getExecutionTime()); } } diff --git a/core/src/main/java/lucee/runtime/thread/ChildThreadImpl.java b/core/src/main/java/lucee/runtime/thread/ChildThreadImpl.java index 9a7fb8bd6b..d26d6dbbef 100755 --- a/core/src/main/java/lucee/runtime/thread/ChildThreadImpl.java +++ b/core/src/main/java/lucee/runtime/thread/ChildThreadImpl.java @@ -61,6 +61,7 @@ import lucee.runtime.type.scope.Undefined; import lucee.runtime.type.scope.UndefinedImpl; import lucee.runtime.type.util.KeyConstants; +import lucee.runtime.util.PageContextUtil; public class ChildThreadImpl extends ChildThread implements Serializable { @@ -121,7 +122,6 @@ public ChildThreadImpl(PageContextImpl parent, Page page, String tagName, int th start = System.currentTimeMillis(); if (attrs == null) this.attrs = new StructImpl(); else this.attrs = attrs; - if (!serializable) { this.page = page; if (parent != null) { @@ -193,7 +193,7 @@ public PageException execute(Config config) { } ConfigWebPro ci = (ConfigWebPro) pc.getConfig(); - if (!pc.isGatewayContext() && ci.debug()) { + if (!pc.isGatewayContext() && PageContextUtil.debug(pc)) { ((DebuggerImpl) pc.getDebugger()).setThreadName(tagName); if (pc.hasDebugOptions(ConfigPro.DEBUG_TEMPLATE)) debugEntry = pc.getDebugger().getEntry(pc, page.getPageSource()); } diff --git a/core/src/main/java/lucee/runtime/thread/ThreadUtil.java b/core/src/main/java/lucee/runtime/thread/ThreadUtil.java index d9d9522f9e..2961756534 100755 --- a/core/src/main/java/lucee/runtime/thread/ThreadUtil.java +++ b/core/src/main/java/lucee/runtime/thread/ThreadUtil.java @@ -19,8 +19,13 @@ package lucee.runtime.thread; import java.io.OutputStream; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.util.Iterator; import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; @@ -29,6 +34,7 @@ import lucee.aprint; import lucee.commons.io.DevNullOutputStream; +import lucee.commons.io.SystemUtil; import lucee.commons.io.res.Resource; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.Pair; @@ -47,6 +53,8 @@ public class ThreadUtil { + private static final boolean ALLOW_FUTURE_THREADS = false; + // do not change, used in Redis extension public static PageContextImpl clonePageContext(PageContext pc, OutputStream os, boolean stateless, boolean register2Thread, boolean register2RunningThreads) { // TODO stateless @@ -58,7 +66,6 @@ public static PageContextImpl clonePageContext(PageContext pc, OutputStream os, PageContextImpl pci = (PageContextImpl) pc; PageContextImpl dest = factory.getPageContextImpl(factory.getServlet(), req, rsp, null, false, -1, false, register2Thread, true, pc.getRequestTimeout(), register2RunningThreads, false, false, stateless ? null : pci); - // pci.copyStateTo(dest); return dest; } @@ -202,4 +209,37 @@ public static boolean isInNativeMethod(Thread thread, boolean defaultValue) { StackTraceElement ste = stes[0]; return ste.isNativeMethod(); } + + public static ExecutorService createExecutorService(int maxThreads) { + if (ALLOW_FUTURE_THREADS && SystemUtil.JAVA_VERSION >= SystemUtil.JAVA_VERSION_19) { + // FUTURE use newVirtualThreadPerTaskExecutor natively + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType methodType = MethodType.methodType(ExecutorService.class); + MethodHandle methodHandle = lookup.findStatic(Executors.class, "newVirtualThreadPerTaskExecutor", methodType); + return (ExecutorService) methodHandle.invoke(); + } + catch (Throwable e) { + ExceptionUtil.rethrowIfNecessary(e); + } + } + return Executors.newFixedThreadPool(maxThreads); + } + + public static ExecutorService createExecutorService() { + if (SystemUtil.JAVA_VERSION >= SystemUtil.JAVA_VERSION_19) { + // FUTURE use newVirtualThreadPerTaskExecutor natively + try { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + MethodType methodType = MethodType.methodType(ExecutorService.class); + MethodHandle methodHandle = lookup.findStatic(Executors.class, "newVirtualThreadPerTaskExecutor", methodType); + return (ExecutorService) methodHandle.invoke(); + } + catch (Throwable e) { + ExceptionUtil.rethrowIfNecessary(e); + } + } + return Executors.newSingleThreadExecutor(); + } + } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/type/QueryImpl.java b/core/src/main/java/lucee/runtime/type/QueryImpl.java index d7d0caf90b..afa4e71603 100755 --- a/core/src/main/java/lucee/runtime/type/QueryImpl.java +++ b/core/src/main/java/lucee/runtime/type/QueryImpl.java @@ -131,7 +131,7 @@ public class QueryImpl implements Query, Objects, QueryResult { private QueryColumnImpl[] columns; private Collection.Key[] columnNames; - private ResultSetMetaData metadata; + private transient ResultSetMetaData metadata; private SQL sql; private Map currRow = new ConcurrentHashMap(); private AtomicInteger recordcount = new AtomicInteger(0); diff --git a/core/src/main/java/lucee/runtime/type/scope/ClosureScope.java b/core/src/main/java/lucee/runtime/type/scope/ClosureScope.java index 027a75e15f..ca48c0a4b6 100644 --- a/core/src/main/java/lucee/runtime/type/scope/ClosureScope.java +++ b/core/src/main/java/lucee/runtime/type/scope/ClosureScope.java @@ -27,6 +27,8 @@ import lucee.runtime.Component; import lucee.runtime.ComponentScope; import lucee.runtime.PageContext; +import lucee.runtime.PageContextImpl; +import lucee.runtime.config.ConfigImpl; import lucee.runtime.dump.DumpData; import lucee.runtime.dump.DumpProperties; import lucee.runtime.dump.DumpTable; @@ -55,7 +57,7 @@ public ClosureScope(PageContext pc, Argument arg, Local local, Variables var) { this.arg = arg; this.local = local; this.var = var; - this.debug = pc.getConfig().debug(); + this.debug = ((PageContextImpl) pc).hasDebugOptions(ConfigImpl.DEBUG_IMPLICIT_ACCESS); } /* diff --git a/core/src/main/java/lucee/runtime/type/scope/ServerImpl.java b/core/src/main/java/lucee/runtime/type/scope/ServerImpl.java index 5f2a314589..39a3cfb5f7 100755 --- a/core/src/main/java/lucee/runtime/type/scope/ServerImpl.java +++ b/core/src/main/java/lucee/runtime/type/scope/ServerImpl.java @@ -160,7 +160,7 @@ public void reload(PageContext pc, Boolean jsr223) { lucee.setEL(KeyConstants._state, getStateAsString(info.getVersion())); lucee.setEL(RELEASE_DATE, new DateTimeImpl(info.getRealeaseTime(), false)); lucee.setEL(LOADER_VERSION, Caster.toDouble(SystemUtil.getLoaderVersion())); - lucee.setEL(LOADER_PATH, ClassUtil.getSourcePathForClass("lucee.loader.servlet.CFMLServlet", "")); + lucee.setEL(LOADER_PATH, ClassUtil.getSourcePathForClass(pc, "lucee.loader.servlet.CFMLServlet", "")); lucee.setEL(ENVIRONMENT, jsr223 != null && jsr223.booleanValue() ? "jsr223" : "servlet"); // singleContext admin Mode diff --git a/core/src/main/java/lucee/runtime/type/scope/UndefinedImpl.java b/core/src/main/java/lucee/runtime/type/scope/UndefinedImpl.java index b4d9af4311..c9753fc82b 100755 --- a/core/src/main/java/lucee/runtime/type/scope/UndefinedImpl.java +++ b/core/src/main/java/lucee/runtime/type/scope/UndefinedImpl.java @@ -53,6 +53,7 @@ import lucee.runtime.type.util.CollectionUtil; import lucee.runtime.type.util.KeyConstants; import lucee.runtime.type.util.StructSupport; +import lucee.runtime.util.PageContextUtil; import lucee.runtime.util.QueryStack; import lucee.runtime.util.QueryStackImpl; @@ -242,7 +243,7 @@ public Object get(PageContext pc, Collection.Key key) throws PageException { } } - if (pc.getConfig().debug()) { + if (PageContextUtil.show(pc)) { String msg = ExceptionUtil.similarKeyMessage(this, key.getString(), "key", "keys", null, false); String detail = ExceptionUtil.similarKeyMessage(this, key.getString(), "keys", null, false); throw new ExpressionException(msg, detail); @@ -571,15 +572,19 @@ public boolean isInitalized() { @Override public void initialize(PageContext pc) { + initialize(pc, ((PageContextImpl) pc).getScopeCascadingType(), ((PageContextImpl) pc).hasDebugOptions(ConfigPro.DEBUG_IMPLICIT_ACCESS)); + } + + public void initialize(PageContext pc, short type, boolean debug) { + // if(isInitalized()) return; isInit = true; variable = pc.variablesScope(); argument = pc.argumentsScope(); local = pc.localScope(); // allowImplicidQueryCall = pc.getConfig().allowImplicidQueryCall(); - type = ((PageContextImpl) pc).getScopeCascadingType(); - debug = ((PageContextImpl) pc).hasDebugOptions(ConfigPro.DEBUG_IMPLICIT_ACCESS); - + this.type = type; + this.debug = debug; // Strict if (type == Config.SCOPE_STRICT) { // print.ln("strict"); @@ -599,7 +604,6 @@ else if (type == Config.SCOPE_SMALL) { else { reinitialize(pc); } - } @Override diff --git a/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerDatasource.java b/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerDatasource.java index 88b8fb35ab..fca57a3246 100644 --- a/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerDatasource.java +++ b/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerDatasource.java @@ -58,10 +58,12 @@ public IKStorageValue loadData(PageContext pc, String appName, String name, Stri if (dc != null) ((DatasourceConnectionPro) dc).release(); } - if (query != null && config.debug()) { + if (query != null) { boolean debugUsage = DebuggerUtil.debugQueryUsage(pc, query); - if (debugUsage) pc.getDebugger().addQuery(debugUsage ? query : null, name, "", query.getSql(), query.getRecordcount(), - ((PageContextImpl) pc).getCurrentPageSource(null), query.getExecutionTime()); + if (debugUsage) { + pc.getDebugger().addQuery(debugUsage ? query : null, name, "", query.getSql(), query.getRecordcount(), ((PageContextImpl) pc).getCurrentPageSource(null), + query.getExecutionTime()); + } } boolean _isNew = query.getRecordcount() == 0; diff --git a/core/src/main/java/lucee/runtime/type/util/CollectionUtil.java b/core/src/main/java/lucee/runtime/type/util/CollectionUtil.java index 6e10ee7cd1..59053aff94 100755 --- a/core/src/main/java/lucee/runtime/type/util/CollectionUtil.java +++ b/core/src/main/java/lucee/runtime/type/util/CollectionUtil.java @@ -80,6 +80,16 @@ public static String getKeyList(Collection coll, String delimiter) { return getKeyList(coll.keyIterator(), delimiter); } + public static Key[] keysFromString(java.util.Collection coll) { + if (coll == null) return new Key[0]; + Iterator it = coll.iterator(); + List rtn = new ArrayList(); + if (it != null) while (it.hasNext()) { + rtn.add(KeyImpl.init(it.next())); + } + return rtn.toArray(new Key[rtn.size()]); + } + public static Key[] keys(Collection coll) { if (coll == null) return new Key[0]; Iterator it = coll.keyIterator(); diff --git a/core/src/main/java/lucee/runtime/type/util/ComponentUtil.java b/core/src/main/java/lucee/runtime/type/util/ComponentUtil.java index 1160582b21..d2cc0da53d 100755 --- a/core/src/main/java/lucee/runtime/type/util/ComponentUtil.java +++ b/core/src/main/java/lucee/runtime/type/util/ComponentUtil.java @@ -168,8 +168,7 @@ public static Class getComponentJavaAccess(PageContext pc, Component component, int max; for (int i = 0; i < keys.length; i++) { max = -1; - while ((max = createMethod(ThreadLocalPageContext.getConfig(pc), constr, _keys, cw, real, component.get(keys[i]), max, writeLog, suppressWSbeforeArg, output, - returnValue)) != -1) { + while ((max = createMethod(pc, constr, _keys, cw, real, component.get(keys[i]), max, writeLog, suppressWSbeforeArg, output, returnValue)) != -1) { break;// for overload remove this } } @@ -473,7 +472,7 @@ private static Class _getComponentPropertiesClass(PageContext pc, Component comp String strExt = component.getExtends(); Class ext = Object.class; if (!StringUtil.isEmpty(strExt, true)) { - ext = Caster.cfTypeToClass(strExt); + ext = Caster.cfTypeToClass(pc, strExt); } // // create file @@ -536,7 +535,7 @@ private static Class _getStructPropertiesClass(PageContext pc, Struct sct, Physi return cl.loadClass(className); } - private static int createMethod(Config config, ConstrBytecodeContext constr, java.util.List keys, ClassWriter cw, String className, Object member, int max, + private static int createMethod(PageContext pc, ConstrBytecodeContext constr, java.util.List keys, ClassWriter cw, String className, Object member, int max, boolean writeLog, boolean suppressWSbeforeArg, boolean output, boolean returnValue) throws PageException { boolean hasOptionalArgs = false; @@ -546,13 +545,13 @@ private static int createMethod(Config config, ConstrBytecodeContext constr, jav FunctionArgument[] args = udf.getFunctionArguments(); Type[] types = new Type[max < 0 ? args.length : max]; for (int y = 0; y < types.length; y++) { - types[y] = toType(args[y].getTypeAsString(), true); + types[y] = toType(pc, args[y].getTypeAsString(), true); if (!args[y].isRequired()) hasOptionalArgs = true; } - Type rtnType = toType(udf.getReturnTypeAsString(), true); + Type rtnType = toType(pc, udf.getReturnTypeAsString(), true); Method method = new Method(udf.getFunctionName(), rtnType, types); GeneratorAdapter adapter = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, method, null, null, cw); - BytecodeContext bc = new BytecodeContext(ThreadLocalPageContext.getConfig(config), null, constr, getPage(constr), keys, cw, className, adapter, method, writeLog, + BytecodeContext bc = new BytecodeContext(ThreadLocalPageContext.getConfig(pc), null, constr, getPage(constr), keys, cw, className, adapter, method, writeLog, suppressWSbeforeArg, output, returnValue); Label start = adapter.newLabel(); adapter.visitLabel(start); @@ -592,8 +591,8 @@ private static int createMethod(Config config, ConstrBytecodeContext constr, jav return -1; } - private static Type toType(String cfType, boolean axistype) throws PageException { - Class clazz = Caster.cfTypeToClass(cfType); + private static Type toType(PageContext pc, String cfType, boolean axistype) throws PageException { + Class clazz = Caster.cfTypeToClass(pc, cfType); if (axistype) clazz = ((ConfigWebPro) ThreadLocalPageContext.getConfig()).getWSHandler().toWSTypeClass(clazz); return Type.getType(clazz); diff --git a/core/src/main/java/lucee/runtime/type/util/KeyConstants.java b/core/src/main/java/lucee/runtime/type/util/KeyConstants.java index 417e631444..ea02cf48a6 100644 --- a/core/src/main/java/lucee/runtime/type/util/KeyConstants.java +++ b/core/src/main/java/lucee/runtime/type/util/KeyConstants.java @@ -2976,6 +2976,7 @@ public class KeyConstants { public static final Key _threadName = KeyImpl._const("threadName"); public static final Key _md5 = KeyImpl._const("md5"); public static final Key _model = KeyImpl._const("model"); + public static final Key _models = KeyImpl._const("models"); public static final Key _role = KeyImpl._const("role"); public static final Key _formUrlAsStruct = KeyImpl._const("formUrlAsStruct"); public static final Key _hasBody = KeyImpl._const("hasBody"); @@ -2992,6 +2993,18 @@ public class KeyConstants { public static final Key _metric = KeyImpl._const("metric"); public static final Key _monitoring = KeyImpl._const("monitoring"); public static final Key _expression = KeyImpl._const("expression"); + public static final Key _maven = KeyImpl._const("maven"); + public static final Key _osgi = KeyImpl._const("osgi"); + public static final Key _mvn = KeyImpl._const("mvn"); + public static final Key _groupId = KeyImpl._const("groupId"); + public static final Key _artifactId = KeyImpl._const("artifactId"); + public static final Key _answer = KeyImpl._const("answer"); + public static final Key _contents = KeyImpl._const("contents"); + public static final Key _parts = KeyImpl._const("parts"); + public static final Key _stream = KeyImpl._const("stream"); + public static final Key _temperature = KeyImpl._const("temperature"); + public static final Key _purpose = KeyImpl._const("purpose"); + private static Map _____keys; static { diff --git a/core/src/main/java/lucee/runtime/type/util/StructSupport.java b/core/src/main/java/lucee/runtime/type/util/StructSupport.java index 2420354bed..ee5eed965b 100755 --- a/core/src/main/java/lucee/runtime/type/util/StructSupport.java +++ b/core/src/main/java/lucee/runtime/type/util/StructSupport.java @@ -41,6 +41,7 @@ import lucee.runtime.type.UDF; import lucee.runtime.type.dt.DateTime; import lucee.runtime.type.it.KeyAsStringIterator; +import lucee.runtime.util.PageContextUtil; public abstract class StructSupport implements Map, Struct { @@ -64,7 +65,7 @@ public static ExpressionException invalidKey(Config config, Struct sct, Key key, config = ThreadLocalPageContext.getConfig(config); String msg = ExceptionUtil.similarKeyMessage(sct, key.getString(), "key", "keys", in, true); String detail = ExceptionUtil.similarKeyMessage(sct, key.getString(), "keys", in, true); - if (config != null && config.debug()) return new ExpressionException(msg, detail); + if (config != null && PageContextUtil.debug(ThreadLocalPageContext.get(config))) return new ExpressionException(msg, detail); return new ExpressionException("key [" + key.getString() + "] doesn't exist" + appendix); } diff --git a/core/src/main/java/lucee/runtime/type/util/StructUtil.java b/core/src/main/java/lucee/runtime/type/util/StructUtil.java index d02cfe027a..41fa275cf2 100755 --- a/core/src/main/java/lucee/runtime/type/util/StructUtil.java +++ b/core/src/main/java/lucee/runtime/type/util/StructUtil.java @@ -241,6 +241,24 @@ public static Struct merge(boolean intoFirst, Struct... scts) { return sct; } + public static void merge(Collection a, Collection b) { + Iterator> it = b.entryIterator(); + Entry e; + Object tmp; + while (it.hasNext()) { + e = it.next(); + // Lucee Collection + if (e.getValue() instanceof Collection) { + tmp = a.get(e.getKey(), null); + if (tmp instanceof Collection) { + merge((Collection) tmp, (Collection) e.getValue()); + continue; + } + } + a.setEL(e.getKey(), e.getValue()); + } + } + public static int getType(Map m) { if (m instanceof LinkedHashMap) return Struct.TYPE_LINKED; if (m instanceof WeakHashMap) return Struct.TYPE_WEAKED; diff --git a/core/src/main/java/lucee/runtime/util/ClassUtilImpl.java b/core/src/main/java/lucee/runtime/util/ClassUtilImpl.java index 44f84c0e6d..e63cfde2e8 100644 --- a/core/src/main/java/lucee/runtime/util/ClassUtilImpl.java +++ b/core/src/main/java/lucee/runtime/util/ClassUtilImpl.java @@ -54,6 +54,11 @@ public Class loadClass(String className) throws ClassException { return lucee.commons.lang.ClassUtil.loadClass(className); } + // FUTURE add to interface + public Class loadClass(PageContext pc, String className) throws ClassException { + return lucee.commons.lang.ClassUtil.loadClass(pc, className); + } + @Override public Class loadClass(PageContext pc, String className, String bundleName, String bundleVersion) throws ClassException, BundleException { Config config = ThreadLocalPageContext.getConfig(pc); @@ -273,7 +278,7 @@ public Object loadInstance(Class clazz) throws ClassException { @Override public Object loadInstance(String className) throws ClassException { - return lucee.commons.lang.ClassUtil.loadInstance(className); + return lucee.commons.lang.ClassUtil.loadInstance((PageContext) null, className); } @Override @@ -378,7 +383,7 @@ public String getSourcePathForClass(Class clazz, String defaultValue) { @Override public String getSourcePathForClass(String className, String defaultValue) { - return lucee.commons.lang.ClassUtil.getSourcePathForClass(className, defaultValue); + return lucee.commons.lang.ClassUtil.getSourcePathForClass(null, className, defaultValue); } @Override diff --git a/core/src/main/java/lucee/runtime/util/PageContextUtil.java b/core/src/main/java/lucee/runtime/util/PageContextUtil.java index 063462a270..da9ac06c47 100644 --- a/core/src/main/java/lucee/runtime/util/PageContextUtil.java +++ b/core/src/main/java/lucee/runtime/util/PageContextUtil.java @@ -282,4 +282,14 @@ public static boolean hasDebugOptions(PageContext pc, int option) { if (c instanceof ConfigPro) return ((ConfigPro) c).hasDebugOptions(option); return false; } + + public static boolean debug(PageContext pc) { + if (pc != null) return ((PageContextImpl) pc).getDebugOptions() > 0; + return false; + } + + public static boolean show(PageContext pc) { + if (pc != null) return ((PageContextImpl) pc).show(); + return false; + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/writer/CFMLWriterImpl.java b/core/src/main/java/lucee/runtime/writer/CFMLWriterImpl.java index 306959dfa2..5844d607a4 100644 --- a/core/src/main/java/lucee/runtime/writer/CFMLWriterImpl.java +++ b/core/src/main/java/lucee/runtime/writer/CFMLWriterImpl.java @@ -39,23 +39,20 @@ */ public class CFMLWriterImpl extends CFMLWriter { - private static final int BUFFER_SIZE = 100000; - // private static final String VERSIONj = Info.getVersionAsString(); + private static final int BUFFER_SIZE = 10000; private OutputStream out; private HttpServletResponse response; private boolean flushed; private StringBuilder htmlHead; private StringBuilder htmlBody; - private StringBuilder buffer = new StringBuilder(BUFFER_SIZE); + private StringBuilder buffer; private boolean closed = false; private boolean closeConn; - private boolean showVersion; private boolean contentLength; private CacheItem cacheItem; private HttpServletRequest request; private Boolean _allowCompression; private PageContext pc; - private String version; /** * constructor of the class @@ -73,14 +70,11 @@ public CFMLWriterImpl(PageContext pc, HttpServletRequest request, HttpServletRes this.autoFlush = autoFlush; this.bufferSize = bufferSize; this.closeConn = closeConn; - this.showVersion = showVersion; this.contentLength = contentLength; - // this.allowCompression=allowCompression; - version = pc.getConfig().getFactory().getEngine().getInfo().getVersion().toString(); } private void _check() throws IOException { - if (autoFlush && buffer.length() > bufferSize) { + if (autoFlush && buffer != null && buffer.length() > bufferSize) { _flush(true); } } @@ -94,6 +88,7 @@ protected void initOut() throws IOException { @Override public void print(char[] arg) throws IOException { + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(arg); _check(); } @@ -131,9 +126,8 @@ public String getHTMLBody() throws IOException { @Override public void flushHTMLBody() throws IOException { - if (htmlBody != null) { - + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(htmlBody); resetHTMLBody(); } @@ -173,9 +167,8 @@ public String getHTMLHead() throws IOException { @Override public void flushHTMLHead() throws IOException { - if (htmlHead != null) { - + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(htmlHead); resetHTMLHead(); } @@ -198,6 +191,7 @@ public void initHeaderBuffer() throws IOException { @Override public void write(char[] cbuf, int off, int len) throws IOException { + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(cbuf, off, len); _check(); } @@ -210,7 +204,7 @@ public void clear() throws IOException { @Override public void clearBuffer() { - buffer = new StringBuilder(BUFFER_SIZE); + buffer = null; } @Override @@ -248,15 +242,16 @@ protected final void flushBuffer(boolean closeConn) throws IOException { flushed = true; out.write(barr); - buffer = new StringBuilder(BUFFER_SIZE); // to not change to clearBuffer, produce problem with CFMLWriterWhiteSpace.clearBuffer + buffer = null; // to not change to clearBuffer, produce problem with CFMLWriterWhiteSpace.clearBuffer } private String _toString(boolean releaseHeadData) { - if (htmlBody == null && htmlHead == null) return buffer.toString(); - - String str = buffer.toString(); + if (htmlBody == null && htmlHead == null) { + return buffer == null ? "" : buffer.toString(); + } + String str = buffer == null ? "" : buffer.toString(); if (htmlHead != null) { int index = StringUtil.indexOfIgnoreCase(str, ""); @@ -364,7 +359,7 @@ private OutputStream getOutputStream(boolean allowCompression) throws IOExceptio @Override public int getRemaining() { - return bufferSize - buffer.length(); + return bufferSize - (buffer == null ? 0 : buffer.length()); } @Override @@ -379,6 +374,7 @@ public void print(boolean arg) throws IOException { @Override public void print(char arg) throws IOException { + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(arg); _check(); } @@ -406,6 +402,7 @@ public void print(double arg) throws IOException { @Override public void print(String arg) throws IOException { + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(arg); _check(); } @@ -490,6 +487,7 @@ public void write(String str, int off, int len) throws IOException { @Override public void write(String str) throws IOException { + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(str); _check(); } @@ -512,6 +510,7 @@ public void setClosed(boolean closed) { } private void _print(String arg) throws IOException { + if (buffer == null) buffer = new StringBuilder(BUFFER_SIZE); buffer.append(arg); _check(); } diff --git a/core/src/main/java/lucee/transformer/bytecode/reflection/ASMProxyFactory.java b/core/src/main/java/lucee/transformer/bytecode/reflection/ASMProxyFactory.java index 34dfae4734..b62daec2f0 100644 --- a/core/src/main/java/lucee/transformer/bytecode/reflection/ASMProxyFactory.java +++ b/core/src/main/java/lucee/transformer/bytecode/reflection/ASMProxyFactory.java @@ -143,7 +143,7 @@ public static ASMMethod getMethod(ExtendableClassLoader pcl, Resource classRoot, // try to load existing ASM Class Class asmClass; try { - asmClass = pcl.loadClass(className); + asmClass = ((ClassLoader) pcl).loadClass(className); // print.e("use existing class"); } catch (ClassNotFoundException cnfe) { @@ -170,7 +170,7 @@ private static ASMMethod getMethod(ExtendableClassLoader pcl, Resource classRoot // try to load existing ASM Class Class asmClass; try { - asmClass = pcl.loadClass(className); + asmClass = ((ClassLoader) pcl).loadClass(className); } catch (ClassNotFoundException cnfe) { byte[] barr = _createMethod(type, clazz, method, classRoot, className); @@ -365,14 +365,4 @@ private static byte[] store(byte[] barr, Resource classFile) throws IOException IOUtil.copy(new ByteArrayInputStream(barr), classFile, true); return barr; } - /* - * private void store(ClassWriter cw) { // create class file byte[] barr = cw.toByteArray(); - * - * try { ResourceUtil.touch(classFile); IOUtil.copy(new ByteArrayInputStream(barr), classFile,true); - * - * cl = (PhysicalClassLoader) mapping.getConfig().getRPCClassLoader(true); Class clazz = - * cl.loadClass(className, barr); return newInstance(clazz, config,cfc); } catch(Throwable t) { - * throw Caster.toPageException(t); } } - */ - } \ No newline at end of file diff --git a/core/src/main/java/lucee/transformer/bytecode/util/ASMPropertyImpl.java b/core/src/main/java/lucee/transformer/bytecode/util/ASMPropertyImpl.java index de809efc1f..56200f2820 100755 --- a/core/src/main/java/lucee/transformer/bytecode/util/ASMPropertyImpl.java +++ b/core/src/main/java/lucee/transformer/bytecode/util/ASMPropertyImpl.java @@ -34,11 +34,6 @@ public ASMPropertyImpl(Class type, String name) throws PageException { this.clazz = type; } - public ASMPropertyImpl(String type, String name) throws PageException { - this.type = ASMUtil.toType(type, true); - this.name = name; - } - public ASMPropertyImpl(Type type, String name) { this.type = type; this.name = name; diff --git a/core/src/main/java/lucee/transformer/bytecode/util/ASMUtil.java b/core/src/main/java/lucee/transformer/bytecode/util/ASMUtil.java index 7a4cfa9aff..4d7c03f5ff 100755 --- a/core/src/main/java/lucee/transformer/bytecode/util/ASMUtil.java +++ b/core/src/main/java/lucee/transformer/bytecode/util/ASMUtil.java @@ -26,8 +26,6 @@ import java.util.Iterator; import java.util.List; -import javax.servlet.jsp.PageContext; - import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; @@ -49,6 +47,7 @@ import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.SerializableObject; import lucee.commons.lang.StringUtil; +import lucee.runtime.PageContext; import lucee.runtime.component.Property; import lucee.runtime.config.Config; import lucee.runtime.config.ConfigWebPro; @@ -670,8 +669,8 @@ public static int sizeOf(Type type) { * @return * @throws PageException */ - public static Type toType(String cfType, boolean axistype) throws PageException { - return toType(Caster.cfTypeToClass(cfType), axistype); + public static Type toType(PageContext pc, String cfType, boolean axistype) throws PageException { + return toType(Caster.cfTypeToClass(pc, cfType), axistype); } /** diff --git a/core/src/main/java/lucee/transformer/bytecode/util/JavaProxyFactory.java b/core/src/main/java/lucee/transformer/bytecode/util/JavaProxyFactory.java index 41209ff00a..6d14d54db0 100644 --- a/core/src/main/java/lucee/transformer/bytecode/util/JavaProxyFactory.java +++ b/core/src/main/java/lucee/transformer/bytecode/util/JavaProxyFactory.java @@ -30,6 +30,7 @@ import java.util.Map; import java.util.Set; +import org.apache.felix.framework.BundleWiringImpl.BundleClassLoader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; @@ -41,7 +42,6 @@ import lucee.commons.io.IOUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.util.ResourceUtil; -import lucee.commons.lang.ClassUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.PhysicalClassLoader; import lucee.commons.lang.StringUtil; @@ -139,23 +139,14 @@ public static Object createProxy(Object defaultValue, PageContext pc, UDF udf, C public static Object createProxy(PageContext pc, UDF udf, Class interf) throws PageException, IOException { PageContextImpl pci = (PageContextImpl) pc; - ClassLoader parent = ClassUtil.getClassLoader(interf); - ClassLoader[] parents = new ClassLoader[] { parent }; + PhysicalClassLoader pcl = getRPCClassLoaderFromClass(pc, interf); + if (pcl == null) pcl = (PhysicalClassLoader) pci.getRPCClassLoader(false); if (!interf.isInterface()) throw new IOException("definition [" + interf.getName() + "] is a class and not a interface"); Type typeExtends = Types.OBJECT; Type typeInterface = Type.getType(interf); String strInterface = typeInterface.getInternalName(); - - // get ClassLoader - PhysicalClassLoader pcl = null; - try { - pcl = (PhysicalClassLoader) pci.getRPCClassLoader(false, parents); - } - catch (IOException e) { - throw Caster.toPageException(e); - } String className = createClassName("udf", null, pcl.getDirectory(), Object.class, interf); Resource classFile = pcl.getDirectory().getRealResource(className.concat(".class")); @@ -219,7 +210,6 @@ public static Object createProxy(PageContext pc, UDF udf, Class interf) throws P ResourceUtil.touch(classFile); IOUtil.copy(new ByteArrayInputStream(barr), classFile, true); - pcl = (PhysicalClassLoader) pci.getRPCClassLoader(true, parents); Class clazz = pcl.loadClass(className, barr); return newInstance(clazz, pc.getConfig(), udf); } @@ -240,7 +230,9 @@ public static Object createProxy(Object defaultValue, PageContext pc, Component public static Object createProxy(PageContext pc, final Component cfc, Class extendz, Class... interfaces) throws PageException, IOException { PageContextImpl pci = (PageContextImpl) pc; - ClassLoader[] parents = extractClassLoaders(null, extendz, interfaces); + PhysicalClassLoader pcl = getRPCClassLoaderFromClasses(pc, extendz, interfaces); + + if (pcl == null) pcl = (PhysicalClassLoader) pci.getRPCClassLoader(false); if (extendz == null) extendz = Object.class; if (interfaces == null) interfaces = new Class[0]; @@ -257,15 +249,6 @@ public static Object createProxy(PageContext pc, final Component cfc, Class exte strInterfaces[i] = typeInterfaces[i].getInternalName(); } - // get ClassLoader - PhysicalClassLoader pcl = null; - try { - pcl = (PhysicalClassLoader) pci.getRPCClassLoader(false, parents);// mapping.getConfig().getRPCClassLoader(false) - } - catch (IOException e) { - throw Caster.toPageException(e); - } - String className = createClassName("cfc", cfc, pcl.getDirectory(), extendz, interfaces); String classPath = className.replace('.', '/'); // Ensure classPath is using slashes Resource classFile = pcl.getDirectory().getRealResource(classPath.concat(".class")); @@ -399,7 +382,6 @@ public static Object createProxy(PageContext pc, final Component cfc, Class exte ResourceUtil.touch(classFile); IOUtil.copy(new ByteArrayInputStream(barr), classFile, true); - pcl = (PhysicalClassLoader) pci.getRPCClassLoader(true, parents); Class clazz = pcl.loadClass(className, barr); return newInstance(clazz, pc.getConfig(), cfc); } @@ -452,24 +434,36 @@ public static Object createProxy(PageContext pc, final Component cfc, Class exte * // adapter.returnValue(); adapter.endMethod(); } */ - private static ClassLoader[] extractClassLoaders(ClassLoader cl, Class extendz, Class... classes) { - HashSet set = new HashSet<>(); - if (cl != null) { - set.add(cl); - cl = null; - } + private static PhysicalClassLoader getRPCClassLoaderFromClasses(PageContext pc, Class extendz, Class... interfaces) throws IOException { + // extends and implement need to come from the same parent classloader + PhysicalClassLoader pcl = null; if (extendz != null) { - set.add(ClassUtil.getClassLoader(extendz)); + pcl = getRPCClassLoaderFromClass(pc, extendz); + if (pcl != null) return pcl; } - if (classes != null) { - for (int i = 0; i < classes.length; i++) { - set.add(ClassUtil.getClassLoader(classes[i])); + if (interfaces != null) { + for (Class cls: interfaces) { + pcl = getRPCClassLoaderFromClass(pc, cls); + if (pcl != null) return pcl; } } - return set.toArray(new ClassLoader[set.size()]); + return null; + } + + public static PhysicalClassLoader getRPCClassLoaderFromClass(PageContext pc, Class clazz) throws IOException { + ClassLoader cl = clazz.getClassLoader(); + if (cl != null) { + if (cl instanceof PhysicalClassLoader) { + return ((PhysicalClassLoader) cl); + } + else if (cl instanceof BundleClassLoader) { + return PhysicalClassLoader.getRPCClassLoader(pc.getConfig(), (BundleClassLoader) cl, false); + } + + } + return null; } - // _createProxy(cw, cDone, mDone, udf, interf, className); private static void _createProxy(ClassWriter cw, Set cDone, Map mDone, UDF udf, Class clazz, String className) throws IOException { if (cDone.contains(clazz)) return; @@ -656,6 +650,7 @@ private static String createClassName(String appendix, Component cfc, Resource r if (extendz == null) extendz = Object.class; StringBuilder sb = new StringBuilder(extendz.getName()); + if (interfaces != null && interfaces.length > 0) { sb.append(';'); @@ -667,7 +662,10 @@ private static String createClassName(String appendix, Component cfc, Resource r sb.append(lucee.runtime.type.util.ListUtil.arrayToList(arr, ";")); } - sb.append(appendix).append(';').append(resource.getAbsolutePath()).append(';'); + + sb.append(appendix).append(';') + // .append(resource.getAbsolutePath()).append(';') + ; StringBuilder name = new StringBuilder().append(appendix.charAt(0)).append(HashUtil.create64BitHashAsString(sb.toString(), Character.MAX_RADIX).toLowerCase()); if (cfc != null && !StringUtil.isEmpty(cfc.getAbsName())) { diff --git a/core/src/main/java/lucee/transformer/bytecode/util/Types.java b/core/src/main/java/lucee/transformer/bytecode/util/Types.java index c73a1de42f..dc767bc8f9 100755 --- a/core/src/main/java/lucee/transformer/bytecode/util/Types.java +++ b/core/src/main/java/lucee/transformer/bytecode/util/Types.java @@ -482,7 +482,7 @@ public static Class toClass(Type type) throws ClassException { * ); } */ if (Types.BYTE_VALUE_ARRAY.equals(type)) return byte[].class; - return ClassUtil.toClass(type.getClassName()); + return ClassUtil.loadClass(type.getClassName()); } } \ No newline at end of file diff --git a/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java b/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java index c829725549..a381561e47 100644 --- a/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java +++ b/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java @@ -19,7 +19,7 @@ /** * Directory ClassLoader */ -public final class DynamicClassLoader extends ExtendableClassLoader { +public final class DynamicClassLoader extends ClassLoader implements ExtendableClassLoader { static { boolean res = registerAsParallelCapable(); diff --git a/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java b/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java index e10fb5bbd3..bf937278b6 100644 --- a/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java +++ b/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java @@ -139,8 +139,8 @@ public Clazz getClazz(Class clazz, boolean useReflection) { * private static int loadClassCount = 0; private static int getMatchCount = 0; private static int * hasMatchCount = 0; private static int create1Count = 0; private static int create2Count = 0; */ - public Pair createInstance(Class clazz, Key methodName, Object[] arguments) throws NoSuchMethodException, IOException, ClassNotFoundException, - UnmodifiableClassException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, PageException { + public Pair createInstance(Class clazz, Key methodName, Object[] arguments) throws NoSuchMethodException, IOException, UnmodifiableClassException, + InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, SecurityException, PageException { // observe(clazz, methodName); // double start = SystemUtil.millis(); @@ -181,129 +181,121 @@ public Pair createInstance(Class clazz, Key methodNam String classPath = Clazz.getPackagePrefix() + sbClassPath.toString();// StringUtil.replace(sbClassPath.toString(), "javae/lang/", "java_lang/", false); String className = classPath.replace('/', '.'); - DynamicClassLoader loader = getCL(clazz); - if (loader.hasClass(className)) { - // try { - return new Pair(fm, loader.loadInstance(className)); - /* - * } finally { hasMatchCount++; hasMatchTotal += (SystemUtil.millis() - start); print.e("has match(" - * + hasMatchCount + "):" + Caster.toString(hasMatchTotal / hasMatchCount)); start = - * SystemUtil.millis(); } - */ - } - Class[] parameterClasses = fm.getArgumentClasses(); - - ClassWriter cw = ASMUtil.getClassWriter(); - MethodVisitor mv; - String abstractClassPath = "java/lang/Object"; - cw.visit(ASMUtil.getJavaVersionForBytecodeGeneration(), Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, classPath, - "Ljava/lang/Object;Ljava/util/function/BiFunction;", "java/lang/Object", - new String[] { "java/util/function/BiFunction" }); - // Constructor - mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); - mv.visitCode(); - mv.visitVarInsn(Opcodes.ALOAD, 0); // Load "this" - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, abstractClassPath, "", "()V", false); // Call the constructor of super class (Object) - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(1, 1); // Compute automatically - mv.visitEnd(); - - // Dynamic invoke method - // public abstract Object invoke(PageContext pc, Object[] args) throws PageException; - mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "apply", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null, null); - mv.visitCode(); - boolean isStatic = true; - if (isConstr) { - mv.visitTypeInsn(Opcodes.NEW, Type.getType(clazz).getInternalName()); - mv.visitInsn(Opcodes.DUP); // Duplicate the top operand stack value + synchronized (SystemUtil.createToken("dyninvoc", className)) { - } - else { - isStatic = fm.isStatic(); - if (!isStatic) { - // Load the instance to call the method on - mv.visitVarInsn(Opcodes.ALOAD, 1); // Load the first method argument (instance) - if (!fm.getDeclaringProviderClassWithSameAccess().equals(Object.class)) { // Only cast if clazz is not java.lang.Object - mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(fm.getDeclaringProviderClassWithSameAccess())); + DynamicClassLoader loader = getCL(clazz); + if (loader.hasClass(className)) { + try { + return new Pair(fm, loader.loadInstance(className)); + + } + catch (Exception e) { + // simply ignore when fail } } - } - // Assuming no arguments are needed for the invoked method, i.e., toString() - // For methods that require arguments, you would need to manipulate the args array appropriately - // here - - // print.e(Type.getInternalName(clazz)); - - StringBuilder methodDesc = new StringBuilder(); - String del = "("; - if (fm.getArgumentCount() > 0) { - // Load method arguments from the args array - Type[] args = fm.getArgumentTypes(); - // TODO if args!=arguments throw ! - for (int i = 0; i < args.length; i++) { - - methodDesc.append(del).append(args[i].getDescriptor()); - del = ""; - - mv.visitVarInsn(Opcodes.ALOAD, 2); // Load the args array - mv.visitTypeInsn(Opcodes.CHECKCAST, "[Ljava/lang/Object;"); // Cast it to Object[] - - mv.visitIntInsn(Opcodes.BIPUSH, i); // Index of the argument in the array - mv.visitInsn(Opcodes.AALOAD); // Load the argument from the array - - // Cast or unbox the argument as necessary - // TOOD Caster.castTo(null, clazz, methodDesc) - Class argType = parameterClasses[i]; // TODO get the class from args - if (argType.isPrimitive()) { - Type type = Type.getType(argType); - Class wrapperType = Reflector.toReferenceClass(argType); - mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(wrapperType)); // Cast to wrapper type - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(wrapperType), type.getClassName() + "Value", "()" + type.getDescriptor(), false); // Unbox + Class[] parameterClasses = fm.getArgumentClasses(); + + ClassWriter cw = ASMUtil.getClassWriter(); + MethodVisitor mv; + String abstractClassPath = "java/lang/Object"; + cw.visit(ASMUtil.getJavaVersionForBytecodeGeneration(), Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, classPath, + "Ljava/lang/Object;Ljava/util/function/BiFunction;", "java/lang/Object", + new String[] { "java/util/function/BiFunction" }); + // Constructor + mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); // Load "this" + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, abstractClassPath, "", "()V", false); // Call the constructor of super class (Object) + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(1, 1); // Compute automatically + mv.visitEnd(); + + // Dynamic invoke method + // public abstract Object invoke(PageContext pc, Object[] args) throws PageException; + mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "apply", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", null, null); + mv.visitCode(); + boolean isStatic = true; + if (isConstr) { + mv.visitTypeInsn(Opcodes.NEW, Type.getType(clazz).getInternalName()); + mv.visitInsn(Opcodes.DUP); // Duplicate the top operand stack value + + } + else { + isStatic = fm.isStatic(); + if (!isStatic) { + // Load the instance to call the method on + mv.visitVarInsn(Opcodes.ALOAD, 1); // Load the first method argument (instance) + if (!fm.getDeclaringProviderClassWithSameAccess().equals(Object.class)) { // Only cast if clazz is not java.lang.Object + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(fm.getDeclaringProviderClassWithSameAccess())); + } } - else { - mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(argType)); // Cast to correct type + } + // Assuming no arguments are needed for the invoked method, i.e., toString() + // For methods that require arguments, you would need to manipulate the args array appropriately + // here + + // print.e(Type.getInternalName(clazz)); + + StringBuilder methodDesc = new StringBuilder(); + String del = "("; + if (fm.getArgumentCount() > 0) { + // Load method arguments from the args array + Type[] args = fm.getArgumentTypes(); + // TODO if args!=arguments throw ! + for (int i = 0; i < args.length; i++) { + + methodDesc.append(del).append(args[i].getDescriptor()); + del = ""; + + mv.visitVarInsn(Opcodes.ALOAD, 2); // Load the args array + mv.visitTypeInsn(Opcodes.CHECKCAST, "[Ljava/lang/Object;"); // Cast it to Object[] + + mv.visitIntInsn(Opcodes.BIPUSH, i); // Index of the argument in the array + mv.visitInsn(Opcodes.AALOAD); // Load the argument from the array + + // Cast or unbox the argument as necessary + // TOOD Caster.castTo(null, clazz, methodDesc) + Class argType = parameterClasses[i]; // TODO get the class from args + if (argType.isPrimitive()) { + Type type = Type.getType(argType); + Class wrapperType = Reflector.toReferenceClass(argType); + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(wrapperType)); // Cast to wrapper type + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(wrapperType), type.getClassName() + "Value", "()" + type.getDescriptor(), false); // Unbox + } + else { + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(argType)); // Cast to correct type + } } } - } - else { - methodDesc.append('('); - } - Type rt = isConstr ? Type.getType(clazz) : method.getReturnType(); - methodDesc.append(')').append(isConstr ? Types.VOID : rt.getDescriptor()); - if (isConstr) { - // Create a new instance of java/lang/String - mv.visitMethodInsn(Opcodes.INVOKESPECIAL, rt.getInternalName(), "", methodDesc.toString(), false); // Call the constructor of String - } - else { - mv.visitMethodInsn(isStatic ? Opcodes.INVOKESTATIC : (fm.getDeclaringProviderClassWithSameAccess().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL), - Type.getInternalName(fm.getDeclaringProviderClassWithSameAccess()), method.getName(), methodDesc.toString(), - fm.getDeclaringProviderClassWithSameAccess().isInterface()); - - } + else { + methodDesc.append('('); + } + Type rt = isConstr ? Type.getType(clazz) : method.getReturnType(); + methodDesc.append(')').append(isConstr ? Types.VOID : rt.getDescriptor()); + if (isConstr) { + // Create a new instance of java/lang/String + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, rt.getInternalName(), "", methodDesc.toString(), false); // Call the constructor of String + } + else { + mv.visitMethodInsn(isStatic ? Opcodes.INVOKESTATIC : (fm.getDeclaringProviderClassWithSameAccess().isInterface() ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL), + Type.getInternalName(fm.getDeclaringProviderClassWithSameAccess()), method.getName(), methodDesc.toString(), + fm.getDeclaringProviderClassWithSameAccess().isInterface()); - boxIfPrimitive(mv, rt); - // method on the - // instance - mv.visitInsn(Opcodes.ARETURN); // Return the result of the method call - if (isConstr) mv.visitMaxs(2, 1); - else mv.visitMaxs(1, 3); // Compute automatically - mv.visitEnd(); + } - cw.visitEnd(); - byte[] barr = cw.toByteArray(); + boxIfPrimitive(mv, rt); + // method on the + // instance + mv.visitInsn(Opcodes.ARETURN); // Return the result of the method call + if (isConstr) mv.visitMaxs(2, 1); + else mv.visitMaxs(1, 3); // Compute automatically + mv.visitEnd(); - /* - * { create1Count++; create1Total += (SystemUtil.millis() - start); print.e("create 1(" + - * create1Count + "):" + Caster.toString(create1Total / create1Count)); start = SystemUtil.millis(); - * } - */ - Object result = loader.loadInstance(className, barr); - /* - * { create2Count++; create2Total += (SystemUtil.millis() - start); print.e("create 2(" + - * create2Count + "):" + Caster.toString(create2Total / create2Count)); start = SystemUtil.millis(); - * } - */ - return new Pair(fm, result); + cw.visitEnd(); + byte[] barr = cw.toByteArray(); + Object result = loader.loadInstance(className, barr); + return new Pair(fm, result); + } } private static void observe(Class clazz, Key methodName) { diff --git a/core/src/main/java/lucee/transformer/library/ClassDefinitionImpl.java b/core/src/main/java/lucee/transformer/library/ClassDefinitionImpl.java index 427961a440..19cc460e51 100644 --- a/core/src/main/java/lucee/transformer/library/ClassDefinitionImpl.java +++ b/core/src/main/java/lucee/transformer/library/ClassDefinitionImpl.java @@ -34,7 +34,12 @@ import lucee.runtime.config.Identification; import lucee.runtime.db.ClassDefinition; import lucee.runtime.listener.JavaSettingsImpl; +import lucee.runtime.op.Caster; import lucee.runtime.osgi.OSGiUtil; +import lucee.runtime.type.KeyImpl; +import lucee.runtime.type.Struct; +import lucee.runtime.type.util.KeyConstants; +import lucee.runtime.type.wrap.MapAsStruct; public class ClassDefinitionImpl implements ClassDefinition, Externalizable { @@ -56,13 +61,6 @@ public ClassDefinitionImpl(String className, String name, String version, Identi this.id = id; } - public ClassDefinitionImpl(Identification id, String className, String name, Version version) { - this.className = className == null ? null : className.trim(); - this.name = StringUtil.isEmpty(name, true) ? null : name.trim(); - this.version = version; - this.id = id; - } - public ClassDefinitionImpl(String className) { this.className = className == null ? null : className.trim(); this.name = null; @@ -78,6 +76,71 @@ public ClassDefinitionImpl(Class clazz) { this.id = null; } + public static ClassDefinitionImpl toClassDefinitionImpl(Struct sct, String prefix, boolean strict, Identification id) { + prefix = improvePrefix(prefix); + + String cl = toClassName(sct, prefix); + + // bundle? + String bn = toBundleName(sct, prefix, strict); + String bv = toBundleVersion(sct, prefix, strict); + + if (!StringUtil.isEmpty(bn)) { + return new ClassDefinitionImpl(cl, bn, bv, id); + } + + // TODO Maven? + return new ClassDefinitionImpl(cl, null, null, id); + } + + public static ClassDefinition toClassDefinition(Map map, boolean strict, Identification id) { + return toClassDefinitionImpl(MapAsStruct.toStruct(map, false), null, strict, id); + } + + private static String improvePrefix(String prefix) { + if (prefix != null) { + prefix = prefix.trim(); + if (StringUtil.isEmpty(prefix)) prefix = null; + else if (prefix.endsWith("-")) prefix = prefix.substring(0, prefix.length() - 1); + } + + return prefix; + } + + public static String toClassName(Struct sct, String prefix) { + if (sct == null) return null; + prefix = improvePrefix(prefix); + + String className = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "class") : KeyConstants._class, null), null); + if (StringUtil.isEmpty(className)) className = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "classname") : KeyConstants._classname, null), null); + if (StringUtil.isEmpty(className)) className = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "-class-name") : KeyImpl.init("class-name"), null), null); + if (StringUtil.isEmpty(className)) return null; + return className; + } + + public static String toBundleName(Struct sct, String prefix, boolean strict) { + if (sct == null) return null; + prefix = improvePrefix(prefix); + + String name = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "bundleName") : KeyConstants._bundleName, null), null); + if (StringUtil.isEmpty(name)) name = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "-bundle-name") : KeyImpl.init("bundle-name"), null), null); + if (!strict && StringUtil.isEmpty(name)) name = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "name") : KeyConstants._name, null), null); + if (StringUtil.isEmpty(name)) return null; + return name; + } + + public static String toBundleVersion(Struct sct, String prefix, boolean strict) { + if (sct == null) return null; + + prefix = improvePrefix(prefix); + + String version = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "bundleVersion") : KeyConstants._bundleVersion, null), null); + if (StringUtil.isEmpty(version)) version = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "-bundle-version") : KeyImpl.init("bundle-version"), null), null); + if (!strict && StringUtil.isEmpty(version)) version = Caster.toString(sct.get(prefix != null ? KeyImpl.init(prefix + "version") : KeyConstants._version, null), null); + if (StringUtil.isEmpty(version)) return null; + return version; + } + /** * only used by deserializer! */ diff --git a/core/src/main/java/resource/fld/core-base.fld b/core/src/main/java/resource/fld/core-base.fld index e81f9e01e5..f14aa74357 100755 --- a/core/src/main/java/resource/fld/core-base.fld +++ b/core/src/main/java/resource/fld/core-base.fld @@ -2993,6 +2993,7 @@ You can find a list of all available timezones in the Lucee administrator (Setti createULID + 6.0.1.65 lucee.runtime.functions.other.CreateULID Generates a ULID (Universally Unique Lexicographically Sortable Identifier), a 128-bit identifier where the first 48 bits are a timestamp representing milliseconds since the Unix Epoch (1970-01-01), ensuring temporal ordering. The remaining 80 bits are populated by a secure random number generator, contributing to the identifier's uniqueness. The output is a 26-character string in its canonical representation. This function can operate in three modes specified by the 'type' argument: 'empty' for standard ULID generation, 'monotonic' to ensure sequential IDs even in rapid succession, and 'hash' to generate a ULID based on hashed input values. @@ -3913,30 +3914,41 @@ Specifies the type of results to return: - DeserializeJSON - lucee.runtime.functions.conversion.DeserializeJSON - deserializeJSON - string,parsing,json,javascript - Converts a JSON (JavaScript Object Notation) string data representation into CFML data, such as a struct or array. - - JSONVar - json,data - string - Yes - A string that contains a valid JSON construct, or variable that represents one. - - - strictMapping - boolean - No - true - A Boolean value that specifies whether to convert the JSON strictly - - - any - + DeserializeJSON + lucee.runtime.functions.conversion.DeserializeJSON + deserializeJSON + string,parsing,json,javascript,json5 + 6.2.0.14 + Converts a JSON (JavaScript Object Notation) or JSON5 string data representation into CFML data, such as a struct or array. + + JSONVar + json,data + string + Yes + A string that contains a valid JSON or JSON5 construct, or a variable that represents one. + + + strictMapping + boolean + No + true + A Boolean value that specifies whether to convert the JSON strictly. When true, the function will adhere to strict JSON mapping rules. + + + format + string + No + json5 + The format of the input string. Possible values are: + - **json**: Standard JSON format + - **json5**: JSON5 format which allows additional features such as comments and unquoted keys + + + any + + DotNetToCFType @@ -7378,6 +7390,7 @@ The following things are considered to be empty: IsFlushed lucee.runtime.functions.other.IsFlushed returns true if the response stream was already flushed. + 6.1.0.70 boolean @@ -7409,23 +7422,33 @@ The following things are considered to be empty: boolean - - IsJson - lucee.runtime.functions.conversion.IsJSON - decision,json,javascript - Evaluates whether a string is in valid JSON (JavaScript Object Notation) data interchange format. - - var - value - any - Yes - A string or variable that represents one. - - - boolean - + IsJson + lucee.runtime.functions.conversion.IsJSON + decision,json,javascript,json5 + 6.2.0.14 + Evaluates whether a string is in a valid JSON (JavaScript Object Notation) or JSON5 data interchange format. + + var + value + any + Yes + A string or variable that represents one. + + + format + string + No + json5 + The format of the input string. Possible values are: + - **json**: Standard JSON format + - **json5**: JSON5 format which allows additional features such as comments and unquoted keys + + + boolean + + IsIPv6 @@ -8238,6 +8261,12 @@ The following things are considered to be empty: any Yes the object to cast + + + javaSettings + struct + no + javasettings to use to load the class any @@ -9583,6 +9612,60 @@ The following things are considered to be empty: + + + LuceeGetAIEngine + hidden + lucee.runtime.functions.ai.LuceeGetAIEngine + + + name + aiName,nameAI + string + yes + + + + object + + + + + + LuceeAIGetMetaData + hidden + lucee.runtime.functions.ai.AIGetMetaData + + + name + aiName,nameAI + string + yes + + + + struct + + + + + + LuceeAIHas + hidden + lucee.runtime.functions.ai.AIHas + + + name + aiName,nameAI + string + yes + + + + boolean + + + LuceeVersionsDetail @@ -17399,6 +17482,7 @@ You can find a list of all available timezones in the Lucee administrator (Setti ValueRef lucee.runtime.functions.system.ValueRef creates a reference to a UDF that acts like a simple value. + 6.1.0.15 ref function,udf diff --git a/core/src/main/java/resource/tld/core-base.tld b/core/src/main/java/resource/tld/core-base.tld index 012b8d8e81..3ff0bbe2d9 100755 --- a/core/src/main/java/resource/tld/core-base.tld +++ b/core/src/main/java/resource/tld/core-base.tld @@ -5282,7 +5282,101 @@ To use cached data, the current query must use the same SQL statement, data sour dynamic Protects Lucee-web.xml from LFI - + + + + + LuceeAI + lucee.runtime.tag.LuceeAI + false + must + false + true + hidden + fixed + + + fixed + + string + default + false + true + + + + string + message + false + true + + + + string + name + false + true + + + + boolean + throwonerror + false + true + true + + + + any + timeout + false + -1 + true + + + + string + meta + false + true + + + + + + + LuceeAIInquiry + lucee.runtime.tag.LuceeAIInquiry + false + empty + false + hidden + fixed + + + fixed + + string + question + false + true + + + + string + answer + false + true + + + + rethrow @@ -5668,6 +5762,7 @@ This attribute only takes effect when a custom debug template is defined in the true Yes or No. When set to No, show suppresses monitor information that would otherwise display at the end of the generated page. + 6.1.0.155 @@ -5678,6 +5773,7 @@ This attribute only takes effect when a custom debug template is defined in the true Yes or No. When set to No, showDebug suppresses debugging information that would otherwise display at the end of the generated page. + 6.1.0.155 boolean @@ -5686,6 +5782,7 @@ This attribute only takes effect when a custom debug template is defined in the true Yes or No. When set to No, showDoc suppresses documentation/reference information that would otherwise display at the end of the generated page. + 6.1.0.155 boolean @@ -5694,6 +5791,7 @@ This attribute only takes effect when a custom debug template is defined in the true Yes or No. When set to No, showMetric suppresses metric information that would otherwise display at the end of the generated page. + 6.1.0.155 boolean @@ -5703,6 +5801,7 @@ This attribute only takes effect when a custom debug template is defined in the true Yes or No. When set to No, showTest suppresses test information that would otherwise display at the end of the generated page. + 6.1.0.155 number @@ -6138,6 +6237,7 @@ If you terminate a thread, the thread scope includes an ERROR metadata structure false true the cause of the exception created with this tag. This can be a cfcatch block or a native java exception. + 6.0.0.534 numeric @@ -8889,7 +8989,8 @@ Depending on this setting Lucee scans certain scopes to find a variable called f doc false false - Show documentation output at the end of teh webpage or not. + Show documentation output at the end of the webpage or not. + 6.1.0.155 boolean @@ -8897,7 +8998,8 @@ Depending on this setting Lucee scans certain scopes to find a variable called f metric false false - Show metric output at the end of teh webpage or not. + Show metric output at the end of the webpage or not. + 6.1.0.155 boolean @@ -8906,7 +9008,8 @@ Depending on this setting Lucee scans certain scopes to find a variable called f hidden false false - Show test output at the end of teh webpage or not. + Show test output at the end of the webpage or not. + 6.1.0.155 boolean @@ -8914,7 +9017,8 @@ Depending on this setting Lucee scans certain scopes to find a variable called f debug false false - Show debug output at the end of teh webpage or not. + Show debug output at the end of the webpage or not. + 6.1.0.155 @@ -8922,63 +9026,72 @@ Depending on this setting Lucee scans certain scopes to find a variable called f debuggingDatabase false false - + Log debugging information from the queries in the request + 6.1.0.156 boolean debuggingException false false - + Log debugging information from the exceptions in the request + 6.1.0.156 boolean debuggingDump false false - + Log debugging information from the dump tag in the request + 6.1.0.156 boolean debuggingTracing false false - + Log debugging information from the trace tag in the request + 6.1.0.156 boolean debuggingTimer false false - + Log debugging information from the timer tag in the request + 6.1.0.156 boolean debuggingImplicitAccess false false - + Log debugging information about unscoped variables accessed in the request + 6.1.0.156 boolean debuggingQueryUsage false false - + Log debugging information about which columns in queries are used the request + 6.1.0.156 boolean debuggingThread false false - + Log debugging information about threads used in the request + 6.1.0.156 boolean debuggingTemplate false false - + Log debugging information about templates used in the request + 6.1.0.156 diff --git a/loader/build.xml b/loader/build.xml index e7a3aa7597..15c23e8487 100644 --- a/loader/build.xml +++ b/loader/build.xml @@ -2,7 +2,7 @@ - + diff --git a/loader/pom.xml b/loader/pom.xml index 05a5fcdc15..754a703731 100644 --- a/loader/pom.xml +++ b/loader/pom.xml @@ -3,7 +3,7 @@ org.lucee lucee - 6.2.0.9-SNAPSHOT + 6.2.0.50-SNAPSHOT jar Lucee Loader Build @@ -290,6 +290,22 @@ + + attach-artifact-light + package + + attach-artifact + + + + + ${project.build.directory}/lucee-light-${project.version}.jar + jar + light + + + + @@ -654,13 +670,13 @@ org.apache.tomcat.embed tomcat-embed-core - 9.0.88 + 9.0.90 org.apache.tomcat tomcat-annotations-api - 9.0.88 + 9.0.90 diff --git a/loader/src/main/java/lucee/loader/engine/CFMLEngineFactory.java b/loader/src/main/java/lucee/loader/engine/CFMLEngineFactory.java index 915c2535b9..c05020b554 100755 --- a/loader/src/main/java/lucee/loader/engine/CFMLEngineFactory.java +++ b/loader/src/main/java/lucee/loader/engine/CFMLEngineFactory.java @@ -982,7 +982,7 @@ public File downloadBundle(final String symbolicName, final String symbolicVersi + ", and has been prevented from downloading it. If this jar is not a core jar, it will need to be manually downloaded and placed in the {{lucee-server}}/context/bundles directory.")); } - jar = new File(jarDir, symbolicName.replace('.', '-') + "-" + symbolicVersion.replace('.', '-') + (".jar")); + jar = new File(jarDir, symbolicName + "-" + symbolicVersion + (".jar")); URL updateUrl; try { diff --git a/loader/src/main/java/lucee/loader/engine/mvn/MavenUpdateProvider.java b/loader/src/main/java/lucee/loader/engine/mvn/MavenUpdateProvider.java index d6c8201ad9..60fd034460 100644 --- a/loader/src/main/java/lucee/loader/engine/mvn/MavenUpdateProvider.java +++ b/loader/src/main/java/lucee/loader/engine/mvn/MavenUpdateProvider.java @@ -106,8 +106,9 @@ public InputStream getCore(Version version) throws IOException, GeneralSecurityE String a = artifact.replace('.', '/'); String v = version.toString(); String repo = repoReleases; - urlLco = new URL(repo + "/" + g + "/" + a + "/" + v + "/" + a + "-" + v + ".lco"); - urljar = new URL(repo + "/" + g + "/" + a + "/" + v + "/" + a + "-" + v + ".jar"); + if (!repo.endsWith("/")) repo += "/"; + urlLco = new URL(repo + g + "/" + a + "/" + v + "/" + a + "-" + v + ".lco"); + urljar = new URL(repo + g + "/" + a + "/" + v + "/" + a + "-" + v + ".jar"); } // LCO if (urlLco != null) { diff --git a/test/artifacts/json5-tests/LICENSE.md b/test/artifacts/json5-tests/LICENSE.md new file mode 100644 index 0000000000..ae4049bbf0 --- /dev/null +++ b/test/artifacts/json5-tests/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2012-2016 Aseem Kishore, and [others](https://github.com/json5/json5/contributors). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/test/artifacts/json5-tests/README.md b/test/artifacts/json5-tests/README.md new file mode 100644 index 0000000000..76b9c81e58 --- /dev/null +++ b/test/artifacts/json5-tests/README.md @@ -0,0 +1,22 @@ +# Parse Test Cases for JSON5 + +The test cases' file extension signals the expected behavior: + +- Valid JSON should remain valid JSON5. These cases have a `.json` extension + and are tested via `JSON.parse()`. + +- JSON5's new features should remain valid ES5. These cases have a `.json5` + extension are tested via `eval()`. + +- Valid ES5 that's explicitly disallowed by JSON5 is also invalid JSON. These + cases have a `.js` extension and are expected to fail. + +- Invalid ES5 should remain invalid JSON5. These cases have a `.txt` extension + and are expected to fail. + +This should cover all our bases. Most of the cases are unit tests for each +supported data type, but aggregate test cases are welcome, too. + +## License + +MIT. See [LICENSE.md](./LICENSE.md) for details. diff --git a/test/artifacts/json5-tests/arrays/empty-array.json b/test/artifacts/json5-tests/arrays/empty-array.json new file mode 100644 index 0000000000..0637a088a0 --- /dev/null +++ b/test/artifacts/json5-tests/arrays/empty-array.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/artifacts/json5-tests/arrays/leading-comma-array.js b/test/artifacts/json5-tests/arrays/leading-comma-array.js new file mode 100644 index 0000000000..23c097c065 --- /dev/null +++ b/test/artifacts/json5-tests/arrays/leading-comma-array.js @@ -0,0 +1,3 @@ +[ + ,null +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/arrays/lone-trailing-comma-array.js b/test/artifacts/json5-tests/arrays/lone-trailing-comma-array.js new file mode 100644 index 0000000000..013b45c5bb --- /dev/null +++ b/test/artifacts/json5-tests/arrays/lone-trailing-comma-array.js @@ -0,0 +1,3 @@ +[ + , +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/arrays/no-comma-array.errorSpec b/test/artifacts/json5-tests/arrays/no-comma-array.errorSpec new file mode 100644 index 0000000000..b476eca846 --- /dev/null +++ b/test/artifacts/json5-tests/arrays/no-comma-array.errorSpec @@ -0,0 +1,6 @@ +{ + at: 16, + lineNumber: 3, + columnNumber: 5, + message: "Expected ']' instead of 'f'" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/arrays/no-comma-array.txt b/test/artifacts/json5-tests/arrays/no-comma-array.txt new file mode 100644 index 0000000000..22b41c10c2 --- /dev/null +++ b/test/artifacts/json5-tests/arrays/no-comma-array.txt @@ -0,0 +1,4 @@ +[ + true + false +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/arrays/regular-array.json b/test/artifacts/json5-tests/arrays/regular-array.json new file mode 100644 index 0000000000..9072780299 --- /dev/null +++ b/test/artifacts/json5-tests/arrays/regular-array.json @@ -0,0 +1,5 @@ +[ + true, + false, + null +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/arrays/trailing-comma-array.json5 b/test/artifacts/json5-tests/arrays/trailing-comma-array.json5 new file mode 100644 index 0000000000..6e6b6ede52 --- /dev/null +++ b/test/artifacts/json5-tests/arrays/trailing-comma-array.json5 @@ -0,0 +1,3 @@ +[ + null, +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/block-comment-following-array-element.json5 b/test/artifacts/json5-tests/comments/block-comment-following-array-element.json5 new file mode 100644 index 0000000000..8677f63db3 --- /dev/null +++ b/test/artifacts/json5-tests/comments/block-comment-following-array-element.json5 @@ -0,0 +1,6 @@ +[ + false + /* + true + */ +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/block-comment-following-top-level-value.json5 b/test/artifacts/json5-tests/comments/block-comment-following-top-level-value.json5 new file mode 100644 index 0000000000..1e6ccfd273 --- /dev/null +++ b/test/artifacts/json5-tests/comments/block-comment-following-top-level-value.json5 @@ -0,0 +1,5 @@ +null +/* + Some non-comment top-level value is needed; + we use null above. +*/ \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/block-comment-in-string.json b/test/artifacts/json5-tests/comments/block-comment-in-string.json new file mode 100644 index 0000000000..7d2916c50c --- /dev/null +++ b/test/artifacts/json5-tests/comments/block-comment-in-string.json @@ -0,0 +1 @@ +"This /* block comment */ isn't really a block comment." \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/block-comment-preceding-top-level-value.json5 b/test/artifacts/json5-tests/comments/block-comment-preceding-top-level-value.json5 new file mode 100644 index 0000000000..df1e52044e --- /dev/null +++ b/test/artifacts/json5-tests/comments/block-comment-preceding-top-level-value.json5 @@ -0,0 +1,5 @@ +/* + Some non-comment top-level value is needed; + we use null below. +*/ +null \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/block-comment-with-asterisks.json5 b/test/artifacts/json5-tests/comments/block-comment-with-asterisks.json5 new file mode 100644 index 0000000000..94c44e79e3 --- /dev/null +++ b/test/artifacts/json5-tests/comments/block-comment-with-asterisks.json5 @@ -0,0 +1,7 @@ +/** + * This is a JavaDoc-like block comment. + * It contains asterisks inside of it. + * It might also be closed with multiple asterisks. + * Like this: + **/ +true \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/inline-comment-following-array-element.json5 b/test/artifacts/json5-tests/comments/inline-comment-following-array-element.json5 new file mode 100644 index 0000000000..d6a3f8c64b --- /dev/null +++ b/test/artifacts/json5-tests/comments/inline-comment-following-array-element.json5 @@ -0,0 +1,3 @@ +[ + false // true +] \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/inline-comment-following-top-level-value.json5 b/test/artifacts/json5-tests/comments/inline-comment-following-top-level-value.json5 new file mode 100644 index 0000000000..cf9ed019bb --- /dev/null +++ b/test/artifacts/json5-tests/comments/inline-comment-following-top-level-value.json5 @@ -0,0 +1 @@ +null // Some non-comment top-level value is needed; we use null here. \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/inline-comment-in-string.json b/test/artifacts/json5-tests/comments/inline-comment-in-string.json new file mode 100644 index 0000000000..f0fb14f694 --- /dev/null +++ b/test/artifacts/json5-tests/comments/inline-comment-in-string.json @@ -0,0 +1 @@ +"This inline comment // isn't really an inline comment." \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/inline-comment-preceding-top-level-value.json5 b/test/artifacts/json5-tests/comments/inline-comment-preceding-top-level-value.json5 new file mode 100644 index 0000000000..d4b9b4d13f --- /dev/null +++ b/test/artifacts/json5-tests/comments/inline-comment-preceding-top-level-value.json5 @@ -0,0 +1,2 @@ +// Some non-comment top-level value is needed; we use null below. +null \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/top-level-block-comment.errorSpec b/test/artifacts/json5-tests/comments/top-level-block-comment.errorSpec new file mode 100644 index 0000000000..9bf5cf517d --- /dev/null +++ b/test/artifacts/json5-tests/comments/top-level-block-comment.errorSpec @@ -0,0 +1,6 @@ +{ + at: 77, + lineNumber: 4, + columnNumber: 3, + message: "Unexpected EOF" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/top-level-block-comment.txt b/test/artifacts/json5-tests/comments/top-level-block-comment.txt new file mode 100644 index 0000000000..7466bd2c0b --- /dev/null +++ b/test/artifacts/json5-tests/comments/top-level-block-comment.txt @@ -0,0 +1,4 @@ +/* + This should fail; + comments cannot be the only top-level value. +*/ \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/top-level-inline-comment.errorSpec b/test/artifacts/json5-tests/comments/top-level-inline-comment.errorSpec new file mode 100644 index 0000000000..3d915cd40f --- /dev/null +++ b/test/artifacts/json5-tests/comments/top-level-inline-comment.errorSpec @@ -0,0 +1,6 @@ +{ + at: 66, + lineNumber: 1, + columnNumber: 67, + message: "Unexpected EOF" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/top-level-inline-comment.txt b/test/artifacts/json5-tests/comments/top-level-inline-comment.txt new file mode 100644 index 0000000000..c5577f19dc --- /dev/null +++ b/test/artifacts/json5-tests/comments/top-level-inline-comment.txt @@ -0,0 +1 @@ +// This should fail; comments cannot be the only top-level value. \ No newline at end of file diff --git a/test/artifacts/json5-tests/comments/unterminated-block-comment.txt b/test/artifacts/json5-tests/comments/unterminated-block-comment.txt new file mode 100644 index 0000000000..627b7bd17c --- /dev/null +++ b/test/artifacts/json5-tests/comments/unterminated-block-comment.txt @@ -0,0 +1,5 @@ +true +/* + This block comment doesn't terminate. + There was a legitimate value before this, + but this is still invalid JS/JSON5. diff --git a/test/artifacts/json5-tests/misc/empty.txt b/test/artifacts/json5-tests/misc/empty.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/artifacts/json5-tests/misc/npm-package.json b/test/artifacts/json5-tests/misc/npm-package.json new file mode 100644 index 0000000000..85568da173 --- /dev/null +++ b/test/artifacts/json5-tests/misc/npm-package.json @@ -0,0 +1,106 @@ +{ + "name": "npm", + "publishConfig": { + "proprietary-attribs": false + }, + "description": "A package manager for node", + "keywords": [ + "package manager", + "modules", + "install", + "package.json" + ], + "version": "1.1.22", + "preferGlobal": true, + "config": { + "publishtest": false + }, + "homepage": "http://npmjs.org/", + "author": "Isaac Z. Schlueter (http://blog.izs.me)", + "repository": { + "type": "git", + "url": "https://github.com/isaacs/npm" + }, + "bugs": { + "email": "npm-@googlegroups.com", + "url": "http://github.com/isaacs/npm/issues" + }, + "directories": { + "doc": "./doc", + "man": "./man", + "lib": "./lib", + "bin": "./bin" + }, + "main": "./lib/npm.js", + "bin": "./bin/npm-cli.js", + "dependencies": { + "semver": "~1.0.14", + "ini": "1", + "slide": "1", + "abbrev": "1", + "graceful-fs": "~1.1.1", + "minimatch": "~0.2", + "nopt": "1", + "node-uuid": "~1.3", + "proto-list": "1", + "rimraf": "2", + "request": "~2.9", + "which": "1", + "tar": "~0.1.12", + "fstream": "~0.1.17", + "block-stream": "*", + "inherits": "1", + "mkdirp": "0.3", + "read": "0", + "lru-cache": "1", + "node-gyp": "~0.4.1", + "fstream-npm": "0 >=0.0.5", + "uid-number": "0", + "archy": "0", + "chownr": "0" + }, + "bundleDependencies": [ + "slide", + "ini", + "semver", + "abbrev", + "graceful-fs", + "minimatch", + "nopt", + "node-uuid", + "rimraf", + "request", + "proto-list", + "which", + "tar", + "fstream", + "block-stream", + "inherits", + "mkdirp", + "read", + "lru-cache", + "node-gyp", + "fstream-npm", + "uid-number", + "archy", + "chownr" + ], + "devDependencies": { + "ronn": "https://github.com/isaacs/ronnjs/tarball/master" + }, + "engines": { + "node": "0.6 || 0.7 || 0.8", + "npm": "1" + }, + "scripts": { + "test": "node ./test/run.js", + "prepublish": "npm prune; rm -rf node_modules/*/{test,example,bench}*; make -j4 doc", + "dumpconf": "env | grep npm | sort | uniq" + }, + "licenses": [ + { + "type": "MIT +no-false-attribs", + "url": "http://github.com/isaacs/npm/raw/master/LICENSE" + } + ] +} diff --git a/test/artifacts/json5-tests/misc/npm-package.json5 b/test/artifacts/json5-tests/misc/npm-package.json5 new file mode 100644 index 0000000000..699440659d --- /dev/null +++ b/test/artifacts/json5-tests/misc/npm-package.json5 @@ -0,0 +1,106 @@ +{ + name: 'npm', + publishConfig: { + 'proprietary-attribs': false, + }, + description: 'A package manager for node', + keywords: [ + 'package manager', + 'modules', + 'install', + 'package.json', + ], + version: '1.1.22', + preferGlobal: true, + config: { + publishtest: false, + }, + homepage: 'http://npmjs.org/', + author: 'Isaac Z. Schlueter (http://blog.izs.me)', + repository: { + type: 'git', + url: 'https://github.com/isaacs/npm', + }, + bugs: { + email: 'npm-@googlegroups.com', + url: 'http://github.com/isaacs/npm/issues', + }, + directories: { + doc: './doc', + man: './man', + lib: './lib', + bin: './bin', + }, + main: './lib/npm.js', + bin: './bin/npm-cli.js', + dependencies: { + semver: '~1.0.14', + ini: '1', + slide: '1', + abbrev: '1', + 'graceful-fs': '~1.1.1', + minimatch: '~0.2', + nopt: '1', + 'node-uuid': '~1.3', + 'proto-list': '1', + rimraf: '2', + request: '~2.9', + which: '1', + tar: '~0.1.12', + fstream: '~0.1.17', + 'block-stream': '*', + inherits: '1', + mkdirp: '0.3', + read: '0', + 'lru-cache': '1', + 'node-gyp': '~0.4.1', + 'fstream-npm': '0 >=0.0.5', + 'uid-number': '0', + archy: '0', + chownr: '0', + }, + bundleDependencies: [ + 'slide', + 'ini', + 'semver', + 'abbrev', + 'graceful-fs', + 'minimatch', + 'nopt', + 'node-uuid', + 'rimraf', + 'request', + 'proto-list', + 'which', + 'tar', + 'fstream', + 'block-stream', + 'inherits', + 'mkdirp', + 'read', + 'lru-cache', + 'node-gyp', + 'fstream-npm', + 'uid-number', + 'archy', + 'chownr', + ], + devDependencies: { + ronn: 'https://github.com/isaacs/ronnjs/tarball/master', + }, + engines: { + node: '0.6 || 0.7 || 0.8', + npm: '1', + }, + scripts: { + test: 'node ./test/run.js', + prepublish: 'npm prune; rm -rf node_modules/*/{test,example,bench}*; make -j4 doc', + dumpconf: 'env | grep npm | sort | uniq', + }, + licenses: [ + { + type: 'MIT +no-false-attribs', + url: 'http://github.com/isaacs/npm/raw/master/LICENSE', + }, + ], +} diff --git a/test/artifacts/json5-tests/misc/readme-example.json5 b/test/artifacts/json5-tests/misc/readme-example.json5 new file mode 100644 index 0000000000..25c920a3ce --- /dev/null +++ b/test/artifacts/json5-tests/misc/readme-example.json5 @@ -0,0 +1,25 @@ +{ + foo: 'bar', + while: true, + + this: 'is a \ +multi-line string', + + // this is an inline comment + here: 'is another', // inline comment + + /* this is a block comment + that continues on another line */ + + hex: 0xDEADbeef, + half: .5, + delta: +10, + to: Infinity, // and beyond! + + finally: 'a trailing comma', + oh: [ + "we shouldn't forget", + 'arrays can have', + 'trailing commas too', + ], +} diff --git a/test/artifacts/json5-tests/misc/valid-whitespace.json5 b/test/artifacts/json5-tests/misc/valid-whitespace.json5 new file mode 100644 index 0000000000..5cb57d3648 --- /dev/null +++ b/test/artifacts/json5-tests/misc/valid-whitespace.json5 @@ -0,0 +1,5 @@ +{ + // An invalid form feed character (\x0c) has been entered before this comment. + // Be careful not to delete it. + "a": true +} diff --git a/test/artifacts/json5-tests/new-lines/.editorconfig b/test/artifacts/json5-tests/new-lines/.editorconfig new file mode 100644 index 0000000000..1784f9e321 --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/.editorconfig @@ -0,0 +1,13 @@ +# Since we're testing different representations of new lines, +# make sure the editor doesn't mangle line endings. +# Don't commit files in this directory unless you've checked +# their escaped new lines. + +[*-lf.*] +end_of_line = lf + +[*-cr.*] +end_of_line = cr + +[*-crlf.*] +end_of_line = crlf diff --git a/test/artifacts/json5-tests/new-lines/.gitattributes b/test/artifacts/json5-tests/new-lines/.gitattributes new file mode 100644 index 0000000000..2b3eea6957 --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/.gitattributes @@ -0,0 +1,4 @@ +# Since we're testing different representations of new lines, +# treat all tests in this folder as binary files. + +* binary diff --git a/test/artifacts/json5-tests/new-lines/comment-cr.json5 b/test/artifacts/json5-tests/new-lines/comment-cr.json5 new file mode 100644 index 0000000000..e55aff8832 --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/comment-cr.json5 @@ -0,0 +1 @@ +{ // This comment is terminated with `\r`. } \ No newline at end of file diff --git a/test/artifacts/json5-tests/new-lines/comment-crlf.json5 b/test/artifacts/json5-tests/new-lines/comment-crlf.json5 new file mode 100644 index 0000000000..3791ee6bda --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/comment-crlf.json5 @@ -0,0 +1,3 @@ +{ + // This comment is terminated with `\r\n`. +} diff --git a/test/artifacts/json5-tests/new-lines/comment-lf.json5 b/test/artifacts/json5-tests/new-lines/comment-lf.json5 new file mode 100644 index 0000000000..e17dd72c6e --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/comment-lf.json5 @@ -0,0 +1,3 @@ +{ + // This comment is terminated with `\n`. +} diff --git a/test/artifacts/json5-tests/new-lines/escaped-cr.json5 b/test/artifacts/json5-tests/new-lines/escaped-cr.json5 new file mode 100644 index 0000000000..38e55b6cc3 --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/escaped-cr.json5 @@ -0,0 +1 @@ +{ // the following string contains an escaped `\r` a: 'line 1 \ line 2' } \ No newline at end of file diff --git a/test/artifacts/json5-tests/new-lines/escaped-crlf.json5 b/test/artifacts/json5-tests/new-lines/escaped-crlf.json5 new file mode 100644 index 0000000000..7e3f1ce4d5 --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/escaped-crlf.json5 @@ -0,0 +1,5 @@ +{ + // the following string contains an escaped `\r\n` + a: 'line 1 \ +line 2' +} diff --git a/test/artifacts/json5-tests/new-lines/escaped-lf.json5 b/test/artifacts/json5-tests/new-lines/escaped-lf.json5 new file mode 100644 index 0000000000..2235e8c7fe --- /dev/null +++ b/test/artifacts/json5-tests/new-lines/escaped-lf.json5 @@ -0,0 +1,5 @@ +{ + // the following string contains an escaped `\n` + a: 'line 1 \ +line 2' +} diff --git a/test/artifacts/json5-tests/numbers/float-leading-decimal-point.json5 b/test/artifacts/json5-tests/numbers/float-leading-decimal-point.json5 new file mode 100644 index 0000000000..d6c9fff3d7 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/float-leading-decimal-point.json5 @@ -0,0 +1 @@ +.5 diff --git a/test/artifacts/json5-tests/numbers/float-leading-zero.json b/test/artifacts/json5-tests/numbers/float-leading-zero.json new file mode 100644 index 0000000000..2eb3c4fe4e --- /dev/null +++ b/test/artifacts/json5-tests/numbers/float-leading-zero.json @@ -0,0 +1 @@ +0.5 diff --git a/test/artifacts/json5-tests/numbers/float-trailing-decimal-point-with-integer-exponent.json5 b/test/artifacts/json5-tests/numbers/float-trailing-decimal-point-with-integer-exponent.json5 new file mode 100644 index 0000000000..70b872070d --- /dev/null +++ b/test/artifacts/json5-tests/numbers/float-trailing-decimal-point-with-integer-exponent.json5 @@ -0,0 +1 @@ +5.e4 diff --git a/test/artifacts/json5-tests/numbers/float-trailing-decimal-point.json5 b/test/artifacts/json5-tests/numbers/float-trailing-decimal-point.json5 new file mode 100644 index 0000000000..e4c8c3130a --- /dev/null +++ b/test/artifacts/json5-tests/numbers/float-trailing-decimal-point.json5 @@ -0,0 +1 @@ +5. diff --git a/test/artifacts/json5-tests/numbers/float-with-integer-exponent.json b/test/artifacts/json5-tests/numbers/float-with-integer-exponent.json new file mode 100644 index 0000000000..0e957c6332 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/float-with-integer-exponent.json @@ -0,0 +1 @@ +1.2e3 diff --git a/test/artifacts/json5-tests/numbers/float.json b/test/artifacts/json5-tests/numbers/float.json new file mode 100644 index 0000000000..5625e59da8 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/float.json @@ -0,0 +1 @@ +1.2 diff --git a/test/artifacts/json5-tests/numbers/hexadecimal-empty.txt b/test/artifacts/json5-tests/numbers/hexadecimal-empty.txt new file mode 100644 index 0000000000..ec687260b8 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/hexadecimal-empty.txt @@ -0,0 +1 @@ +0x diff --git a/test/artifacts/json5-tests/numbers/hexadecimal-lowercase-letter.json5 b/test/artifacts/json5-tests/numbers/hexadecimal-lowercase-letter.json5 new file mode 100644 index 0000000000..57e27eede0 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/hexadecimal-lowercase-letter.json5 @@ -0,0 +1 @@ +0xc8 diff --git a/test/artifacts/json5-tests/numbers/hexadecimal-uppercase-x.json5 b/test/artifacts/json5-tests/numbers/hexadecimal-uppercase-x.json5 new file mode 100644 index 0000000000..1a35066be7 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/hexadecimal-uppercase-x.json5 @@ -0,0 +1 @@ +0XC8 diff --git a/test/artifacts/json5-tests/numbers/hexadecimal-with-integer-exponent.json5 b/test/artifacts/json5-tests/numbers/hexadecimal-with-integer-exponent.json5 new file mode 100644 index 0000000000..3c2204af84 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/hexadecimal-with-integer-exponent.json5 @@ -0,0 +1 @@ +0xc8e4 diff --git a/test/artifacts/json5-tests/numbers/hexadecimal.json5 b/test/artifacts/json5-tests/numbers/hexadecimal.json5 new file mode 100644 index 0000000000..cf832ed1da --- /dev/null +++ b/test/artifacts/json5-tests/numbers/hexadecimal.json5 @@ -0,0 +1 @@ +0xC8 diff --git a/test/artifacts/json5-tests/numbers/infinity.json5 b/test/artifacts/json5-tests/numbers/infinity.json5 new file mode 100644 index 0000000000..3c62151db7 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/infinity.json5 @@ -0,0 +1 @@ +Infinity diff --git a/test/artifacts/json5-tests/numbers/integer-with-float-exponent.txt b/test/artifacts/json5-tests/numbers/integer-with-float-exponent.txt new file mode 100644 index 0000000000..fa0688c933 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-float-exponent.txt @@ -0,0 +1 @@ +1e2.3 diff --git a/test/artifacts/json5-tests/numbers/integer-with-hexadecimal-exponent.txt b/test/artifacts/json5-tests/numbers/integer-with-hexadecimal-exponent.txt new file mode 100644 index 0000000000..0f58237dcf --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-hexadecimal-exponent.txt @@ -0,0 +1 @@ +1e0x4 diff --git a/test/artifacts/json5-tests/numbers/integer-with-integer-exponent.json b/test/artifacts/json5-tests/numbers/integer-with-integer-exponent.json new file mode 100644 index 0000000000..0d5cde84b5 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-integer-exponent.json @@ -0,0 +1 @@ +2e23 diff --git a/test/artifacts/json5-tests/numbers/integer-with-negative-float-exponent.txt b/test/artifacts/json5-tests/numbers/integer-with-negative-float-exponent.txt new file mode 100644 index 0000000000..5be09158f5 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-negative-float-exponent.txt @@ -0,0 +1 @@ +1e-2.3 diff --git a/test/artifacts/json5-tests/numbers/integer-with-negative-hexadecimal-exponent.txt b/test/artifacts/json5-tests/numbers/integer-with-negative-hexadecimal-exponent.txt new file mode 100644 index 0000000000..adeb2b9836 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-negative-hexadecimal-exponent.txt @@ -0,0 +1 @@ +1e-0x4 diff --git a/test/artifacts/json5-tests/numbers/integer-with-negative-integer-exponent.json b/test/artifacts/json5-tests/numbers/integer-with-negative-integer-exponent.json new file mode 100644 index 0000000000..6118c3ecd3 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-negative-integer-exponent.json @@ -0,0 +1 @@ +2e-23 diff --git a/test/artifacts/json5-tests/numbers/integer-with-negative-zero-integer-exponent.json b/test/artifacts/json5-tests/numbers/integer-with-negative-zero-integer-exponent.json new file mode 100644 index 0000000000..eb67bf46c2 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-negative-zero-integer-exponent.json @@ -0,0 +1 @@ +5e-0 diff --git a/test/artifacts/json5-tests/numbers/integer-with-positive-float-exponent.txt b/test/artifacts/json5-tests/numbers/integer-with-positive-float-exponent.txt new file mode 100644 index 0000000000..f89d55ecf3 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-positive-float-exponent.txt @@ -0,0 +1 @@ +1e+2.3 diff --git a/test/artifacts/json5-tests/numbers/integer-with-positive-hexadecimal-exponent.txt b/test/artifacts/json5-tests/numbers/integer-with-positive-hexadecimal-exponent.txt new file mode 100644 index 0000000000..a6c75d9495 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-positive-hexadecimal-exponent.txt @@ -0,0 +1 @@ +1e+0x4 diff --git a/test/artifacts/json5-tests/numbers/integer-with-positive-integer-exponent.json b/test/artifacts/json5-tests/numbers/integer-with-positive-integer-exponent.json new file mode 100644 index 0000000000..90c0616f53 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-positive-integer-exponent.json @@ -0,0 +1 @@ +1e+2 diff --git a/test/artifacts/json5-tests/numbers/integer-with-positive-zero-integer-exponent.json b/test/artifacts/json5-tests/numbers/integer-with-positive-zero-integer-exponent.json new file mode 100644 index 0000000000..1d7002f9f2 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-positive-zero-integer-exponent.json @@ -0,0 +1 @@ +5e+0 diff --git a/test/artifacts/json5-tests/numbers/integer-with-zero-integer-exponent.json b/test/artifacts/json5-tests/numbers/integer-with-zero-integer-exponent.json new file mode 100644 index 0000000000..a5e3196e3a --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer-with-zero-integer-exponent.json @@ -0,0 +1 @@ +5e0 diff --git a/test/artifacts/json5-tests/numbers/integer.json b/test/artifacts/json5-tests/numbers/integer.json new file mode 100644 index 0000000000..60d3b2f4a4 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/integer.json @@ -0,0 +1 @@ +15 diff --git a/test/artifacts/json5-tests/numbers/lone-decimal-point.txt b/test/artifacts/json5-tests/numbers/lone-decimal-point.txt new file mode 100644 index 0000000000..9c558e357c --- /dev/null +++ b/test/artifacts/json5-tests/numbers/lone-decimal-point.txt @@ -0,0 +1 @@ +. diff --git a/test/artifacts/json5-tests/numbers/nan.json5 b/test/artifacts/json5-tests/numbers/nan.json5 new file mode 100644 index 0000000000..736991a138 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/nan.json5 @@ -0,0 +1 @@ +NaN diff --git a/test/artifacts/json5-tests/numbers/negative-float-leading-decimal-point.json5 b/test/artifacts/json5-tests/numbers/negative-float-leading-decimal-point.json5 new file mode 100644 index 0000000000..c6eaee5ce7 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-float-leading-decimal-point.json5 @@ -0,0 +1 @@ +-.5 diff --git a/test/artifacts/json5-tests/numbers/negative-float-leading-zero.json b/test/artifacts/json5-tests/numbers/negative-float-leading-zero.json new file mode 100644 index 0000000000..e118203f34 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-float-leading-zero.json @@ -0,0 +1 @@ +-0.5 diff --git a/test/artifacts/json5-tests/numbers/negative-float-trailing-decimal-point.json5 b/test/artifacts/json5-tests/numbers/negative-float-trailing-decimal-point.json5 new file mode 100644 index 0000000000..52e52459fc --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-float-trailing-decimal-point.json5 @@ -0,0 +1 @@ +-5. diff --git a/test/artifacts/json5-tests/numbers/negative-float.json b/test/artifacts/json5-tests/numbers/negative-float.json new file mode 100644 index 0000000000..1d94c8a014 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-float.json @@ -0,0 +1 @@ +-1.2 diff --git a/test/artifacts/json5-tests/numbers/negative-hexadecimal.json5 b/test/artifacts/json5-tests/numbers/negative-hexadecimal.json5 new file mode 100644 index 0000000000..8882fae361 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-hexadecimal.json5 @@ -0,0 +1 @@ +-0xC8 diff --git a/test/artifacts/json5-tests/numbers/negative-infinity.json5 b/test/artifacts/json5-tests/numbers/negative-infinity.json5 new file mode 100644 index 0000000000..879e80ee34 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-infinity.json5 @@ -0,0 +1 @@ +-Infinity diff --git a/test/artifacts/json5-tests/numbers/negative-integer.json b/test/artifacts/json5-tests/numbers/negative-integer.json new file mode 100644 index 0000000000..21922364cc --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-integer.json @@ -0,0 +1 @@ +-15 diff --git a/test/artifacts/json5-tests/numbers/negative-noctal.js b/test/artifacts/json5-tests/numbers/negative-noctal.js new file mode 100644 index 0000000000..8826f48663 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-noctal.js @@ -0,0 +1 @@ +-098 diff --git a/test/artifacts/json5-tests/numbers/negative-octal.txt b/test/artifacts/json5-tests/numbers/negative-octal.txt new file mode 100644 index 0000000000..2e7a4b4500 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-octal.txt @@ -0,0 +1 @@ +-0123 diff --git a/test/artifacts/json5-tests/numbers/negative-zero-float-leading-decimal-point.json5 b/test/artifacts/json5-tests/numbers/negative-zero-float-leading-decimal-point.json5 new file mode 100644 index 0000000000..8dd8e037eb --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-zero-float-leading-decimal-point.json5 @@ -0,0 +1 @@ +-.0 diff --git a/test/artifacts/json5-tests/numbers/negative-zero-float-trailing-decimal-point.json5 b/test/artifacts/json5-tests/numbers/negative-zero-float-trailing-decimal-point.json5 new file mode 100644 index 0000000000..90cc048c59 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-zero-float-trailing-decimal-point.json5 @@ -0,0 +1 @@ +-0. diff --git a/test/artifacts/json5-tests/numbers/negative-zero-float.json b/test/artifacts/json5-tests/numbers/negative-zero-float.json new file mode 100644 index 0000000000..1344bfd9db --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-zero-float.json @@ -0,0 +1 @@ +-0.0 diff --git a/test/artifacts/json5-tests/numbers/negative-zero-hexadecimal.json5 b/test/artifacts/json5-tests/numbers/negative-zero-hexadecimal.json5 new file mode 100644 index 0000000000..8847d0524d --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-zero-hexadecimal.json5 @@ -0,0 +1 @@ +-0x0 diff --git a/test/artifacts/json5-tests/numbers/negative-zero-integer.json b/test/artifacts/json5-tests/numbers/negative-zero-integer.json new file mode 100644 index 0000000000..ec064f61ba --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-zero-integer.json @@ -0,0 +1 @@ +-0 diff --git a/test/artifacts/json5-tests/numbers/negative-zero-octal.txt b/test/artifacts/json5-tests/numbers/negative-zero-octal.txt new file mode 100644 index 0000000000..200a801846 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/negative-zero-octal.txt @@ -0,0 +1 @@ +-00 diff --git a/test/artifacts/json5-tests/numbers/noctal-with-leading-octal-digit.js b/test/artifacts/json5-tests/numbers/noctal-with-leading-octal-digit.js new file mode 100644 index 0000000000..1fd7c08c14 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/noctal-with-leading-octal-digit.js @@ -0,0 +1 @@ +0780 diff --git a/test/artifacts/json5-tests/numbers/noctal.js b/test/artifacts/json5-tests/numbers/noctal.js new file mode 100644 index 0000000000..fa5c7835bd --- /dev/null +++ b/test/artifacts/json5-tests/numbers/noctal.js @@ -0,0 +1 @@ +080 diff --git a/test/artifacts/json5-tests/numbers/octal.txt b/test/artifacts/json5-tests/numbers/octal.txt new file mode 100644 index 0000000000..9e8493eaee --- /dev/null +++ b/test/artifacts/json5-tests/numbers/octal.txt @@ -0,0 +1 @@ +010 diff --git a/test/artifacts/json5-tests/numbers/positive-float-leading-decimal-point.json5 b/test/artifacts/json5-tests/numbers/positive-float-leading-decimal-point.json5 new file mode 100644 index 0000000000..043460803c --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-float-leading-decimal-point.json5 @@ -0,0 +1 @@ ++.5 diff --git a/test/artifacts/json5-tests/numbers/positive-float-leading-zero.json5 b/test/artifacts/json5-tests/numbers/positive-float-leading-zero.json5 new file mode 100644 index 0000000000..d89b45d161 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-float-leading-zero.json5 @@ -0,0 +1 @@ ++0.5 diff --git a/test/artifacts/json5-tests/numbers/positive-float-trailing-decimal-point.json5 b/test/artifacts/json5-tests/numbers/positive-float-trailing-decimal-point.json5 new file mode 100644 index 0000000000..bee758a7bc --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-float-trailing-decimal-point.json5 @@ -0,0 +1 @@ ++5. diff --git a/test/artifacts/json5-tests/numbers/positive-float.json5 b/test/artifacts/json5-tests/numbers/positive-float.json5 new file mode 100644 index 0000000000..c5732cbcfb --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-float.json5 @@ -0,0 +1 @@ ++1.2 diff --git a/test/artifacts/json5-tests/numbers/positive-hexadecimal.json5 b/test/artifacts/json5-tests/numbers/positive-hexadecimal.json5 new file mode 100644 index 0000000000..c91ede9698 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-hexadecimal.json5 @@ -0,0 +1 @@ ++0xC8 diff --git a/test/artifacts/json5-tests/numbers/positive-infinity.json5 b/test/artifacts/json5-tests/numbers/positive-infinity.json5 new file mode 100644 index 0000000000..9bcb989f10 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-infinity.json5 @@ -0,0 +1 @@ ++Infinity diff --git a/test/artifacts/json5-tests/numbers/positive-integer.json5 b/test/artifacts/json5-tests/numbers/positive-integer.json5 new file mode 100644 index 0000000000..8ed01e070e --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-integer.json5 @@ -0,0 +1 @@ ++15 diff --git a/test/artifacts/json5-tests/numbers/positive-noctal.js b/test/artifacts/json5-tests/numbers/positive-noctal.js new file mode 100644 index 0000000000..2f450fce46 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-noctal.js @@ -0,0 +1 @@ ++098 diff --git a/test/artifacts/json5-tests/numbers/positive-octal.txt b/test/artifacts/json5-tests/numbers/positive-octal.txt new file mode 100644 index 0000000000..faa86009c5 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-octal.txt @@ -0,0 +1 @@ ++0123 diff --git a/test/artifacts/json5-tests/numbers/positive-zero-float-leading-decimal-point.json5 b/test/artifacts/json5-tests/numbers/positive-zero-float-leading-decimal-point.json5 new file mode 100644 index 0000000000..557bcde17b --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-zero-float-leading-decimal-point.json5 @@ -0,0 +1 @@ ++.0 diff --git a/test/artifacts/json5-tests/numbers/positive-zero-float-trailing-decimal-point.json5 b/test/artifacts/json5-tests/numbers/positive-zero-float-trailing-decimal-point.json5 new file mode 100644 index 0000000000..d8912d161a --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-zero-float-trailing-decimal-point.json5 @@ -0,0 +1 @@ ++0. diff --git a/test/artifacts/json5-tests/numbers/positive-zero-float.json5 b/test/artifacts/json5-tests/numbers/positive-zero-float.json5 new file mode 100644 index 0000000000..11e8402c56 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-zero-float.json5 @@ -0,0 +1 @@ ++0.0 diff --git a/test/artifacts/json5-tests/numbers/positive-zero-hexadecimal.json5 b/test/artifacts/json5-tests/numbers/positive-zero-hexadecimal.json5 new file mode 100644 index 0000000000..40a9ce6374 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-zero-hexadecimal.json5 @@ -0,0 +1 @@ ++0x0 diff --git a/test/artifacts/json5-tests/numbers/positive-zero-integer.json5 b/test/artifacts/json5-tests/numbers/positive-zero-integer.json5 new file mode 100644 index 0000000000..9317bcb621 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-zero-integer.json5 @@ -0,0 +1 @@ ++0 diff --git a/test/artifacts/json5-tests/numbers/positive-zero-octal.txt b/test/artifacts/json5-tests/numbers/positive-zero-octal.txt new file mode 100644 index 0000000000..80959e57a5 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/positive-zero-octal.txt @@ -0,0 +1 @@ ++00 diff --git a/test/artifacts/json5-tests/numbers/zero-float-leading-decimal-point.json5 b/test/artifacts/json5-tests/numbers/zero-float-leading-decimal-point.json5 new file mode 100644 index 0000000000..7d856fdb3f --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-float-leading-decimal-point.json5 @@ -0,0 +1 @@ +.0 diff --git a/test/artifacts/json5-tests/numbers/zero-float-trailing-decimal-point.json5 b/test/artifacts/json5-tests/numbers/zero-float-trailing-decimal-point.json5 new file mode 100644 index 0000000000..17a5757b17 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-float-trailing-decimal-point.json5 @@ -0,0 +1 @@ +0. diff --git a/test/artifacts/json5-tests/numbers/zero-float.json b/test/artifacts/json5-tests/numbers/zero-float.json new file mode 100644 index 0000000000..ba66466c2a --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-float.json @@ -0,0 +1 @@ +0.0 diff --git a/test/artifacts/json5-tests/numbers/zero-hexadecimal.json5 b/test/artifacts/json5-tests/numbers/zero-hexadecimal.json5 new file mode 100644 index 0000000000..9982566dc0 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-hexadecimal.json5 @@ -0,0 +1 @@ +0x0 diff --git a/test/artifacts/json5-tests/numbers/zero-integer-with-integer-exponent.json b/test/artifacts/json5-tests/numbers/zero-integer-with-integer-exponent.json new file mode 100644 index 0000000000..da219e3f10 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-integer-with-integer-exponent.json @@ -0,0 +1 @@ +0e23 diff --git a/test/artifacts/json5-tests/numbers/zero-integer.json b/test/artifacts/json5-tests/numbers/zero-integer.json new file mode 100644 index 0000000000..573541ac97 --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-integer.json @@ -0,0 +1 @@ +0 diff --git a/test/artifacts/json5-tests/numbers/zero-octal.txt b/test/artifacts/json5-tests/numbers/zero-octal.txt new file mode 100644 index 0000000000..4daddb72ff --- /dev/null +++ b/test/artifacts/json5-tests/numbers/zero-octal.txt @@ -0,0 +1 @@ +00 diff --git a/test/artifacts/json5-tests/objects/duplicate-keys.json b/test/artifacts/json5-tests/objects/duplicate-keys.json new file mode 100644 index 0000000000..bb0e4cc3d3 --- /dev/null +++ b/test/artifacts/json5-tests/objects/duplicate-keys.json @@ -0,0 +1,4 @@ +{ + "a": true, + "a": false +} diff --git a/test/artifacts/json5-tests/objects/empty-object.json b/test/artifacts/json5-tests/objects/empty-object.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/test/artifacts/json5-tests/objects/empty-object.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/illegal-unquoted-key-number.errorSpec b/test/artifacts/json5-tests/objects/illegal-unquoted-key-number.errorSpec new file mode 100644 index 0000000000..e44dc850d8 --- /dev/null +++ b/test/artifacts/json5-tests/objects/illegal-unquoted-key-number.errorSpec @@ -0,0 +1,6 @@ +{ + at: 7, + lineNumber: 2, + columnNumber: 5, + message: "Bad identifier as unquoted key" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/illegal-unquoted-key-number.txt b/test/artifacts/json5-tests/objects/illegal-unquoted-key-number.txt new file mode 100644 index 0000000000..aebcac2cde --- /dev/null +++ b/test/artifacts/json5-tests/objects/illegal-unquoted-key-number.txt @@ -0,0 +1,3 @@ +{ + 10twenty: "ten twenty" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/illegal-unquoted-key-symbol.errorSpec b/test/artifacts/json5-tests/objects/illegal-unquoted-key-symbol.errorSpec new file mode 100644 index 0000000000..95ba468319 --- /dev/null +++ b/test/artifacts/json5-tests/objects/illegal-unquoted-key-symbol.errorSpec @@ -0,0 +1,6 @@ +{ + at: 12, + lineNumber: 2, + columnNumber: 10, + message: "Expected ':' instead of '-'" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/illegal-unquoted-key-symbol.txt b/test/artifacts/json5-tests/objects/illegal-unquoted-key-symbol.txt new file mode 100644 index 0000000000..4cb2bd5c0e --- /dev/null +++ b/test/artifacts/json5-tests/objects/illegal-unquoted-key-symbol.txt @@ -0,0 +1,3 @@ +{ + multi-word: "multi-word" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/leading-comma-object.errorSpec b/test/artifacts/json5-tests/objects/leading-comma-object.errorSpec new file mode 100644 index 0000000000..e44dc850d8 --- /dev/null +++ b/test/artifacts/json5-tests/objects/leading-comma-object.errorSpec @@ -0,0 +1,6 @@ +{ + at: 7, + lineNumber: 2, + columnNumber: 5, + message: "Bad identifier as unquoted key" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/leading-comma-object.txt b/test/artifacts/json5-tests/objects/leading-comma-object.txt new file mode 100644 index 0000000000..bfb3c51fa4 --- /dev/null +++ b/test/artifacts/json5-tests/objects/leading-comma-object.txt @@ -0,0 +1,3 @@ +{ + ,"foo": "bar" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/lone-trailing-comma-object.txt b/test/artifacts/json5-tests/objects/lone-trailing-comma-object.txt new file mode 100644 index 0000000000..3f3f9f796b --- /dev/null +++ b/test/artifacts/json5-tests/objects/lone-trailing-comma-object.txt @@ -0,0 +1,3 @@ +{ + , +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/no-comma-object.txt b/test/artifacts/json5-tests/objects/no-comma-object.txt new file mode 100644 index 0000000000..c0738750db --- /dev/null +++ b/test/artifacts/json5-tests/objects/no-comma-object.txt @@ -0,0 +1,4 @@ +{ + "foo": "bar" + "hello": "world" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/reserved-unquoted-key.json5 b/test/artifacts/json5-tests/objects/reserved-unquoted-key.json5 new file mode 100644 index 0000000000..4b80a63b4c --- /dev/null +++ b/test/artifacts/json5-tests/objects/reserved-unquoted-key.json5 @@ -0,0 +1,3 @@ +{ + while: true +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/single-quoted-key.json5 b/test/artifacts/json5-tests/objects/single-quoted-key.json5 new file mode 100644 index 0000000000..842ca19c6a --- /dev/null +++ b/test/artifacts/json5-tests/objects/single-quoted-key.json5 @@ -0,0 +1,3 @@ +{ + 'hello': "world" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/trailing-comma-object.json5 b/test/artifacts/json5-tests/objects/trailing-comma-object.json5 new file mode 100644 index 0000000000..ab61ba76e1 --- /dev/null +++ b/test/artifacts/json5-tests/objects/trailing-comma-object.json5 @@ -0,0 +1,3 @@ +{ + "foo": "bar", +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/objects/unquoted-keys.json5 b/test/artifacts/json5-tests/objects/unquoted-keys.json5 new file mode 100644 index 0000000000..0c06f3fc76 --- /dev/null +++ b/test/artifacts/json5-tests/objects/unquoted-keys.json5 @@ -0,0 +1,8 @@ +{ + hello: "world", + _: "underscore", + $: "dollar sign", + one1: "numerals", + _$_: "multiple symbols", + $_$hello123world_$_: "mixed" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/strings/escaped-single-quoted-string.json5 b/test/artifacts/json5-tests/strings/escaped-single-quoted-string.json5 new file mode 100644 index 0000000000..1c799101b7 --- /dev/null +++ b/test/artifacts/json5-tests/strings/escaped-single-quoted-string.json5 @@ -0,0 +1 @@ +'I can\'t wait' \ No newline at end of file diff --git a/test/artifacts/json5-tests/strings/multi-line-string.json5 b/test/artifacts/json5-tests/strings/multi-line-string.json5 new file mode 100644 index 0000000000..964dc2decb --- /dev/null +++ b/test/artifacts/json5-tests/strings/multi-line-string.json5 @@ -0,0 +1,2 @@ +'hello\ + world' \ No newline at end of file diff --git a/test/artifacts/json5-tests/strings/no-comma-array.errorSpec b/test/artifacts/json5-tests/strings/no-comma-array.errorSpec new file mode 100644 index 0000000000..98355610e5 --- /dev/null +++ b/test/artifacts/json5-tests/strings/no-comma-array.errorSpec @@ -0,0 +1,6 @@ +{ + at: 16, + lineNumber: 3, + columNumber: 5, + message: "Expected ']' instead of 'f'" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/strings/single-quoted-string.json5 b/test/artifacts/json5-tests/strings/single-quoted-string.json5 new file mode 100644 index 0000000000..5dadd333cc --- /dev/null +++ b/test/artifacts/json5-tests/strings/single-quoted-string.json5 @@ -0,0 +1 @@ +'hello world' \ No newline at end of file diff --git a/test/artifacts/json5-tests/strings/unescaped-multi-line-string.errorSpec b/test/artifacts/json5-tests/strings/unescaped-multi-line-string.errorSpec new file mode 100644 index 0000000000..a85f1ad185 --- /dev/null +++ b/test/artifacts/json5-tests/strings/unescaped-multi-line-string.errorSpec @@ -0,0 +1,6 @@ +{ + at: 5, + lineNumber: 2, + columnNumber: 0, + message: "Bad string" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/strings/unescaped-multi-line-string.txt b/test/artifacts/json5-tests/strings/unescaped-multi-line-string.txt new file mode 100644 index 0000000000..7325139a82 --- /dev/null +++ b/test/artifacts/json5-tests/strings/unescaped-multi-line-string.txt @@ -0,0 +1,2 @@ +"foo +bar" diff --git a/test/artifacts/json5-tests/todo/unicode-escaped-unquoted-key.json5 b/test/artifacts/json5-tests/todo/unicode-escaped-unquoted-key.json5 new file mode 100644 index 0000000000..56c3457113 --- /dev/null +++ b/test/artifacts/json5-tests/todo/unicode-escaped-unquoted-key.json5 @@ -0,0 +1,3 @@ +{ + sig\u03A3ma: "the sum of all things" +} \ No newline at end of file diff --git a/test/artifacts/json5-tests/todo/unicode-unquoted-key.json5 b/test/artifacts/json5-tests/todo/unicode-unquoted-key.json5 new file mode 100644 index 0000000000..98382e6997 --- /dev/null +++ b/test/artifacts/json5-tests/todo/unicode-unquoted-key.json5 @@ -0,0 +1,3 @@ +{ + ümlåût: "that's not really an ümlaüt, but this is" +} \ No newline at end of file diff --git a/test/components/Administrator.cfc b/test/components/Administrator.cfc index c695f62e07..cfc16c4ac7 100644 --- a/test/components/Administrator.cfc +++ b/test/components/Administrator.cfc @@ -997,7 +997,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ var columnlists = ["database","debug","dump","exception","implicitAccess","queryUsage","timer","tracing"]; for(var columnlist in columnlists){ - assertEquals(adminWebDebuggingSetting["#columnlist#"] EQ adminDebuggingSetting["#columnlist#"], true); + // assertEquals(adminWebDebuggingSetting["#columnlist#"] EQ adminDebuggingSetting["#columnlist#"], true); } }); }); diff --git a/test/functions/CreateULID.cfc b/test/functions/CreateULID.cfc index 044b7a860e..0e41e8e1a1 100644 --- a/test/functions/CreateULID.cfc +++ b/test/functions/CreateULID.cfc @@ -1,6 +1,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" { - variables.rounds = 10000; + variables.rounds = 10; variables.mysql = server.getDatasource("mysql"); function run( testResults , testBox ) { diff --git a/test/functions/DeSerializeJSON.cfc b/test/functions/DeSerializeJSON.cfc index 21b9101ebd..b04230eadb 100644 --- a/test/functions/DeSerializeJSON.cfc +++ b/test/functions/DeSerializeJSON.cfc @@ -163,6 +163,66 @@ expect(result.filecontent.trim()).toBe("DESERIALISED"); }); }); + + describe( title="Test suite for comments", body=function() { + // we allowing, because other parser do as well (some) + it( "deserializeJson should allow json5, block comment by default", function(){ + var str = '{ + "name" : "lucee" + /* + block comment + */ + }'; + structKeyExists( deserializeJson( str ), "name" ); + }); + + it( "deserializeJson should allow json5, block comment when format is set to json5", function(){ + var str = '{ + "name" : "lucee" + /* + block comment + */ + }'; + structKeyExists( deserializeJson( str,true,"json5" ), "name" ); + }); + + it( "deserializeJson should NOT allow json5, block comment when format is set to json", function(){ + var str = '{ + "name" : "lucee" + /* + block comment + */ + }'; + expect( function(){ + structKeyExists( deserializeJson( str,true,"json" ), "name" ) + }).toThrow(); + }); + + + // we allowing, because other parser do as well (some) + it( "deserializeJson should allow json5, single line comment by default", function(){ + var str = '{ + "name" : "lucee" // single line + }'; + structKeyExists( deserializeJson( str ), "name" ) ; + }); + + it( "deserializeJson should allow json5, single line comment when format is set to json5", function(){ + var str = '{ + "name" : "lucee" // single line + }'; + structKeyExists( deserializeJson( str,true,"json5" ), "name" ); + }); + + it( "deserializeJson should NOT allow json5, single line comment when format is set to json", function(){ + var str = '{ + "name" : "lucee" // single line + }'; + expect( function(){ + structKeyExists( deserializeJson( str,true,"json" ), "name" ) + }).toThrow(); + }); + }); } private function toHex(nbr){ diff --git a/test/functions/FileSetAccessMode.cfc b/test/functions/FileSetAccessMode.cfc new file mode 100644 index 0000000000..ff5fa2a3f8 --- /dev/null +++ b/test/functions/FileSetAccessMode.cfc @@ -0,0 +1,85 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function beforeAll(){ + variables.dir = getTempDirectory() & "fileSetAccessMode/"; + if ( directoryExists( dir ) ) + directoryDelete( dir, true ); + directoryCreate( dir ); + }; + + function afterAll(){ + if ( directoryExists( dir ) ){ + directoryDelete( dir, true ); + }; + }; + + function run( testResults, testBox ){ + describe( "fileSetAccessMode", function(){ + it( title="test access modes", skip=isNotUnix(), body=function(){ + var tests = []; + + arrayAppend( tests, _dir( dir, "755", "755" ) ); + arrayAppend( tests, _dir( dir, "777", "777" ) ); + arrayAppend( tests, _dir( dir, "644", "644" ) ); + + var files = directoryList( dir, true, "query"); + var st = QueryToStruct( files, "name" ); + + loop array=st index="local.item"{ + systemOutput( item, true ); + } + + arrayAppend( tests, _file( dir, "644.txt", "644" ) ); + arrayAppend( tests, _file( dir, "743.txt", "743" ) ); + arrayAppend( tests, _file( dir, "043.txt", "443" ) ); + arrayAppend( tests, _file( dir, "400.txt", "400" ) ); + + var files = directoryList( dir, true, "query"); + var st = QueryToStruct( files, "name" ); + + loop collection=st item="local.item"{ + systemOutput( item, true ); + } + loop array=tests item="local.test" { + systemOutput( test, true ); + } + loop array=tests item="local.test" { + systemOutput( test, true ); + var key = mid( test.name, len( dir ) + 1 ); + systemOutput( key, true ); + expect( st ).toHaveKey( key ); + systemOutput( st[ key ], true ); + expect( test.mode ).toBe( st[ key ].mode ); + } + + }); + } ); + } + + private function _dir( parent, name, mode ){ + var dir = parent & name; + directoryCreate( dir ); + fileSetAccessMode( dir, mode ); + return { + name: dir, + mode: mode, + type: "dir" + }; + } + + private function _file( parent, name, mode ){ + var file = parent & name; + fileWrite( file, "" ); + fileSetAccessMode( file, mode ); + return { + name: file, + mode: mode, + type: "file" + }; + } + + private function isNotUnix(){ + return (server.os.name == "windows"); + } + +} diff --git a/test/functions/IsJson.cfc b/test/functions/IsJson.cfc index 2ca5aa6763..6599176c71 100644 --- a/test/functions/IsJson.cfc +++ b/test/functions/IsJson.cfc @@ -108,4 +108,45 @@ component extends="org.lucee.cfml.test.LuceeTestCase" { {a:1} ')); } + + // uses files from https://github.com/json5/json5-tests + private function testSuite (){ + var json5TestDir = expandPath( getDirectoryFromPath( getCurrentTemplatePath() ) & "../artifacts/json5-tests" ); + var json5Tests = directoryList( path=json5TestDir, recurse=true, listinfo="path", type="file" ); + var canJson5 = ( len( getFunctionData( "isJson" ).arguments ) == 2 ); + + expect( ArrayLen( json5Tests ) ).toBeGT( 0 ); + systemOutput( "", true); + systemOutput( "running json5 testsuite with #ArrayLen( json5Tests )# tests", true); + + loop array=json5Tests item="local.test"{ + var fileType = listLast( test, "." ); + var expectedResult=""; + switch( fileType ) { + case "json": + expectedResult = true; + break; + case "json5": + expectedResult = canJson5; + break; + case "txt": + expectedResult = false; + break; + case "js": + expectedResult = false; + break; + case "errorspec": + expectedResult = true; + break; + default: + expectedResult = false; + } + // due to false positives, this function is private / disabled LDEV-5017 + if ( isJson( fileRead( test ) ) neq expectedResult ) { + systemOutput( "expected: " & expectedResult & " isJson:" & isJson( fileRead( test ) ) & " " & test, true); + } + //TBD add second argument, version for json5 support + //expect( isJson( fileRead( test ) ) ).toBe( expectedResult, test ); + } + } } diff --git a/test/functions/IsJson2.cfc b/test/functions/IsJson2.cfc new file mode 100644 index 0000000000..188f11d0a5 --- /dev/null +++ b/test/functions/IsJson2.cfc @@ -0,0 +1,64 @@ +component extends = "org.lucee.cfml.test.LuceeTestCase" label="json" { + + function run( testResults, testBox ){ + describe( "json5 testing", function(){ + + + // we allowing, because other parser do as well (some) + it( "isJson should allow json5, block comment by default", function(){ + var str = '{ + "name" : "lucee" + /* + block comment + */ + }'; + expect( isJson( str ) ).toBeTrue(); + }); + + it( "isJson should allow json5, block comment when format is set to [json5]", function(){ + var str = '{ + "name" : "lucee" + /* + block comment + */ + }'; + expect( isJson( str,"json5" ) ).toBeTrue(); + }); + + it( "isJson shouldn't allow json5, block comment when format is set to json", function(){ + var str = '{ + "name" : "lucee" + /* + block comment + */ + }'; + expect( isJson( str,"json" ) ).toBeFalse(); + }); + + // we allowing, because other parser do as well (some) + it( "isJson should allow json5 inline by default", function(){ + var str = '{ + "name" : "lucee" // inline comment + }'; + expect( isJson( str ) ).toBeTrue(); + }); + + it( "isJson should allow json5 inline when format is set to [json5]", function(){ + var str = '{ + "name" : "lucee" // inline comment + }'; + expect( isJson( str,"json5" ) ).toBeTrue(); + }); + + it( "isJson shouldn't allow json5 inline when format is set to [json]", function(){ + var str = '{ + "name" : "lucee" // inline comment + }'; + expect( isJson( str,"json" ) ).toBeFalse(); + }); + + + } ); + } + +} \ No newline at end of file diff --git a/test/tickets/LDEV1202/Test.lucee b/test/tickets/LDEV1202/Test.lucee deleted file mode 100644 index 651a0e1515..0000000000 --- a/test/tickets/LDEV1202/Test.lucee +++ /dev/null @@ -1,11 +0,0 @@ -component { - private function inner(){ - return {}; - } - function elvis(){ - return inner().elvis?:''; - } - function isItNull(){ - return isNull(inner().isItNull); - } -} \ No newline at end of file diff --git a/test/tickets/LDEV1323/Application.cfc b/test/tickets/LDEV1323/Application.cfc index 8ced6e0827..2240784eaa 100644 --- a/test/tickets/LDEV1323/Application.cfc +++ b/test/tickets/LDEV1323/Application.cfc @@ -1,5 +1,5 @@ component { - this.name = "test3"; + this.name = "ldev1323"; mySQL= getCredentials(); this.datasource = { type: "mysql" diff --git a/test/tickets/LDEV1440/Application.cfc b/test/tickets/LDEV1440/Application.cfc index 9dddb78eb7..4f50fc3550 100644 --- a/test/tickets/LDEV1440/Application.cfc +++ b/test/tickets/LDEV1440/Application.cfc @@ -1,5 +1,5 @@ component { - this.name = "test"; + this.name = "ldev1440"; mySQL = getCredentials(); this.datasource = mySQL; @@ -10,13 +10,13 @@ component { function onApplicationStart(){ query{ - echo("DROP TABLE IF EXISTS usersDetails"); + echo("DROP TABLE IF EXISTS ldev1440"); } query{ - echo("CREATE TABLE usersDetails( id INT , name VARCHAR(30) )"); + echo("CREATE TABLE ldev1440( id INT , name VARCHAR(30) )"); } query{ - echo("INSERT INTO usersDetails VALUES(1,'micha'), (2, 'lucee')"); + echo("INSERT INTO ldev1440 VALUES(1,'micha'), (2, 'lucee')"); } } diff --git a/test/tickets/LDEV1440/test.cfm b/test/tickets/LDEV1440/test.cfm index 0bc0534bf1..937e9cece9 100644 --- a/test/tickets/LDEV1440/test.cfm +++ b/test/tickets/LDEV1440/test.cfm @@ -1,5 +1,5 @@ - select name, name from usersDetails + select name, name from ldev1440 #ListLen(qry.ColumnList)# diff --git a/test/tickets/LDEV1525.cfc b/test/tickets/LDEV1525.cfc new file mode 100644 index 0000000000..bc38719be9 --- /dev/null +++ b/test/tickets/LDEV1525.cfc @@ -0,0 +1,50 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" label="qoq" { + function beforeAll(){ + variables.uri = createURI("LDEV1525"); + } + function run( testResults , testBox ) { + describe( "test case for LDEV-1525", function() { + it(title = "Checking QoQ while missing column (native)", body = function( currentSpec ) { + local.result = _InternalRequest( + template:"#variables.uri#/ldev1525.cfm", + url: "scene=native" + ); + var q = deserializeJSON( local.result.filecontent, false ); + expect( QueryColumnExists(q, "column_0") ).toBeTrue(); + }); + + it(title = "Checking QoQ while missing column (native) with order by", body = function( currentSpec ) { + local.result = _InternalRequest( + template:"#variables.uri#/ldev1525.cfm", + url: "scene=native&orderby=true" + ); + var q = deserializeJSON( local.result.filecontent, false ); + expect( QueryColumnExists(q, "column_0") ).toBeTrue(); + }); + + it(title = "Checking QoQ while missing column (hsqldb)", body = function( currentSpec ) { + local.result = _InternalRequest( + template:"#variables.uri#/ldev1525.cfm", + url: "scene=hsqldb" + ); + var q = deserializeJSON( local.result.filecontent, false ); + expect( QueryColumnExists( q, "c1") ).toBeTrue(); + }); + + it(title = "Checking QoQ while missing column (hsqldb) with order by", body = function( currentSpec ) { + local.result = _InternalRequest( + template:"#variables.uri#/ldev1525.cfm", + url: "scene=hsqldb&orderby=true" + ); + systemOutput( local.result.filecontent, true ); + expect ( local.result.filecontent).toInclude("invalid ORDER BY expression"); + }); + + + }); + } + private string function createURI(string calledName){ + var baseURI="/test/#listLast(getDirectoryFromPath(getCurrenttemplatepath()),"\/")#/"; + return baseURI&""&calledName; + } +} \ No newline at end of file diff --git a/test/tickets/LDEV1525/ldev1525.cfm b/test/tickets/LDEV1525/ldev1525.cfm new file mode 100644 index 0000000000..43a15cdc34 --- /dev/null +++ b/test/tickets/LDEV1525/ldev1525.cfm @@ -0,0 +1,33 @@ + + + + + + + SELECT count(*),id + FROM q + group by id + + order by name + + + + + SELECT count(*), q1.id + FROM q q1, q q2 + WHERE q1.id = q2.id + group by q1.id + + order by q1.name + + + + + echo(qoq.toJson()); + + + + echo(cfcatch.stacktrace); + + + diff --git a/test/tickets/LDEV1525/test.cfm b/test/tickets/LDEV1525/test.cfm deleted file mode 100644 index e015520dbd..0000000000 --- a/test/tickets/LDEV1525/test.cfm +++ /dev/null @@ -1,15 +0,0 @@ - - - - - SELECT count(*),id - FROM q - group by id - ORDER BY name - - - - #cfcatch.detail# - - - diff --git a/test/tickets/LDEV1614/Application.cfc b/test/tickets/LDEV1614/Application.cfc index 88074b1d04..3390e30064 100644 --- a/test/tickets/LDEV1614/Application.cfc +++ b/test/tickets/LDEV1614/Application.cfc @@ -1,4 +1,5 @@ component { + this.name = "LDEV1614"; // place holder to prevent the testFilter checking to see if these files are test cases public function onRequestStart() { diff --git a/test/tickets/LDEV1650/Application.cfc b/test/tickets/LDEV1650/Application.cfc index 54357cf7b3..160157abed 100644 --- a/test/tickets/LDEV1650/Application.cfc +++ b/test/tickets/LDEV1650/Application.cfc @@ -2,7 +2,7 @@ component { this.name = createUUID(); mySQL= getCredentials(); - this.datasources["testdb"] = = mySQL; + this.datasources["testdb"] = mySQL; public function onRequestStart() { diff --git a/test/tickets/LDEV1671/Application.cfc b/test/tickets/LDEV1671/Application.cfc index 5862d932a2..c18ea9d21b 100644 --- a/test/tickets/LDEV1671/Application.cfc +++ b/test/tickets/LDEV1671/Application.cfc @@ -1,7 +1,7 @@ component { this.name = "sample"; mySQL = getCredentials(); - this.datasource = = mySQL; + this.datasource = mySQL; public function onRequestStart() { setting requesttimeout=10 showdebugOutput=false; diff --git a/test/tickets/_LDEV1867.cfc b/test/tickets/LDEV1867.cfc similarity index 100% rename from test/tickets/_LDEV1867.cfc rename to test/tickets/LDEV1867.cfc diff --git a/test/tickets/_LDEV1953.cfc b/test/tickets/LDEV1953.cfc similarity index 96% rename from test/tickets/_LDEV1953.cfc rename to test/tickets/LDEV1953.cfc index f3d9a00cf9..2601436df2 100644 --- a/test/tickets/_LDEV1953.cfc +++ b/test/tickets/LDEV1953.cfc @@ -13,7 +13,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { it( title='Checking mysql with zeroDateTimeBehavior as convertToNull',skip=isMySqlNotSupported(),body=function( currentSpec ) { var uri = createURI("LDEV1953"); var result = _InternalRequest( - template:"#uri#/test.cfm" + template:"#uri#/ldev1953.cfm" ); expect(result.fileContent.trim()).toBe("123"); }); diff --git a/test/tickets/LDEV1953/Application.cfc b/test/tickets/LDEV1953/Application.cfc index b7e1c45198..520eb9998a 100644 --- a/test/tickets/LDEV1953/Application.cfc +++ b/test/tickets/LDEV1953/Application.cfc @@ -1,5 +1,5 @@ component { - this.name = "test345"; + this.name = "LDEV1953"; mySQL= getCredentials(); mySQL.storage = true; @@ -12,13 +12,13 @@ component { function onApplicationStart(){ query{ - echo("DROP TABLE IF EXISTS users"); + echo("DROP TABLE IF EXISTS LDEV1953"); } query{ - echo("CREATE TABLE users( id INT , name VARCHAR(30) )"); + echo("CREATE TABLE LDEV1953( id INT , name VARCHAR(30) )"); } query{ - echo("INSERT INTO users VALUES(123,'micha'), (999, 'lucee')"); + echo("INSERT INTO LDEV1953 VALUES(123,'micha'), (999, 'lucee')"); } } diff --git a/test/tickets/LDEV1953/test.cfm b/test/tickets/LDEV1953/ldev1953.cfm similarity index 84% rename from test/tickets/LDEV1953/test.cfm rename to test/tickets/LDEV1953/ldev1953.cfm index 805da65675..a0314255b0 100644 --- a/test/tickets/LDEV1953/test.cfm +++ b/test/tickets/LDEV1953/ldev1953.cfm @@ -1,7 +1,7 @@ - select * from users + select * from LDEV1953 #qry.id# diff --git a/test/tickets/_LDEV2057.cfc b/test/tickets/LDEV2057.cfc similarity index 100% rename from test/tickets/_LDEV2057.cfc rename to test/tickets/LDEV2057.cfc diff --git a/test/tickets/LDEV2374.cfc b/test/tickets/LDEV2374.cfc new file mode 100644 index 0000000000..13c78fb257 --- /dev/null +++ b/test/tickets/LDEV2374.cfc @@ -0,0 +1,52 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { + + function beforeAll() { + variables.uri = createURI("LDEV2374"); + } + + function run( testResults , testBox ) { + describe( "Test suite for LDEV-2374, mergeFormUrlAsStruct=false", function() { + it( title='Checking url scope with multiple dot notation [&ts=10&res...temp=1]', body=function( currentSpec ) { + var qs = "ts=10&res...temp=1"; + local.result = _InternalRequest( + template : "#uri#/LDEV2374.cfm", + urls: qs + ); + var parsedUrl = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedUrl, true ); + + expect( structCount( parsedUrl ) ).toBe( 2 ); + expect( parsedUrl ).toHaveKey( "ts" ); + expect( parsedUrl ).notToHaveKey( "res" ); + expect( parsedUrl ).toHaveKey( "res...temp" ); + }); + + it( title='Checking url scope with single dot notation [&mt=10&test.temp=lucee]', body=function( currentSpec ) { + var qs = "mt=10&test.temp=lucee"; + local.result = _InternalRequest( + template : "#uri#/LDEV2374.cfm", + urls: qs + ); + + var parsedUrl = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedUrl, true ); + + expect( structCount( parsedUrl ) ).toBe( 2 ); + expect( parsedUrl ).toHaveKey( "mt" ); + expect( parsedUrl ).toHaveKey( "test.temp" ); + }); + }); + } + + private string function createURI(string calledName){ + var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrentTemplatepath()),"\/")#/"; + return baseURI&""&calledName; + } + +} diff --git a/test/tickets/LDEV2374/Application.cfc b/test/tickets/LDEV2374/Application.cfc new file mode 100644 index 0000000000..ec337bed08 --- /dev/null +++ b/test/tickets/LDEV2374/Application.cfc @@ -0,0 +1,3 @@ +component { + this.name = "LDEV2374"; +} diff --git a/test/tickets/LDEV2374/LDEV2374.cfm b/test/tickets/LDEV2374/LDEV2374.cfm new file mode 100644 index 0000000000..50517fe89a --- /dev/null +++ b/test/tickets/LDEV2374/LDEV2374.cfm @@ -0,0 +1,3 @@ + + echo(url.toJson()); + \ No newline at end of file diff --git a/test/tickets/LDEV2423.cfc b/test/tickets/LDEV2423.cfc index 0eeb20b6a2..34acd0d8ef 100644 --- a/test/tickets/LDEV2423.cfc +++ b/test/tickets/LDEV2423.cfc @@ -1,25 +1,101 @@ -component extends = "org.lucee.cfml.test.LuceeTestCase" labels="mssql" skip="true" { +component extends = "org.lucee.cfml.test.LuceeTestCase" labels="mssql" { function beforeAll(){ variables.uri = createURI("LDEV2423"); } function run( testResults, testBox ){ - describe( "Test case for LDEV2423 ", function(){ - it(title = "cfqueryparam not working with CF_SQL_FLOAT for negative exponent numbers", skip=notHasMssql(), body = function(){ + describe( "Test case for LDEV2423 MSSQL", function(){ + it(title = "cfqueryparam not working with CF_SQL_FLOAT for negative exponent numbers (mssql)", skip=true, body = function(){ //skip=notHas("mssql") local.result = _InternalRequest( template : "#uri#\test.cfm", - forms : { Scene = 1 } + forms : { + Scene = "CF_SQL_FLOAT", + db: "mssql" + } + ); + expect(trim(result.filecontent)).tobe("1"); // TODO this returns 0 + }); + + it(title = "cfqueryparam not working with CF_SQL_NUMERIC for negative exponent numbers (mssql)", skip=notHas("mssql"), body = function(){ + local.result = _InternalRequest( + template : "#uri#\test.cfm", + forms : { + Scene = "CF_SQL_NUMERIC", + db: "mssql" + } + ); + expect(trim(result.filecontent)).tobe("1"); + }); + }); + + describe( "Test case for LDEV2423 MYSQL", function(){ + it(title = "cfqueryparam not working with CF_SQL_FLOAT for negative exponent numbers (mysql)", skip=notHas("mysql"), body = function(){ + local.result = _InternalRequest( + template : "#uri#\test.cfm", + forms : { + Scene = "CF_SQL_FLOAT", + db: "mysql" + } ); expect(trim(result.filecontent)).tobe("1"); }); - it(title = "cfqueryparam not working with CF_SQL_NUMERIC for negative exponent numbers", skip=notHasMssql(), body = function(){ + it(title = "cfqueryparam not working with CF_SQL_NUMERIC for negative exponent numbers (mysql)", skip=notHas("mysql"), body = function(){ local.result = _InternalRequest( template : "#uri#\test.cfm", - forms : { Scene = 2 } + forms : { + Scene = "CF_SQL_NUMERIC", + db: "mysql" + } + ); + expect(trim(result.filecontent)).tobe("1"); + }); + }); + + describe( "Test case for LDEV2423 qoq", function(){ + it(title = "round trip exponent number, qoq",body = function(){ + var q = queryNew( "id" ); + queryAddRow( q ); + var res = QueryExecute( "SELECT 1E-8 as num from q",{},{dbtype="query", result="local.result"}); + expect( res.recordCount ).toBe( 1 ); + expect( res.num ).toBe( 1E-8 ); + }); + + it(title = "cfqueryparam not working with CF_SQL_FLOAT for negative exponent numbers, qoq", body = function(){ + var q = queryNew( "id" ); + queryAddRow( q ); + var res = QueryExecute( + "SELECT 1 FROM q WHERE 1E-8 = :FloatingPoint", + { + FloatingPoint = { + cfsqltype="CF_SQL_FLOAT", + value="0.00000001" + } + }, + {dbtype="query", result="local.result"} + ); + systemOutput( res, true ); + systemOutput( result, true); + expect( res.recordCount ).toBe( 1 ); + }); + + it(title = "cfqueryparam not working with CF_SQL_NUMERIC for negative exponent numbers, qoq", body = function(){ + var q = queryNew( "id" ); + queryAddRow( q ); + var res = QueryExecute( + "SELECT 1 FROM q WHERE 1E-8 = :FloatingPoint", + { + FloatingPoint = { + cfsqltype="CF_SQL_NUMERIC", + value="0.00000001" + } + }, + {dbtype="query", result="local.result"} ); - expect(trim(result.filecontent)).tobe("0"); + systemOutput( res, true ); + systemOutput( result, true); + expect( res.recordCount ).toBe( 1 ); }); }); } @@ -29,7 +105,7 @@ component extends = "org.lucee.cfml.test.LuceeTestCase" labels="mssql" skip="tru return baseURI&""&calledName; } - private function notHasMssql() { - return structCount(server.getDatasource("mssql")) == 0; + private function notHas(db) { + return structCount(server.getDatasource(arguments.db)) == 0; } } \ No newline at end of file diff --git a/test/tickets/LDEV2423/Application.cfc b/test/tickets/LDEV2423/Application.cfc index 0bf95f26eb..e5e5bb6f04 100644 --- a/test/tickets/LDEV2423/Application.cfc +++ b/test/tickets/LDEV2423/Application.cfc @@ -1,7 +1,7 @@ component { - this.name = "luceetest"; - this.datasources["luceedb"] = server.getDatasource("mssql"); - this.datasource = "luceedb"; + this.name = "ldev2403"; + this.datasources["ldev2403"] = server.getDatasource(db); + this.datasource = "ldev2403"; public function onRequestStart() { setting requesttimeout=10; diff --git a/test/tickets/LDEV2423/test.cfm b/test/tickets/LDEV2423/test.cfm index 1b771355b8..6ca1223b43 100644 --- a/test/tickets/LDEV2423/test.cfm +++ b/test/tickets/LDEV2423/test.cfm @@ -1,34 +1,34 @@ param name="FORM.scene" default=""; - - if(form.scene eq 1){ + + if (form.scene eq "CF_SQL_FLOAT"){ qQuery = QueryExecute( - "SELECT 1 WHERE 1E-8 = :FloatingPoint", - { - FloatingPoint = { - cfsqltype="CF_SQL_FLOAT", + "SELECT 1 WHERE 1E-8 = :FloatingPoint", + { + FloatingPoint = { + cfsqltype="CF_SQL_FLOAT", value="0.00000001" } }, { - datasource="luceedb" + datasource="ldev2403", + result="result" } ); writeOutput(qQuery.recordcount); - } - - if(form.scene eq 2){ + } else if (form.scene eq "CF_SQL_NUMERIC"){ qQuery1 = QueryExecute( - "SELECT 1 WHERE 1E-8 = :FloatingPoint", + "SELECT 1 WHERE 1E-8 = :FloatingPoint", { - FloatingPoint = { - cfsqltype="CF_SQL_NUMERIC", + FloatingPoint = { + cfsqltype="CF_SQL_NUMERIC", value="0.00000001" - } + } }, { - datasource="luceedb" + datasource="ldev2403", + result: "result" } ); writeOutput(qQuery1.recordcount); diff --git a/test/tickets/LDEV2549.cfc b/test/tickets/LDEV2549.cfc new file mode 100644 index 0000000000..151ab3c74f --- /dev/null +++ b/test/tickets/LDEV2549.cfc @@ -0,0 +1,56 @@ +component extends = "org.lucee.cfml.test.LuceeTestCase" labels="mssql" { + + function beforeAll() { + variables.uri = createURI("LDEV2549"); + } + + function run( testResults , testBox ) { + describe( title="test suite for LDEV2549",skip=isNotSupported("mssql"), body=function() { + it(title = "query param without CFSQLTYPE='cf_sql_date' for date type value (mssql)", skip=true, body = function( currentSpec ) { + // query fails trying to convert {ts '1996-10-27 00:00:00'} to a date time + local.result = _InternalRequest( + template : "#uri#/LDEV2549.cfm", + forms: {scene: "varchar", db: "mssql"} + ); + expect(1).toBe(result.filecontent); + }); + + it(title = "query param with CFSQLTYPE='cf_sql_date' for date type value (mssql)", body = function( currentSpec ) { + local.result = _InternalRequest( + template : "#uri#/LDEV2549.cfm", + forms: {scene: "date", db: "mssql"} + ); + expect(2).toBe(local.result.filecontent); + }); + }); + + describe( title="test suite for LDEV2549",skip=isNotSupported("mysql"), body=function() { + it(title = "query param without CFSQLTYPE='cf_sql_date' for date type value (mysql)", skip=true, body = function( currentSpec ) { + // query fails trying to convert {ts '1996-10-27 00:00:00'} to a date time + local.result = _InternalRequest( + template : "#uri#/LDEV2549.cfm", + forms: {scene: "varchar", db: "mysql"} + ); + expect(1).toBe(result.filecontent); + }); + + it(title = "query param with CFSQLTYPE='cf_sql_date' for date type value (mysql)", body = function( currentSpec ) { + local.result = _InternalRequest( + template : "#uri#/LDEV2549.cfm", + forms: {scene: "date", db: "mysql"} + ); + expect(2).toBe(local.result.filecontent); + }); + }); + } + + private string function createURI(string calledName){ + var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrentTemplatepath()),"\/")#/"; + return baseURI&""&calledName; + } + + private boolean function isNotSupported( db ) { + // getting the credentials from the environment variables + return ( structCount( server.getDatasource( arguments.db) ) eq 0 ); + } +} \ No newline at end of file diff --git a/test/tickets/LDEV2549/Application.cfc b/test/tickets/LDEV2549/Application.cfc index a1d8234920..20ab86b49e 100644 --- a/test/tickets/LDEV2549/Application.cfc +++ b/test/tickets/LDEV2549/Application.cfc @@ -1,7 +1,7 @@ component { - - this.name = "Luceetest"; - this.datasources["LDEV2549_DSN"] = server.getDatasource("mssql"); + param name="form.db"; + this.name = "LDEV2549"; + this.datasources["LDEV2549_DSN"] = server.getDatasource(form.db); this.datasource = "LDEV2549_DSN"; public function onRequestStart() { @@ -14,16 +14,19 @@ component { } query{ echo("INSERT INTO LDEV2549 VALUES( '1996-10-27','2')"); + } + query{ echo("INSERT INTO LDEV2549 VALUES( '1998-10-20','1')"); } + query{ - echo("DROP TABLE IF EXISTS mytest"); + echo("DROP TABLE IF EXISTS LDEV2549_2"); } query{ - echo("CREATE TABLE mytest( day date, block int)"); + echo("CREATE TABLE LDEV2549_2( day date, block int)"); } query{ - echo("INSERT INTO mytest VALUES( '1996-10-27',1)"); + echo("INSERT INTO LDEV2549_2 VALUES( '1996-10-27',1)"); } } diff --git a/test/tickets/LDEV2549/LDEV2549.cfm b/test/tickets/LDEV2549/LDEV2549.cfm index 7c9284c787..594a5a186d 100644 --- a/test/tickets/LDEV2549/LDEV2549.cfm +++ b/test/tickets/LDEV2549/LDEV2549.cfm @@ -1,25 +1,32 @@ - - - - select * from mytest - + + + select * from LDEV2549_2 + - + + + systemOutput("", true); + systemOutput("#db#, #scene# [#test1.day#]", true); + param = { + value: test1.day + }; + if (form.scene eq "date"){ + param.cfsqltype = "cf_sql_date"; + } + + - SELECT COUNT(DISTINCT id) AS deviceCount, MAX(id) AS maxDeviceID FROM LDEV2549 WHERE day = + SELECT COUNT(DISTINCT id) AS deviceCount, MAX(id) AS maxDeviceID + FROM LDEV2549 + WHERE day = #test2.maxDeviceID# - - - - - - select * from mytest - - - - SELECT COUNT(DISTINCT id) AS deviceCount, MAX(id) AS maxDeviceID FROM LDEV2549 WHERE day = - - #test2.maxDeviceID# - - + + + systemOutput(cfcatch, true); + + + + + + \ No newline at end of file diff --git a/test/tickets/_LDEV2616.cfc b/test/tickets/LDEV2616.cfc similarity index 50% rename from test/tickets/_LDEV2616.cfc rename to test/tickets/LDEV2616.cfc index 1b03fd940a..a520edabbc 100644 --- a/test/tickets/_LDEV2616.cfc +++ b/test/tickets/LDEV2616.cfc @@ -5,22 +5,20 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ } function run( testResults , testBox ) { - describe( "test case for LDEV-2581", function() { + describe( "test case for LDEV-2616", function() { it(title = " value empty throws an error for queryexecute function", body = function( currentSpec ) { - local.result = _InternalRequest( - template : uri&"/LDEV2616.cfm", - forms : {scene = 1} - ); - expect(result.filecontent).toBe(0); + var result = ""; + try { + result = _InternalRequest( + template : uri&"/LDEV2616.cfm" + ); + result = result.fileContent; + } catch ( e ) { + result = e.message; + } + expect(result).toInclude("param [id] may not be empty" ); }); - it(title = "Value empty execute in query member function query.queryexecute", body = function( currentSpec ) { - local.result = _InternalRequest( - template : uri&"/LDEV2616.cfm", - forms : {scene = 2} - ); - expect(result.filecontent).toBe(0); - }); }); } diff --git a/test/tickets/LDEV2616/Application.cfc b/test/tickets/LDEV2616/Application.cfc index 2d040f4ed6..38ad0221f8 100644 --- a/test/tickets/LDEV2616/Application.cfc +++ b/test/tickets/LDEV2616/Application.cfc @@ -1,6 +1,6 @@ component { - this.name = "luceetest"; + this.name = "LDEV2616"; this.datasources["LDEV2616"] = server.getDatasource("mssql"); this.datasource = "LDEV2616"; diff --git a/test/tickets/LDEV2616/LDEV2616.cfm b/test/tickets/LDEV2616/LDEV2616.cfm index 9271107574..ebb6715c66 100644 --- a/test/tickets/LDEV2616/LDEV2616.cfm +++ b/test/tickets/LDEV2616/LDEV2616.cfm @@ -1,17 +1,7 @@ - cfparam (name="form.scene",default=""); - if(form.scene eq 1){ - res = QueryExecute("SELECT * FROM LDEV2616 WHERE id IN (:id)", - { id:{ list :true, value :"" }}, - {datasource ='LDEV2616'} - ) - writeoutput(res.getresult().recordcount); - } - else if(form.scene eq 2){ - queryService = new query(); - queryService.setDatasource("LDEV2616"); - queryservice.addparam(name="id",value=""); - result = queryService.execute(sql="SELECT * FROM LDEV2616 WHERE id IN (:id)"); - writeoutput(result.getresult().recordcount); - } + res = QueryExecute("SELECT * FROM LDEV2616 WHERE id IN (:id)", + { id:{ list :true, value :"" }}, + {datasource ='LDEV2616'} + ) + writeoutput(res.getresult().recordcount); \ No newline at end of file diff --git a/test/tickets/_LDEV3604.cfc b/test/tickets/LDEV3604.cfc similarity index 93% rename from test/tickets/_LDEV3604.cfc rename to test/tickets/LDEV3604.cfc index 2d5554a340..dd528a5de5 100644 --- a/test/tickets/_LDEV3604.cfc +++ b/test/tickets/LDEV3604.cfc @@ -1,4 +1,4 @@ -component extends="org.lucee.cfml.test.LuceeTestCase"{ +component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { function run( testResults, testBox ){ describe("Testcase for LDEV-3604", function( currentSpec ) { it(title="Checking instance of original CFC", body=function( currentSpec ) { diff --git a/test/tickets/LDEV3672.cfc b/test/tickets/LDEV3672.cfc new file mode 100644 index 0000000000..88bc2857a7 --- /dev/null +++ b/test/tickets/LDEV3672.cfc @@ -0,0 +1,24 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { + + function beforeAll(){ + variables.uri = createURI("LDEV3672"); + } + + function run( testResults , testBox ) { + + describe( title='LDEV-3672' , body=function(){ + + it( title='dumping a cfc errors with final' , body=function() { + local.result = _InternalRequest( + template:"#variables.uri#/LDEV3672.cfm" + ); + expect(local.result.filecontent.trim()).toBe('success'); + }); + }); + } + + private string function createURI(string calledName){ + var baseURI="/test/#listLast(getDirectoryFromPath(getCurrentTemplatepath()),"\/")#/"; + return baseURI & "" & calledName; + } +} \ No newline at end of file diff --git a/test/tickets/LDEV3672/Application.cfc b/test/tickets/LDEV3672/Application.cfc new file mode 100644 index 0000000000..18c56ed620 --- /dev/null +++ b/test/tickets/LDEV3672/Application.cfc @@ -0,0 +1,3 @@ +component { + this.name = "LDEV3672"; +} diff --git a/test/tickets/LDEV3672/LDEV3672.cfm b/test/tickets/LDEV3672/LDEV3672.cfm new file mode 100644 index 0000000000..164ce9b4da --- /dev/null +++ b/test/tickets/LDEV3672/LDEV3672.cfm @@ -0,0 +1,11 @@ + + cfc = new LDEV3672_final(); + try { + savecontent variable="ignore"{ + dump( cfc ); + } + echo( "success" ); + } catch( e ){ + echo( e.stacktrace ); + } + \ No newline at end of file diff --git a/test/tickets/LDEV3672/LDEV3672_final.cfc b/test/tickets/LDEV3672/LDEV3672_final.cfc new file mode 100644 index 0000000000..9dde36bfa9 --- /dev/null +++ b/test/tickets/LDEV3672/LDEV3672_final.cfc @@ -0,0 +1,3 @@ +component { + final this.test = "test" +} \ No newline at end of file diff --git a/test/tickets/LDEV4020.cfc b/test/tickets/LDEV4020.cfc new file mode 100644 index 0000000000..8c4a1d2e0b --- /dev/null +++ b/test/tickets/LDEV4020.cfc @@ -0,0 +1,63 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function afterAll(){ + application action='update' nullSupport=false; + }; + + function run( testResults , testBox ) { + + describe( title='check null argument handling with full null support' , body=function(){ + + beforeEach( function( currentSpec, data ){ + application action='update' nullSupport=true; + }); + + afterEach( function( currentSpec, data ){ + application action='update' nullSupport=false; + }); + + it( title='named arguments', body=function() { + var named = _proxy( arg1=456 ); + expect( named.arg1 ).toBe( 456 ); + expect( named.arg2 ).toBe( "default 2" ); // was null in 5.4 + }); + + it( title='positional arguments', body=function() { + var positional = _proxy( 456 ); + expect( positional.arg1 ).toBe( 456 ); + expect( positional.arg2 ).toBe( "default 2" ); + }); + }); + + describe( title='check null argument handling without full null support' , body=function(){ + + beforeEach( function( currentSpec, data ){ + application action='update' nullSupport=false; + }); + + afterEach( function( currentSpec, data ){ + application action='update' nullSupport=false; + }); + + it( title='named arguments', body=function() { + var named = _proxy( arg1=456 ); + expect( named.arg1 ).toBe( 456 ); + expect( named.arg2 ).toBe( "default 2" ); + }); + + it( title='positional arguments', body=function() { + var positional = _proxy( 456 ); + expect( positional.arg1 ).toBe( 456 ); + expect( positional.arg2 ).toBe( "default 2" ); + }); + }); + + } + + private function _proxy( arg1, arg2 ) { + return _udf( argumentCollection = arguments ); + } + private function _udf( arg1='default 1', arg2='default 2' ){ + return arguments; + } +} \ No newline at end of file diff --git a/test/tickets/LDEV4583.cfc b/test/tickets/LDEV4583.cfc index 4ae5769eb7..6f8dbe3454 100644 --- a/test/tickets/LDEV4583.cfc +++ b/test/tickets/LDEV4583.cfc @@ -11,35 +11,23 @@ component extends = "org.lucee.cfml.test.LuceeTestCase" label="json" { expect( structKeyExists( deserializeJson( str ), "name" ) ).toBeTrue(); }); - it( "isJson allows json ", function(){ - var str = '{ - "name" : "lucee" - }'; - expect( isJson( str ) ).toBeTrue(); - expect( structKeyExists( deserializeJson( str ), "name" ) ).toBeTrue(); - }); - - it( "isJson shouldn't allow json5, block comment", function(){ + it( "isJson should allow json5, block comment", function(){ var str = '{ "name" : "lucee" /* block comment */ }'; - expect( isJson( str ) ).toBeFalse(); - expect( function(){ - structKeyExists( deserializeJson( str ), "name" ) - }).toThrow(); + expect( isJson( str ) ).toBeTrue(); + structKeyExists( deserializeJson( str ), "name" ) ; }); - it( "isJson shouldn't allow json5 inline", function(){ + it( "isJson should allow json5 inline", function(){ var str = '{ "name" : "lucee" // inline comment }'; - expect( isJson( str ) ).toBeFalse(); - expect( function(){ - structKeyExists( deserializeJson( str ), "name" ) - }).toThrow(); + expect( isJson( str ) ).toBeTrue(); + structKeyExists( deserializeJson( str ), "name" ); }); } ); diff --git a/test/tickets/LDEV4656.cfc b/test/tickets/LDEV4656.cfc new file mode 100644 index 0000000000..8a977ce364 --- /dev/null +++ b/test/tickets/LDEV4656.cfc @@ -0,0 +1,85 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function afterAll(){ + application action='update' nullSupport=false; + }; + + function run( testResults , testBox ) { + + describe( title='check null argument handling with full null support' , body=function(){ + + beforeEach( function( currentSpec, data ){ + application action='update' nullSupport=true; + }); + + afterEach( function( currentSpec, data ){ + application action='update' nullSupport=false; + }); + + it( title='full null support, no arguments, via argumentCollection', body=function() { + var args = _proxy(); + expect( args.arg1 ).toBe( "default 1" ); + expect( args.arg2 ).toBe( "default 2" ); + }); + + it( title='full null support, no arguments', body=function() { + var args = _udf(); + expect( args.arg1 ).toBe( "default 1" ); + expect( args.arg2 ).toBe( "default 2" ); + }); + + it( title='full null support, no arguments, without defaults', skip=true, body=function() { + var args = _udfNoDefaults(); // args scope is currently empty + expect( isNull( args.arg1 ) ).toBeTrue(); + expect( isNull( args.arg2 ) ).toBeTrue(); + expect( args ).toHaveKey( "arg1" ); + expect( args ).toHaveKey( "arg2" ); + }); + + }); + + describe( title='check null argument handling without full null support' , body=function(){ + + beforeEach( function( currentSpec, data ){ + application action='update' nullSupport=false; + }); + + afterEach( function( currentSpec, data ){ + application action='update' nullSupport=false; + }); + + it( title='normal, no arguments, via argumentCollection', body=function() { + var args = _proxy(); + expect( args.arg1 ).toBe( "default 1" ); + expect( args.arg2 ).toBe( "default 2" ); + }); + + it( title='normal, no arguments, with defaults', body=function() { + var args = _udf(); + expect( args.arg1 ).toBe( "default 1" ); + expect( args.arg2 ).toBe( "default 2" ); + }); + + it( title='normal, no arguments, without defaults', body=function() { + var args = _udfNoDefaults(); + expect( args ).notToHaveKey( "arg1" ); // keys with null values don't exist + expect( args ).notToHaveKey( "arg2" ); // keys with null values don't exist + expect( isNull( args.arg1 ) ).toBeTrue(); + expect( isNull( args.arg2 ) ).toBeTrue(); + }); + + }); + + } + + private function _proxy( arg1, arg2 ) { + return _udf( argumentCollection = arguments ); + } + private function _udf( arg1='default 1', arg2='default 2' ){ + return arguments; + } + + private function _udfNoDefaults( arg1, arg2 ){ + return arguments; + } +} \ No newline at end of file diff --git a/test/tickets/LDEV4871.cfc b/test/tickets/LDEV4871.cfc new file mode 100644 index 0000000000..ffbdae79f7 --- /dev/null +++ b/test/tickets/LDEV4871.cfc @@ -0,0 +1,35 @@ +component extends = "org.lucee.cfml.test.LuceeTestCase" { + + variables.mysql = server.getDatasource("mysql"); + + private function isMySqlNotSupported() { + return isEmpty(variables.mysql); + } + + function run( testResults, testBox ) { + describe( "Testcase for LDEV-4871", function() { + it( title="testing array", body=function( currentSpec ) { + var arr=[1,2,3]; + var ser=objectSave(arr); + var rel=objectLoad(ser); + expect( serializeJson(rel) ).toBe( serializeJson(arr) ); + }); + it( title="testing struct", body=function( currentSpec ) { + var sct={a:1}; + var ser=objectSave(sct); + var rel=objectLoad(ser); + expect( serializeJson(rel) ).toBe( serializeJson(sct) ); + }); + it( title="testing query",skip=isMySqlNotSupported(), body=function( currentSpec ) { + query datasource=variables.mysql name="local.qry" { + ``` + show tables + ``` + } + var ser=objectSave(qry); + var rel=objectLoad(ser); + expect( serializeJson(rel) ).toBe( serializeJson(qry) ); + }); + }); + } +} diff --git a/test/tickets/LDEV4920.cfc b/test/tickets/LDEV4920.cfc new file mode 100644 index 0000000000..74a6b37d9e --- /dev/null +++ b/test/tickets/LDEV4920.cfc @@ -0,0 +1,206 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + + function run( testResults , testBox ) { + describe( "test case for LDEV-4920", function() { + it( title="multi line: test with no comment", body=function( currentSpec ) { + str='{ "b": ""}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(""); + }); + + it( title="multi line: test with comment before name", body=function( currentSpec ) { + str='{ /*dd*/"b": ""}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(""); + }); + + it( title="multi line: test with comment after name", body=function( currentSpec ) { + str='{ "b"/*dd*/: ""}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(""); + }); + + it( title="multi line: test with comment before number value", body=function( currentSpec ) { + str='{ "b": /*dd*/1}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(1); + }); + + it( title="multi line: test with comment after number value", body=function( currentSpec ) { + str='{ "b": 1/*dd*/}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(1); + }); + + it( title="multi line: test with comment before boolean value", body=function( currentSpec ) { + str='{ "b": /*dd*/true}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(true); + }); + + it( title="multi line: test with comment after boolean value", body=function( currentSpec ) { + str='{ "b": true/*dd*/}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(true); + }); + + it( title="multi line: test with comment before string value", body=function( currentSpec ) { + str='{ "b": /*dd*/"susi"}'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="multi line: test with comment after string value", body=function( currentSpec ) { + str='{ "b": "susi"/*dd*/}'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="multi line: test with comment before string (single quote) value", body=function( currentSpec ) { + str='{ "b": /*dd*/''susi''}'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="multi line: test with comment after string (single quote) value", body=function( currentSpec ) { + str='{ "b": ''susi''/*dd*/}'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="multi line: test with comment before struct value", body=function( currentSpec ) { + str='{ "b": /*dd*/{"a":1}}'; + var result=deserializeJSON(str); + expect( result.b.a ).toBe(1); + }); + + it( title="multi line: test with comment after struct value", body=function( currentSpec ) { + str='{ "b": {"a":1}/*dd*/}'; + var result=deserializeJSON(str); + expect( result.b.a ).toBe(1); + }); + + it( title="multi line: test with comment before array value", body=function( currentSpec ) { + str='{ "b": /*dd*/[1,2,3]}'; + var result=deserializeJSON(str); + expect( result.b[1] ).toBe(1); + }); + + it( title="multi line: test with comment after array value", body=function( currentSpec ) { + str='{ "b": [1,2,3]/*dd*/}'; + var result=deserializeJSON(str); + expect( result.b[1] ).toBe(1); + }); + + + + + + + + it( title="single line: test with comment before name", body=function( currentSpec ) { + str='{ + //dd + "b": ""}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(""); + }); + + it( title="single line: test with comment after name", body=function( currentSpec ) { + str='{ "b"//dd + : ""}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(""); + }); + + it( title="single line: test with comment before number value", body=function( currentSpec ) { + str='{ "b": //dd + 1}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(1); + }); + + it( title="single line: test with comment after number value", body=function( currentSpec ) { + str='{ "b": 1//dd + }'; + var result=deserializeJSON(str); + expect( result.b ).toBe(1); + }); + + it( title="single line: test with comment before boolean value", body=function( currentSpec ) { + str='{ "b": // + true}'; + var result=deserializeJSON(str); + expect( result.b ).toBe(true); + }); + + it( title="single line: test with comment after boolean value", body=function( currentSpec ) { + str='{ "b": true//dd + }'; + var result=deserializeJSON(str); + expect( result.b ).toBe(true); + }); + + it( title="single line: test with comment before string value", body=function( currentSpec ) { + str='{ "b": //dd + "susi"}'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="single line: test with comment after string value", body=function( currentSpec ) { + str='{ "b": "susi"//dd* + }'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="single line: test with comment before string (single quote) value", body=function( currentSpec ) { + str='{ "b": //dd + ''susi''}'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="single line: test with comment after string (single quote) value", body=function( currentSpec ) { + str='{ "b": ''susi''//dd + }'; + var result=deserializeJSON(str); + expect( result.b ).toBe("susi"); + }); + + it( title="single line: test with comment before struct value", body=function( currentSpec ) { + str='{ "b": //dd + {"a":1}}'; + var result=deserializeJSON(str); + expect( result.b.a ).toBe(1); + }); + + it( title="single line: test with comment after struct value", body=function( currentSpec ) { + str='{ "b": {"a":1}//dd + }'; + var result=deserializeJSON(str); + expect( result.b.a ).toBe(1); + }); + + it( title="single line: test with comment before array value", body=function( currentSpec ) { + str='{ "b": //dd + [1,2,3]}'; + var result=deserializeJSON(str); + expect( result.b[1] ).toBe(1); + }); + + it( title="single line: test with comment after array value", body=function( currentSpec ) { + str='{ "b": [1,2,3]//dd + }'; + var result=deserializeJSON(str); + expect( result.b[1] ).toBe(1); + }); + + }); + } +} + + + diff --git a/test/tickets/LDEV4955.cfc b/test/tickets/LDEV4955.cfc index 7b408c6f17..05620d7729 100644 --- a/test/tickets/LDEV4955.cfc +++ b/test/tickets/LDEV4955.cfc @@ -1,8 +1,9 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { +component extends="org.lucee.cfml.test.LuceeTestCase" { function run( testResults, testBox ) { describe("Testcase for LDEV-4955", function() { - it( title="check env var string expansion works for mulitple tokens", body=function( currentSpec ){ + it( title="check environment var placeholder expansion works for mulitple tokens", body=function( currentSpec ){ + var raw =""; var tokens = ""; var c = 1; @@ -12,19 +13,50 @@ component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { or key contains "secret" or key contains "s3" or key contains "token") continue; - tokens &= ",{env.#key#}"; + tokens &= ",{env:#key#}"; raw &= ",#server.system.environment[ key ]#"; + // check single + expect( _expand( "${#key#}" ) ).toBe( server.system.environment[ key ] ); + expect( _expand( "{env:#key#}" ) ).toBe( server.system.environment[ key ] ); + if ( c gt 3 ) break; + c++; + } + var expanded = _expand( tokens ); + expect( ( expanded eq raw ) ).toBeTrue(); // manual compare as we don't want to leak env vars + }); + + it( title="check system properties placeholder expansion works for mulitple tokens", body=function( currentSpec ){ + + var raw =""; + var tokens = ""; + var c = 1; + for ( key in server.system.properties ){ + if ( len( server.system.properties[ key ] ) gt 2 + or key contains "pass" + or key contains "secret" + or key contains "s3" + or key contains "token") continue; + tokens &= ",{system:#key#}"; + raw &= ",#server.system.properties[ key ]#"; + // check single + expect( _expand( "{system:#key#}" ) ).toBe( server.system.properties[ key ] ); if ( c gt 3 ) break; c++; } - //systemoutput( "", true ); - //systemoutput( tokens, true ); - //systemoutput( raw, true ); - var expanded = expandPath( tokens ); - //systemoutput( expanded , true ); + var expanded = _expand( tokens ); expect( ( expanded eq raw ) ).toBeTrue(); // manual compare as we don't want to leak env vars }); + + it( title="check ${envvar:default_value) placeholder expansion fallback to default", body=function( currentSpec ){ + // $$$ is an invalid name, so we always fallback to default + expect( _expand( "${$$$:haha}" ) ).toBe( "haha" ); + }); }); + } + private function _expand ( str ){ + var webFactory = createObject("java", "lucee.runtime.config.ConfigWebFactory"); + return webFactory.replaceConfigPlaceHolder(str ); + } } \ No newline at end of file diff --git a/test/tickets/LDEV4982.cfc b/test/tickets/LDEV4982.cfc new file mode 100644 index 0000000000..dc6a2547e5 --- /dev/null +++ b/test/tickets/LDEV4982.cfc @@ -0,0 +1,15 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults , testBox ) { + describe( "Test case for LDEV-4982", function() { + it( title = "Checking {lucee-config-file}", body=function( currentSpec ) { + var cfconfigPath = expandPath( "{lucee-config-file}" ); + systemOutput(chr(10) & cfconfigPath, true) + expect( fileExists( cfconfigPath ) ).toBeTrue(); + var json= fileRead( cfconfigPath ); + expect( isJson( json ) ).toBeTrue(); + }); + }); + } + +} diff --git a/test/tickets/LDEV5010.cfc b/test/tickets/LDEV5010.cfc new file mode 100644 index 0000000000..1f32140eb6 --- /dev/null +++ b/test/tickets/LDEV5010.cfc @@ -0,0 +1,27 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { + + function run( testResults , testBox ) { + + describe( title='LDEV-5010' , body=function(){ + + it( title='check fileWrite charset exception makes sense' , body=function() { + try { + fileWrite(getTempFile(getTempDirectory(), "test"), "test", true); + } catch ( e ){ + expect( e.message ).notToInclude( "Invalid file" ); // should be invalid encoding + } + }); + + it( title='check fileAppend charset exception makes sense' , body=function() { + try { + fileAppend(getTempFile(getTempDirectory(), "test"), "test", true); + } catch ( e ){ + expect( e.message ).notToInclude( "Invalid file" ); // should be invalid encoding + } + + }); + + + }); + } +} \ No newline at end of file diff --git a/test/tickets/LDEV5023.cfc b/test/tickets/LDEV5023.cfc new file mode 100644 index 0000000000..599dabf2a5 --- /dev/null +++ b/test/tickets/LDEV5023.cfc @@ -0,0 +1,107 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq" { + + function run( testResults , testBox ) { + + describe( title='QofQ' , body=function(){ + + it( title='QoQ select * from table same source table name HSQLDB', body=function() { + var q = getTestQuery(); + var cols = replaceNoCase( q.columnList, ",unique", "" ); // cleanup reserved word + // native engine + cols = "name, id"; + var q_native = QueryExecute( + sql = "SELECT #cols# FROM q", + options = { dbtype: 'query', maxrows=5 } + ); + var q_stash = duplicate( q_native ); + expect( q_stash.recordcount ).toBe( q_native.recordcount ); + // hsqldb engine, coz join + var q_hsqlb = QueryExecute( + sql = "SELECT t1.name FROM q_native t1, q_native t2 WHERE t1.id = t2.id", + options = { dbtype: 'query' } + ); + expect( q_stash.recordcount ).toBe( q_hsqlb.recordcount ); + expect( q_native.recordcount ).toBe( q_hsqlb.recordcount ); + expect( q_stash.recordcount ).toBe( q_native.recordcount ); + }); + + it( title='QoQ select * from table same source table name (arguments) HSQLDB', body=function() { + var q = getTestQuery(); + var cols = replaceNoCase( q.columnList, ",unique", "" ); // cleanup reserved word + // native engine + cols = "name, id"; + var q_native = QueryExecute( + sql = "SELECT #cols# FROM q", + options = { dbtype: 'query', maxrows=5 } + ); + var q_stash = duplicate( q_native ); + // hsqldb engine, coz join + arguments.q_native = q_native; + var q_hsqlb = QueryExecute( + sql = "SELECT t1.name FROM q_native t1, arguments.q_native t2 WHERE t1.id = t2.id", + options = { dbtype: 'query' } + ); + + expect( q_stash.recordcount ).toBe( q_hsqlb.recordcount ); + expect( q_native.recordcount ).toBe( q_hsqlb.recordcount ); + expect( q_stash.recordcount ).toBe( q_native.recordcount ); + }); + + it( title='QoQ select * from table same source table name (all cols) HSQLDB', body=function() { + var q = getTestQuery(); + var cols = replaceNoCase( q.columnList, ",unique", "" ); // cleanup reserved word + // native engine + var q_native = QueryExecute( + sql = "SELECT #cols# FROM q", + options = { dbtype: 'query', maxrows=5 } + ); + var q_stash = duplicate( q_native ); + // hsqldb engine, coz join + var q_hsqlb = QueryExecute( + sql = "SELECT t1.name FROM q_native t1, q_native t2 WHERE t1.id = t2.id", + options = { dbtype: 'query' } + ); + expect( q_stash.recordcount ).toBe( q_hsqlb.recordcount ); + expect( q_native.recordcount ).toBe( q_hsqlb.recordcount ); + expect( q_stash.recordcount ).toBe( q_native.recordcount ); + }); + + it( title='QoQ select * from table same source table name (all cols) HSQLDB, 5000 threads', body=function() { + + var arr = []; + ArraySet(arr, 1, 1000, 0); + arrayEach(arr, function(){ + var q = getTestQuery(); + var cols = replaceNoCase( q.columnList, ",unique", "" ); // cleanup reserved word + // native engine + q = QueryExecute( + sql = "SELECT #cols# FROM q", + options = { dbtype: 'query' } + ); + // hsqldb engine, coz join + q = QueryExecute( + sql = "SELECT t1.name FROM q t1, q t2 WHERE t1.id = t2.id", + options = { dbtype: 'query' } + ); + }, true); + + }); + }); + + } + + private function getTestQuery(){ + return extensionList(); // has arrays and the like + /* + var q = queryNew("id,name,data","integer,varchar, varchar"); + loop list="micha,zac,brad,pothys,gert" item="local.n" index="local.i" { + var r = queryAddRow( q ); + querySetCell(q, "id", r, r) + querySetCell(q, "name", n, r) + //querySetCell(q, "data", repeatString("lucee",1000), r); + } + return q; + */ + } + +} \ No newline at end of file diff --git a/test/tickets/LDEV5031.cfc b/test/tickets/LDEV5031.cfc new file mode 100644 index 0000000000..4a6e03eb25 --- /dev/null +++ b/test/tickets/LDEV5031.cfc @@ -0,0 +1,24 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq"{ + function run( testResults , testBox ) { + describe( "Test suite for LDEV-5031", function() { + + it("Checking QOQ with unrelated columns in aggregate order by", function( currentSpec ){ + var testQuery = queryNew("amount, relatedData, unrelateddata","integer, varchar, integer"); + testQuery.addRow({amount: 1, relatedData: "a", unrelateddata: 1}); + testQuery.addRow({amount: 2, relatedData: "a", unrelateddata: 2}); + testQuery.addRow({amount: 3, relatedData: "a", unrelateddata: 3}); + + // should produce a single row + local.result = queryExecute(" + select sum(amount) as sum + from testQuery + where 1=1 + order by unrelatedData + ", {}, {dbtype="query"}); + + expect(local.result.recordCount).toBe(1); + expect(local.result.sum).toBe(6); + }); + }); + } +} \ No newline at end of file diff --git a/test/tickets/LDEV5034.cfc b/test/tickets/LDEV5034.cfc new file mode 100644 index 0000000000..e329274d71 --- /dev/null +++ b/test/tickets/LDEV5034.cfc @@ -0,0 +1,107 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function beforeAll(){ + variables.dir = getTempDirectory() & "LDEV-5034/"; + if ( directoryExists( dir ) ) + directoryDelete( dir, true ); + directoryCreate( dir ); + }; + + function afterAll(){ + if ( directoryExists( dir ) ){ + directoryDelete( dir, true ); + }; + }; + + function run( testResults, testBox ){ + describe( "fileSetAccessMode", function(){ + it( title="test access modes", skip=isNotUnix(), body=function(){ + var tests = []; + + arrayAppend( tests, _dir( dir, "755", "755" ) ); + arrayAppend( tests, _dir( dir, "777", "777" ) ); + arrayAppend( tests, _dir( dir, "644", "644" ) ); + + var files = directoryList( dir, true, "query"); + var st = QueryToStruct( files, "name" ); + + loop array=st index="local.item"{ + systemOutput( item, true ); + } + + arrayAppend( tests, _file( dir, "644.txt", "644" ) ); + arrayAppend( tests, _file( dir, "743.txt", "743" ) ); + arrayAppend( tests, _file( dir, "043.txt", "443" ) ); + arrayAppend( tests, _file( dir, "400.txt", "400" ) ); + + var files = directoryList( dir, true, "query"); + var st = QueryToStruct( files, "name" ); + + loop collection=st item="local.item"{ + systemOutput( item, true ); + } + loop array=tests item="local.test" { + systemOutput( test, true ); + } + loop array=tests item="local.test" { + systemOutput( test, true ); + var key = mid( test.name, len( dir ) + 1 ); + systemOutput( key, true ); + expect( st ).toHaveKey( key ); + systemOutput( st[ key ], true ); + expect( test.mode ).toBe( st[ key ].mode ); + } + + var tar = getTempFile( getTempDirectory(), "LDEV-5034", ".tar.gz" ); + compress( "tgz", dir, tar ); + + var dest = getTempDirectory() & "LDEV-5034-" & createUUID() & "/"; + directoryCreate( dest ); + extract( "tgz", tar, dest ); + + var extractedFiles = directoryList( dest, true, "query" ); + var st2 = QueryToStruct( files, "name" ); + + files=justFiles(files); + extractedFiles=justFiles(extractedFiles); + expect( files.recordcount ).toBe( extractedFiles.recordcount ); + }); + } ); + } + + private function justFiles(qry) { + qry=duplicate(qry); + for(var row=qry.recordcount;row>0;row--) { + if(qry.type[row]!="File") queryDeleteRow(qry,row); + } + querySort(qry, "type"); + return qry; + } + + private function _dir( parent, name, mode ){ + var dir = parent & name; + directoryCreate( dir ); + fileSetAccessMode( dir, mode ); + return { + name: dir, + mode: mode, + type: "dir" + }; + } + + private function _file( parent, name, mode ){ + var file = parent & name; + fileWrite( file, "" ); + fileSetAccessMode( file, mode ); + return { + name: file, + mode: mode, + type: "file" + }; + } + + private function isNotUnix(){ + return (server.os.name == "windows"); + } + +} diff --git a/test/tickets/LDEV5049.cfc b/test/tickets/LDEV5049.cfc new file mode 100644 index 0000000000..42a3e68412 --- /dev/null +++ b/test/tickets/LDEV5049.cfc @@ -0,0 +1,107 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { + + function beforeAll() { + variables.uri = createURI("LDEV5049"); + } + + function run( testResults , testBox ) { + describe( "Test suite for LDEV-5049", function() { + + it( title='internalRequest string url scope with leading & [&mt=10]', body=function( currentSpec ) { + var qs = "&mt=10"; + local.result = _InternalRequest( + template : "#uri#/LDEV5049_url.cfm", + urls: qs + ); + + var parsedUrl = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedUrl, true ); + + expect( parsedUrl ).toHaveKey( "mt" ); + expect( structCount( parsedUrl ) ).toBe( 1 ); + }); + + it( title='internalRequest string url scope with trailing & [mt=10&]', body=function( currentSpec ) { + var qs = "mt=10&"; + local.result = _InternalRequest( + template : "#uri#/LDEV5049_url.cfm", + urls: qs + ); + + var parsedUrl = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedUrl, true ); + + expect( parsedUrl ).toHaveKey( "mt" ); + expect( structCount( parsedUrl ) ).toBe( 1 ); + }); + + it( title='internalRequest string form scope with leading & [&mt=10]', body=function( currentSpec ) { + var qs = "&mt=10"; + local.result = _InternalRequest( + template : "#uri#/LDEV5049_form.cfm", + form: qs + ); + + var parsedForm = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedForm, true ); + + expect( parsedForm ).toHaveKey( "mt" ); + expect( parsedForm ).toHaveKey( "fieldnames" ); + expect( structCount( parsedForm ) ).toBe( 2 ); + }); + + it( title='internalRequest string form scope with trailing & [mt=10&]', body=function( currentSpec ) { + var qs = "mt=10&"; + local.result = _InternalRequest( + template : "#uri#/LDEV5049_form.cfm", + form: qs + ); + + var parsedForm = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedForm, true ); + + expect( parsedForm ).toHaveKey( "mt" ); + expect( parsedForm ).toHaveKey( "fieldnames" ); + expect( structCount( parsedForm ) ).toBe( 2 ); + }); + + it( title='internalRequest string url [a&=b]', body=function( currentSpec ) { + var qs = "a&=b"; + local.result = _InternalRequest( + template : "#uri#/LDEV5049_url.cfm", + urls: qs + ); + + var parsedUrl = deserializeJSON( result.filecontent ); + + systemOutput( "", true ); + systemOutput( qs, true ); + systemOutput( parsedUrl, true ); + + expect( parsedUrl ).toHaveKey( "a" ); + expect( parsedUrl["a"] ).toBe( "" ); + expect( parsedUrl ).toHaveKey( "" ); + expect( parsedUrl[""] ).toBe( "b" ); + expect( structCount( parsedUrl ) ).toBe( 2 ); + }); + }); + } + + private string function createURI(string calledName){ + var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrentTemplatepath()),"\/")#/"; + return baseURI&""&calledName; + } + +} diff --git a/test/tickets/LDEV5049/Application.cfc b/test/tickets/LDEV5049/Application.cfc new file mode 100644 index 0000000000..214ab8fc70 --- /dev/null +++ b/test/tickets/LDEV5049/Application.cfc @@ -0,0 +1,3 @@ +component { + this.name = "LDEV5049"; +} diff --git a/test/tickets/LDEV5049/LDEV5049_form.cfm b/test/tickets/LDEV5049/LDEV5049_form.cfm new file mode 100644 index 0000000000..dd3703a9dd --- /dev/null +++ b/test/tickets/LDEV5049/LDEV5049_form.cfm @@ -0,0 +1,3 @@ + + echo(form.toJson()); + \ No newline at end of file diff --git a/test/tickets/LDEV5049/LDEV5049_url.cfm b/test/tickets/LDEV5049/LDEV5049_url.cfm new file mode 100644 index 0000000000..50517fe89a --- /dev/null +++ b/test/tickets/LDEV5049/LDEV5049_url.cfm @@ -0,0 +1,3 @@ + + echo(url.toJson()); + \ No newline at end of file diff --git a/test/tickets/LDEV5050.cfc b/test/tickets/LDEV5050.cfc new file mode 100644 index 0000000000..8989f05c4f --- /dev/null +++ b/test/tickets/LDEV5050.cfc @@ -0,0 +1,29 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { + + function beforeAll() { + variables.uri = createURI("LDEV5050"); + } + + function run( testResults , testBox ) { + describe( "test case for LDEV-5050", function() { + + it (title = "Empty list value with query.queryexecute should throw", body = function( currentSpec ) { + var result = ""; + try { + result = _InternalRequest( + template : uri&"/LDEV5050.cfm" + ); + result = result.fileContent; + } catch ( e ) { + result = e.message; + } + expect(result).toInclude("param [id] may not be empty" ); + }); + }); + } + + private string function createURI(string calledName){ + var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrentTemplatepath()),"\/")#/"; + return baseURI&""&calledName; + } +} \ No newline at end of file diff --git a/test/tickets/LDEV5050/Application.cfc b/test/tickets/LDEV5050/Application.cfc new file mode 100644 index 0000000000..fffe625e36 --- /dev/null +++ b/test/tickets/LDEV5050/Application.cfc @@ -0,0 +1,23 @@ +component { + + this.name = "LDEV5050"; + this.datasources["LDEV5050"] = server.getDatasource("mssql"); + this.datasource = "LDEV5050"; + + + public function onRequestStart() { + setting requesttimeout=10; + query{ + echo("DROP TABLE IF EXISTS LDEV5050"); + } + query{ + echo("CREATE TABLE LDEV5050( id int, name varchar(20))"); + } + } + + public function onRequestEnd(){ + query{ + echo("DROP TABLE IF EXISTS LDEV5050"); + } + } +} diff --git a/test/tickets/LDEV5050/LDEV5050.cfm b/test/tickets/LDEV5050/LDEV5050.cfm new file mode 100644 index 0000000000..957a3bc2d9 --- /dev/null +++ b/test/tickets/LDEV5050/LDEV5050.cfm @@ -0,0 +1,9 @@ + + queryService = new query(); + queryService.setDatasource("LDEV5050"); + queryservice.addparam( name="id", value="", list=true); + result = queryService.execute(sql="SELECT * FROM LDEV5050 WHERE id IN (:id)"); + systemOutput(result.getresult(), true); + systemOutput(result, true); + writeoutput(result.getresult().recordcount); + \ No newline at end of file diff --git a/test/tickets/LDEV5065.cfc b/test/tickets/LDEV5065.cfc new file mode 100644 index 0000000000..3ae3c56515 --- /dev/null +++ b/test/tickets/LDEV5065.cfc @@ -0,0 +1,30 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults , testBox ) { + describe( "Test suite for LDEV-5065", function() { + + it( title='assignment regression', body=function( currentSpec ) { + var brad=5; + writeDump( var=( brad=4) ); + }); + + it( title='assignment regression', body=function( currentSpec ) { + var brad=5; + writeDump( ( brad+=4) ); + }); + + it( title='assignment regression', body=function( currentSpec ) { + var brad=5; + writeDump( var=( brad+=4) ); + }); + + it( title='assignment regression', body=function( currentSpec ) { + var brad=5; + writeDump( var=( brad=4) ); + writeDump( ( brad+=4) ); + writeDump( var=( brad+=4) ); + }); + }); + } + +} diff --git a/test/tickets/LDEV5067.cfc b/test/tickets/LDEV5067.cfc new file mode 100644 index 0000000000..a77dc73527 --- /dev/null +++ b/test/tickets/LDEV5067.cfc @@ -0,0 +1,31 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq" { + + function run( testResults , testBox ) { + describe( title='LDEV-5067', body=function(){ + + it( title='test QOQ query parsing, with comments in quotes ', body=function() { + + var params = { + id: { + value: 1, sqltype="numeric" + } + }; + + var qry= queryNew( "engine,id", "varchar,numeric", [ + [ "lucee", 1 ], + [ "ralio" , 2 ] + ]); + + query name="local.q" dbtype="query" params="#params#" result="local.result" { + echo ( "SELECT engine, '/* multi */ // -- single' AS comments FROM qry WHERE id = :id"); + }; + + expect( q.recordcount ).toBe( 1 ); + expect( q.engine ).toBe( "lucee" ); + expect( q.comments ).toBe( "/* multi */ // -- single" ); + }); + + }); + } + +} \ No newline at end of file diff --git a/test/tickets/LDEV_2902.cfc b/test/tickets/LDEV_2902.cfc index 66be8753be..b7ec1b5786 100644 --- a/test/tickets/LDEV_2902.cfc +++ b/test/tickets/LDEV_2902.cfc @@ -20,7 +20,7 @@ component extends = "org.lucee.cfml.test.LuceeTestCase" { template : "#uri#\test.cfm", forms : { Scene = 2 } ); - expect(trim(result.filecontent)).toBe("The key [TIMEZONE] does not exist"); + expect(trim(result.filecontent)).toBe("key [TIMEZONE] doesn't exist"); }); it( title = "Checking datasource configured Empty timezone", body = function( currentSpec ){ diff --git a/test/tickets/LDEV_3054.cfc b/test/tickets/LDEV_3054.cfc index 418c22f44d..8df24a6f61 100644 --- a/test/tickets/LDEV_3054.cfc +++ b/test/tickets/LDEV_3054.cfc @@ -10,7 +10,8 @@ component extends = "org.lucee.cfml.test.LuceeTestCase" { local.result = _InternalRequest( template : "#uri#\test.cfm" ); - expect(find("true,The key [T]",trim(result.filecontent))>0 ).toBeTrue(); + var c=trim(result.filecontent); + expect( (find("true,The key [T]",c)>0) || (find("true,variable [T] doesn't exist",c)>0) ).toBeTrue(); }); }); } diff --git a/test/tickets/_LDEV0153.cfc b/test/tickets/_LDEV0153.cfc index ff57747b86..001b8aa9ef 100644 --- a/test/tickets/_LDEV0153.cfc +++ b/test/tickets/_LDEV0153.cfc @@ -22,8 +22,8 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="http" { public void function test(){ http url="#variables.updateProvider#/rest/update/provider/echoGet?filtername=henk+patat" result="local.res"; - res=evaluate(res.filecontent); - assertEquals("henk patat",res.url.filtername); + res=deserializeJSON(res.filecontent); + assertEquals("henk patat",res.url.filtername); // comes back as henk+patat ??? } diff --git a/test/tickets/_LDEV0155.cfc b/test/tickets/_LDEV0155.cfc index 4dbccfb53e..1e37e1b734 100644 --- a/test/tickets/_LDEV0155.cfc +++ b/test/tickets/_LDEV0155.cfc @@ -57,8 +57,8 @@ - - + + @@ -68,8 +68,8 @@ - - + + @@ -79,8 +79,8 @@ - - + + @@ -90,8 +90,8 @@ - - + + diff --git a/test/tickets/_LDEV1197.cfc b/test/tickets/_LDEV1197.cfc index a3050c8ae1..dda2827d90 100644 --- a/test/tickets/_LDEV1197.cfc +++ b/test/tickets/_LDEV1197.cfc @@ -1,7 +1,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ function beforeAll(){ - variables.serveradmin = "password"; - variables.Webadmin = "password"; + variables.serveradmin = server.SERVERADMINPASSWORD; + variables.Webadmin = server.WEBADMINPASSWORD; variables.uri = createURI("LDEV1197"); createMapping(); } @@ -54,7 +54,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ private string function createMapping(){ admin action="updateMapping" - type="web" + type="server" password="#variables.Webadmin#" virtual="/w1197" diff --git a/test/tickets/_LDEV1202.cfc b/test/tickets/_LDEV1202.cfc index e24b92e237..3614efc84a 100644 --- a/test/tickets/_LDEV1202.cfc +++ b/test/tickets/_LDEV1202.cfc @@ -21,26 +21,16 @@ LDEV-273 ---> component extends="org.lucee.cfml.test.LuceeTestCase" { - public void function testCFMLElvis() { var c=new LDEV1202.Test2(); assertEquals("",c.elvis()); } - public void function testLuceeElvis() { - var c=new LDEV1202.Test(); - assertEquals("",c.elvis()); - } public void function testCFMLIsNull() { var c=new LDEV1202.Test2(); assertTrue(c.isItNull()); } - - public void function testLuceeIsNull() { - var c=new LDEV1202.Test(); - assertTrue(c.isItNull()); - } } \ No newline at end of file diff --git a/test/tickets/_LDEV1440.cfc b/test/tickets/_LDEV1440.cfc index 77777c7636..88542e3b0f 100644 --- a/test/tickets/_LDEV1440.cfc +++ b/test/tickets/_LDEV1440.cfc @@ -11,7 +11,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { function run( testResults , testBox ) { describe( title="Test suite for LDEV-1440", body=function() { - it( title='Checking cfquery with duplicate column',skip=isNotSupported(),body=function( currentSpec ) { + it( title='Checking cfquery with duplicate column names (WONTFIX)',skip=isNotSupported(),body=function( currentSpec ) { var uri = createURI("LDEV1440"); var result = _InternalRequest( template:"#uri#/test.cfm" diff --git a/test/tickets/_LDEV1445.cfc b/test/tickets/_LDEV1445.cfc index 30ae19e55b..58ae32d47c 100644 --- a/test/tickets/_LDEV1445.cfc +++ b/test/tickets/_LDEV1445.cfc @@ -4,6 +4,8 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { variables.mySQL= getCredentials(); // Admin password variables.adminPassword = request.WEBADMINPASSWORD; + + systemOutput(variables.mySQL, true); return structisEmpty(variables.mySQL); } @@ -51,6 +53,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { } private struct function getCredentials() { - return server.getDatasource("mysql"); + return server.getDatasource(service="mysql", onlyConfig=true); } } diff --git a/test/tickets/_LDEV1525.cfc b/test/tickets/_LDEV1525.cfc deleted file mode 100644 index 2677d5bbc2..0000000000 --- a/test/tickets/_LDEV1525.cfc +++ /dev/null @@ -1,22 +0,0 @@ -component extends="org.lucee.cfml.test.LuceeTestCase"{ - function beforeAll(){ - variables.uri = createURI("LDEV1525"); - } - function run( testResults , testBox ) { - describe( "test case for LDEV-1525", function() { - it(title = "Checking QoQ while missing column", body = function( currentSpec ) { - local.result = _InternalRequest( - template:"#variables.uri#/test.cfm" - ); - var replaceName = replace(local.result.filecontent,"org.hsqldb.Expression","name"); - var changeName = listfirst(replaceName,"@") & " " & "in" & listdeleteat(listlast(replaceName,"@"),"1","in"); - expect(local.result.filecontent.trim()).toBe(changeName); - }); - - }); - } - private string function createURI(string calledName){ - var baseURI="/test/#listLast(getDirectoryFromPath(getCurrenttemplatepath()),"\/")#/"; - return baseURI&""&calledName; - } -} \ No newline at end of file diff --git a/test/tickets/_LDEV1671.cfc b/test/tickets/_LDEV1671.cfc index 603a665110..db9e96e5b8 100644 --- a/test/tickets/_LDEV1671.cfc +++ b/test/tickets/_LDEV1671.cfc @@ -12,7 +12,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="mysql" { } function run( testResults , testBox ) { describe( "test case for LDEV-1671", function() { - it(title = "Checking value attribute of cfqueryparam is blank", body = function( currentSpec ) { + it(title = "Checking value attribute of cfqueryparam is blank", skip=isNotSupported(), body = function( currentSpec ) { local.result = _InternalRequest( template:"#variables.uri#/test.cfm" ); diff --git a/test/tickets/_LDEV2133.cfc b/test/tickets/_LDEV2133.cfc index cbea87437e..3b15d0d91d 100644 --- a/test/tickets/_LDEV2133.cfc +++ b/test/tickets/_LDEV2133.cfc @@ -1,10 +1,10 @@ -component extends="org.lucee.cfml.test.LuceeTestCase"{ +component extends="org.lucee.cfml.test.LuceeTestCase" { function beforeAll(){ variables.uri = createURI("LDEV2133"); } function run( testResults , testBox ) { describe( "test case for LDEV-2133", function() { - it(title = "checking abort with type='page in cfscript'", body = function( currentSpec ) { + it(title = "checking abort with type='page in cfscript'", skip=true, body = function( currentSpec ) { local.result = _InternalRequest( template:"#variables.uri#/test.cfm", forms:{Scene=1} @@ -28,7 +28,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ expect(local.result.filecontent.trim()).toBe('Page type'); }); - it(title = "checking cfabort with type='request'", body = function( currentSpec ) { + it(title = "checking cfabort with type='request'", skip=true, body = function( currentSpec ) { local.result = _InternalRequest( template:"#variables.uri#/test.cfm", forms:{Scene=4} diff --git a/test/tickets/_LDEV2374.cfc b/test/tickets/_LDEV2374.cfc deleted file mode 100644 index 5ca7d69b58..0000000000 --- a/test/tickets/_LDEV2374.cfc +++ /dev/null @@ -1,24 +0,0 @@ -component extends="org.lucee.cfml.test.LuceeTestCase"{ - function run( testResults , testBox ) { - describe( "Test suite for LDEV-2374", function() { - it( title='Checking url scope with multiple dot notation', body=function( currentSpec ) { - if(Not structkeyexists(url,"res") && Not structkeyexists(url,"test")){ - cflocation(url="http://#cgi.HTTP_HOST##cgi.script_name#?method=runremote&ts=10&res...temp=1",addtoken="false"); - } - expect(structCount(url)).toBe('2'); - expect(structkeyexists(url,"res")).toBe('False'); - expect(structkeyexists(url,"res...temp")).toBe('true'); - }); - - it( title='Checking url scope with single dot notation', body=function( currentSpec ) { - if(Not structkeyexists(url,"test")){ - cflocation(url="http://#cgi.HTTP_HOST##cgi.script_name#?method=runremote&mt=10&test.temp=lucee",addtoken="false"); - } - expect(url.test.temp EQ "lucee").toBe('true'); - expect(structCount(url)).toBe('2'); - expect(structkeyexists(url,"test")).toBe('False'); - expect(structkeyexists(url,"test.temp")).toBe('true'); - }); - }); - } -} diff --git a/test/tickets/_LDEV2530.cfc b/test/tickets/_LDEV2530.cfc index 4969f2aac2..4a2fe21a8b 100644 --- a/test/tickets/_LDEV2530.cfc +++ b/test/tickets/_LDEV2530.cfc @@ -1,7 +1,8 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq"{ +// this is simply crappy code, and will never be supported by lucee, WONT FIX +component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq" { function run( testResults , testBox ) { describe( "test suite for LDEV-2530", function() { - it(title = "Query returns single column instead of multiple columns", body = function( currentSpec ) { + it(title = "Query returns single column instead of multiple columns with the same name (WONTFIX)", body = function( currentSpec ) { a = querynew("id","integer",[{"id": 1},{"id": 2},{"id": 3}]); b = duplicate(a); query name="qry" dbtype="query" { diff --git a/test/tickets/_LDEV2549.cfc b/test/tickets/_LDEV2549.cfc deleted file mode 100644 index 8585c27602..0000000000 --- a/test/tickets/_LDEV2549.cfc +++ /dev/null @@ -1,36 +0,0 @@ -component extends = "org.lucee.cfml.test.LuceeTestCase" labels="mssql"{ - - function beforeAll() { - variables.uri = createURI("LDEV2549"); - } - - function run( testResults , testBox ) { - describe( "test suite for LDEV2549",skip=isNotSupported(), function() { - it(title = "query param not working without CFSQLTYPE for date type values", body = function( currentSpec ) { - local.result = _InternalRequest( - template : "#uri#/LDEV2549.cfm", - forms: {scene=1} - ); - expect(1).toBe(result.filecontent); - }); - - it(title = "query param works fine with CFSQLTYPE for date type values", body = function( currentSpec ) { - local.result = _InternalRequest( - template : "#uri#/LDEV2549.cfm", - forms: {scene=2} - ); - expect(2).toBe(local.result.filecontent); - }); - }); - } - - private string function createURI(string calledName){ - var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrenttemplatepath()),"\/")#/"; - return baseURI&""&calledName; - } - - private boolean function isNotSupported() { - // getting the credentials from the environment variables - return ( structCount(server.getDatasource("mssql")) eq 0 ); - } -} \ No newline at end of file diff --git a/travis-docker-build.sh b/travis-docker-build.sh new file mode 100644 index 0000000000..32b8590135 --- /dev/null +++ b/travis-docker-build.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# get lucee version +LUCEE_VERSION=$(