From 90bfad5b6f41e0cc261836531635ebc050d290d1 Mon Sep 17 00:00:00 2001 From: CF Mitrah Date: Wed, 11 Mar 2026 12:23:56 +0530 Subject: [PATCH 1/3] LDEV-6135 Added test case for CFTHREAD JOIN issue --- test/tickets/LDEV6135.cfc | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 test/tickets/LDEV6135.cfc diff --git a/test/tickets/LDEV6135.cfc b/test/tickets/LDEV6135.cfc new file mode 100644 index 00000000000..e91622b21d9 --- /dev/null +++ b/test/tickets/LDEV6135.cfc @@ -0,0 +1,37 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults , testBox ) { + describe( title="Test suite for LDEV-6135", body=function() { + + it(title="cfthread join should wait for all threads", skip=true, body=function(currentSpec){ + var threadSize = 200; + var threadNames = []; + for(i=1; i <= threadSize; i++){ + var threadName = "thread_test_#i#_" & replace(createUUID(), "-", "", "all"); + threadNames.append(threadName); + thread action="run" name="#threadName#" { + try { + sleep(randRange(500, 1500)); + throw("here"); + } catch (Any e){ + thread.errored = true; + } + thread.finished = true; + } + } + thread action="join" name="#threadNames.toList()#" timeout="60000"; + + // Check all threads finished + var finishedCount = 0; + structEach(cfthread, function(tname, tresult){ + var finished = tresult.FINISHED ?: false; + if(finished) finishedCount++; + }); + + expect(finishedCount).toBe(threadSize); + }); + + }); + } + +} From f71134f5e4bbb892946c4704ace1e5514c27b7a6 Mon Sep 17 00:00:00 2001 From: CF Mitrah Date: Thu, 12 Mar 2026 12:51:48 +0530 Subject: [PATCH 2/3] Updated the missed file changes. --- .../lucee/runtime/config/DeployHandler.java | 6 +- .../config/maven/ExtensionProvider.java | 63 ++-- .../config/maven/MavenUpdateProvider.java | 20 +- .../runtime/config/maven/MetadataReader.java | 12 +- .../runtime/config/maven/RepoReader.java | 4 +- .../lucee/runtime/config/maven/Version.java | 318 ++++++++++++++++++ .../lucee/runtime/engine/CFMLEngineImpl.java | 2 + .../lucee/runtime/exp/NativeException.java | 3 +- .../runtime/extension/ExtensionMetadata.java | 11 + .../lucee/runtime/extension/RHExtension.java | 9 +- .../functions/system/LuceeExtension.java | 18 +- .../system/LuceeVersionsDetailMvn.java | 4 +- .../system/LuceeVersionsListMvn.java | 6 +- .../java/lucee/runtime/osgi/OSGiUtil.java | 74 ++-- .../main/java/lucee/runtime/tag/Admin.java | 36 +- loader/build.xml | 2 +- loader/pom.xml | 2 +- 17 files changed, 465 insertions(+), 125 deletions(-) create mode 100644 core/src/main/java/lucee/runtime/config/maven/Version.java diff --git a/core/src/main/java/lucee/runtime/config/DeployHandler.java b/core/src/main/java/lucee/runtime/config/DeployHandler.java index 767a748b4c7..eb47aaa6d79 100644 --- a/core/src/main/java/lucee/runtime/config/DeployHandler.java +++ b/core/src/main/java/lucee/runtime/config/DeployHandler.java @@ -26,8 +26,6 @@ import java.util.List; import java.util.Map; -import org.osgi.framework.Version; - import lucee.commons.io.IOUtil; import lucee.commons.io.SystemUtil; import lucee.commons.io.log.Log; @@ -48,6 +46,7 @@ import lucee.commons.net.http.httpclient.HeaderImpl; import lucee.runtime.config.ConfigAdmin.AlreadyInstalledExtension; import lucee.runtime.config.maven.ExtensionProvider; +import lucee.runtime.config.maven.Version; import lucee.runtime.engine.CFMLEngineImpl; import lucee.runtime.engine.ThreadQueue; import lucee.runtime.exp.ApplicationException; @@ -59,7 +58,6 @@ import lucee.runtime.functions.system.IsZipFile; import lucee.runtime.net.http.ReqRspUtil; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.type.Struct; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.KeyConstants; @@ -507,7 +505,7 @@ private static Resource downloadExtensionFromMaven(Config config, ExtensionDefin } } else { - version = OSGiUtil.toVersion(ed.getVersion(), false); + version = Version.parseVersion(ed.getVersion()); if (LogUtil.doesDebug(log) && version != null) { log.debug("main", "use defined [" + version + "] for artifact [" + artifact + "]"); } diff --git a/core/src/main/java/lucee/runtime/config/maven/ExtensionProvider.java b/core/src/main/java/lucee/runtime/config/maven/ExtensionProvider.java index eecbb4406fa..d45556c78cd 100644 --- a/core/src/main/java/lucee/runtime/config/maven/ExtensionProvider.java +++ b/core/src/main/java/lucee/runtime/config/maven/ExtensionProvider.java @@ -18,7 +18,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.osgi.framework.Version; import org.xml.sax.SAXException; import lucee.aprint; @@ -42,7 +41,6 @@ import lucee.runtime.mvn.MavenUtil.GAVSO; import lucee.runtime.mvn.POM; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.tag.Http; import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.util.ArrayUtil; @@ -214,6 +212,11 @@ private Set listAllProjects() throws InterruptedException, IOException { threads.add(thread); } + // Join all threads + for (Thread thread: threads) { + thread.join(); + } + // handle exceptions if (exceptions.size() > 0) { Exception e = exceptions.pop(); @@ -221,10 +224,6 @@ private Set listAllProjects() throws InterruptedException, IOException { throw ExceptionUtil.toIOException(e); } - // Join all threads - for (Thread thread: threads) { - thread.join(); - } return subfolders; } @@ -288,7 +287,7 @@ public ExtensionDefintion toExtensionDefintion(Config config, GAVSO gavso, boole version = last(gavso.a); } else { - version = OSGiUtil.toVersion(gavso.v); + version = Version.parseVersion(gavso.v); } Resource res = getResource((ConfigPro) config, gavso.a, version); @@ -380,12 +379,12 @@ public Version last(String artifact) throws IOException, GeneralSecurityExceptio for (Version v: list(artifact)) { if (v.toString().toUpperCase().endsWith("-SNAPSHOT")) { - if (lastRel == null || OSGiUtil.compare(lastRel, v) < 0) { + if (lastRel == null || Version.compare(lastRel, v) < 0) { lastRel = v; } } - if (last == null || OSGiUtil.compare(last, v) < 0) { + if (last == null || Version.compare(last, v) < 0) { last = v; } @@ -434,8 +433,7 @@ public Map detail(String artifact, Version version, Map list = ep.list("image-extension"); + for (Version v: list) { + aprint.e(ep.detail("image-extension", v)); + } + + aprint.e(list); + aprint.e("extension-image:" + (System.currentTimeMillis() - start)); + } + if (true) return; start = System.currentTimeMillis(); aprint.e(ep.list("ehcache-extension")); @@ -604,11 +601,11 @@ public static void main(String[] args) throws Exception { { start = System.currentTimeMillis(); - Map detail = ep.detail("redis-extension", OSGiUtil.toVersion("3.0.0.56-SNAPSHOT")); + Map detail = ep.detail("redis-extension", Version.parseVersion("3.0.0.56-SNAPSHOT")); aprint.e("detail:" + (System.currentTimeMillis() - start)); aprint.e(detail); - ep.get("redis-extension", OSGiUtil.toVersion("3.0.0.56-SNAPSHOT")); + ep.get("redis-extension", Version.parseVersion("3.0.0.56-SNAPSHOT")); } if (true) return; @@ -643,7 +640,7 @@ public static void main(String[] args) throws Exception { aprint.e(versions); start = System.currentTimeMillis(); - Map detail = ep.detail("mssql-jdbc-extension", OSGiUtil.toVersion("6.5.4")); + Map detail = ep.detail("mssql-jdbc-extension", Version.parseVersion("6.5.4")); aprint.e("detail:" + (System.currentTimeMillis() - start)); aprint.e(detail); @@ -672,4 +669,4 @@ public static void main(String[] args) throws Exception { // print.e(mup.list()); } -} +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/config/maven/MavenUpdateProvider.java b/core/src/main/java/lucee/runtime/config/maven/MavenUpdateProvider.java index 6e4d11fe1c6..94e81d7756e 100644 --- a/core/src/main/java/lucee/runtime/config/maven/MavenUpdateProvider.java +++ b/core/src/main/java/lucee/runtime/config/maven/MavenUpdateProvider.java @@ -19,7 +19,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import org.osgi.framework.Version; import org.xml.sax.SAXException; import lucee.commons.digest.HashUtil; @@ -44,7 +43,6 @@ import lucee.runtime.op.CastImpl; import lucee.runtime.op.Caster; import lucee.runtime.op.date.DateCaster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.thread.ThreadUtil; import lucee.runtime.type.util.ListUtil; @@ -55,7 +53,7 @@ public final class MavenUpdateProvider { // new last 90 days private static final Repository DEFAULT_REPOSITORY_SONATYPE_LAST90 = new Repository("Sonatype Repositry for Snapshots (last 90 days)", - "https://central.sonatype.com/repository/maven-snapshots/", Repository.TIMEOUT_15MINUTES, Repository.TIMEOUT_NEVER); + "https://central.sonatype.com/repository/maven-snapshots/", Repository.TIMEOUT_1HOUR, Repository.TIMEOUT_NEVER); private static final Repository[] DEFAULT_REPOSITORY_SNAPSHOTS_CORE = new Repository[] { DEFAULT_REPOSITORY_SONATYPE_LAST90 }; private static final Repository[] DEFAULT_REPOSITORY_SNAPSHOTS_EXTENSIONS = new Repository[] { DEFAULT_REPOSITORY_SONATYPE_LAST90 }; @@ -209,6 +207,11 @@ public List list() throws IOException, GeneralSecurityException, SAXExc threads.add(thread); } + // Join all threads + for (Thread thread: threads) { + thread.join(); + } + // handle exceptions if (exceptions.size() > 0) { Exception e = exceptions.pop(); @@ -218,14 +221,9 @@ public List list() throws IOException, GeneralSecurityException, SAXExc throw ExceptionUtil.toIOException(new IOException("Failed to list available versions from Maven repositories for [" + group + ":" + artifact + "]", e)); } - // Join all threads - for (Thread thread: threads) { - thread.join(); - } - if (versions.size() > 0) { List sortedList = new ArrayList<>(versions); - Collections.sort(sortedList, OSGiUtil::compare); + Collections.sort(sortedList, Version::compare); return sortedList; } @@ -274,7 +272,7 @@ public Map detail(Version version, String requiredArtifactExtens // SNAPSHOT - snapshot have a more complicated structure, ebcause there can be udaptes/multiple // versions - boolean isSnap = version.getQualifier().endsWith("-SNAPSHOT"); + boolean isSnap = version.getQualifier().equals("SNAPSHOT"); List repos = isSnap ? merge(repoSnapshots, repoMixed) : merge(repoReleases, repoMixed); if (requiredArtifactExtension == null) requiredArtifactExtension = "jar"; @@ -516,4 +514,4 @@ else if (policy == CFMLEngineImpl.MAVEN_DOWNLOAD_POLICY_WARN) { + "Set 'lucee.maven.download.policy' to 'error' to block downloads " + "or 'ignore' to suppress this warning."); } } -} +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/config/maven/MetadataReader.java b/core/src/main/java/lucee/runtime/config/maven/MetadataReader.java index c14a60e9873..a0624e7ba79 100644 --- a/core/src/main/java/lucee/runtime/config/maven/MetadataReader.java +++ b/core/src/main/java/lucee/runtime/config/maven/MetadataReader.java @@ -9,8 +9,6 @@ import java.util.List; import java.util.Stack; -import org.osgi.framework.BundleException; -import org.osgi.framework.Version; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -26,7 +24,6 @@ import lucee.commons.net.http.HTTPDownloader; import lucee.runtime.config.maven.MavenUpdateProvider.Repository; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.text.xml.XMLUtil; import lucee.runtime.type.util.ListUtil; import lucee.transformer.library.function.FunctionLibEntityResolver; @@ -83,7 +80,8 @@ public List read() throws IOException, GeneralSecurityException, SAXExc // Use HTTPDownloader with DEBUG logging for Maven metadata lookups Reader r = null; try { - r = IOUtil.getReader( HTTPDownloader.get( url, null, null, MavenUpdateProvider.CONNECTION_TIMEOUT, MavenUpdateProvider.READ_TIMEOUT, null, Log.LEVEL_TRACE ), (Charset) null ); + r = IOUtil.getReader(HTTPDownloader.get(url, null, null, MavenUpdateProvider.CONNECTION_TIMEOUT, MavenUpdateProvider.READ_TIMEOUT, null, Log.LEVEL_TRACE), + (Charset) null); init(new InputSource(r)); } catch (IOException ioe) { @@ -132,7 +130,7 @@ private List readFromCache(String appendix) { if (content.length() > 0) { List list = ListUtil.listToList(content, ',', true); for (String v: list) { - versions.add(OSGiUtil.toVersion(v.trim())); + versions.add(Version.parseVersion(v.trim())); } } return versions; @@ -176,9 +174,9 @@ public void endElement(String uri, String name, String qName) { if (insideVersion) { insideVersion = false; try { - versions.add(OSGiUtil.toVersion(content.toString().trim(), false)); + versions.add(Version.parseVersion(content.toString().trim())); } - catch (BundleException e) { + catch (Exception e) { LogUtil.log("MavenReader", e); } } diff --git a/core/src/main/java/lucee/runtime/config/maven/RepoReader.java b/core/src/main/java/lucee/runtime/config/maven/RepoReader.java index a039746cb84..3ea0a9eb325 100644 --- a/core/src/main/java/lucee/runtime/config/maven/RepoReader.java +++ b/core/src/main/java/lucee/runtime/config/maven/RepoReader.java @@ -15,7 +15,6 @@ import java.util.Map.Entry; import java.util.Stack; -import org.osgi.framework.Version; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -75,7 +74,8 @@ public Map read(String requiredArtifactExtension) throws IOExcep // Use HTTPDownloader with DEBUG logging for Maven repo metadata lookups Reader r = null; try { - r = IOUtil.getReader( HTTPDownloader.get( url, null, null, MavenUpdateProvider.CONNECTION_TIMEOUT, MavenUpdateProvider.READ_TIMEOUT, null, Log.LEVEL_TRACE ), (Charset) null ); + r = IOUtil.getReader(HTTPDownloader.get(url, null, null, MavenUpdateProvider.CONNECTION_TIMEOUT, MavenUpdateProvider.READ_TIMEOUT, null, Log.LEVEL_TRACE), + (Charset) null); init(new InputSource(r)); } catch (IOException ioe) { diff --git a/core/src/main/java/lucee/runtime/config/maven/Version.java b/core/src/main/java/lucee/runtime/config/maven/Version.java new file mode 100644 index 00000000000..38f0125952c --- /dev/null +++ b/core/src/main/java/lucee/runtime/config/maven/Version.java @@ -0,0 +1,318 @@ +package lucee.runtime.config.maven; +/* + * Maven-compatible Version class. + * + * Key differences from OSGi Version: + * - minor, micro, and build may be null when not present in the original string + * - qualifier separator is '-' (Maven) not '.' (OSGi) + * - compareTo() uses Maven ordering rules + * + * Maven version grammar handled here: + * version ::= major('.'minor('.'micro('.'build)?)?)?('-'qualifier)? + * major ::= digit+ + * minor ::= digit+ (optional – null if absent) + * micro ::= digit+ (optional – null if absent) + * build ::= digit+ (optional – null if absent, e.g. "0.9.4.119-RC") + * qualifier ::= any non-empty string (SNAPSHOT, BETA, RC1, Final, …) + */ + +import java.io.IOException; + +import lucee.runtime.op.Caster; + +public class Version implements Comparable { + + // ------------------------------------------------------------------ fields + + private final int major; + private final Integer minor; // null when not specified in input + private final Integer micro; // null when not specified in input + private final Integer build; // null when not specified in input + /** Empty string means "no qualifier", never null. */ + private final String qualifier; + /** Canonical string built at construction time, used by toString(). */ + private final String original; + + private transient int hash; // lazy cache + + // --------------------------------------------------------- well-known constants + + /** The empty version "0.0.0". */ + public static final Version emptyVersion = new Version(0, 0, 0); + + // --------------------------------------------------------------- constructors + + /** + * Creates a version from numeric components; qualifier is set to "". + */ + public Version(int major, int minor, int micro) { + this(major, minor, micro, null, null); + } + + /** + * Internal constructor — all parsing goes through {@link #parseVersion(String)} or + * {@link #parseVersion(String, Version)}; this constructor only accepts already-validated + * components. + */ + private Version(int major, Integer minor, Integer micro, Integer build, String qualifier) { + if (major < 0) throw new IllegalArgumentException("invalid version: negative major \"" + major + "\""); + if (minor != null && minor < 0) throw new IllegalArgumentException("invalid version: negative minor \"" + minor + "\""); + if (micro != null && micro < 0) throw new IllegalArgumentException("invalid version: negative micro \"" + micro + "\""); + if (build != null && build < 0) throw new IllegalArgumentException("invalid version: negative build \"" + build + "\""); + + this.major = major; + this.minor = minor; + this.micro = micro; + this.build = build; + this.qualifier = (qualifier == null || qualifier.trim().isEmpty()) ? "" : qualifier.trim(); + this.original = buildString(major, minor, micro, build, this.qualifier); + } + + // --------------------------------------------------------- static factories + + /** Static factory — returns {@link #emptyVersion} for null/empty input. */ + public static Version valueOf(String version) { + if (version == null || version.trim().isEmpty()) return emptyVersion; + return parseVersion(version, emptyVersion); + } + + // ----------------------------------------------------------------- getters + + /** Returns the major component. Always present. */ + public int getMajor() { + return major; + } + + /** + * Returns the minor component exactly as parsed, or {@code null} if it was not present in the + * version string (e.g. {@code "2-BETA"}). + */ + public Integer getMinor() { + return minor; + } + + /** + * Returns the micro component exactly as parsed, or {@code null} if it was not present in the + * version string (e.g. {@code "2.5-BETA"}). + */ + public Integer getMicro() { + return micro; + } + + /** + * Returns the build component exactly as parsed, or {@code null} if it was not present in the + * version string (e.g. {@code "0.9.4-RC"} has no build, but {@code "0.9.4.119-RC"} has build 119). + */ + public Integer getBuild() { + return build; + } + + /** Returns the qualifier, or the empty string if there is none. */ + public String getQualifier() { + return qualifier; + } + + // ---------------------------------------------------------------- toString + + /** + * Returns the canonical string: "major", "major.minor", "major.minor.micro", or + * "major.minor.micro.build", each optionally followed by "-qualifier". + */ + @Override + public String toString() { + return original; + } + + // --------------------------------------------------------------- hashCode / equals + + @Override + public int hashCode() { + int h = hash; + if (h != 0) return h; + h = 31 * 17; + h = 31 * h + major; + h = 31 * h + minor(); + h = 31 * h + micro(); + h = 31 * h + build(); + h = 31 * h + qualifier.hashCode(); + return hash = h; + } + + /** + * Two versions are equal when major, effective minor, effective micro, effective build, and + * qualifier are all equal (absent components treated as 0). + * Note: "1.0.0" and "1.0.0.0" are considered equal by this contract. + */ + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof Version)) return false; + Version o = (Version) obj; + return major == o.major && minor() == o.minor() && micro() == o.micro() && build() == o.build() && qualifier.equals(o.qualifier); + } + + // --------------------------------------------------------------- compareTo + + /** + * Maven-style ordering: + *
    + *
  1. Compare major, minor, micro, build numerically (absent treated as 0).
  2. + *
  3. A release (no qualifier) is newer than any pre-release.
  4. + *
  5. {@code SNAPSHOT} is always the oldest qualifier.
  6. + *
  7. Other qualifiers compared case-insensitively.
  8. + *
+ */ + @Override + public int compareTo(Version other) { + if (other == this) return 0; + + int result = Integer.compare(major, other.major); + if (result != 0) return result; + + result = Integer.compare(minor(), other.minor()); + if (result != 0) return result; + + result = Integer.compare(micro(), other.micro()); + if (result != 0) return result; + + result = Integer.compare(build(), other.build()); + if (result != 0) return result; + + return compareQualifiers(qualifier, other.qualifier); + } + + public static int compare(Version v1, Version v2) { + if (v1 == v2) return 0; + if (v1 == null) return -1; + if (v2 == null) return 1; + return v1.compareTo(v2); + } + + // --------------------------------------------------------------- parseVersion factories + + /** + * Lenient parse — returns {@code defaultValue} instead of throwing. + * + * Accepts both hyphen-separated qualifiers ("2.5.2-BETA") and dot-separated ones ("2.5.2.BETA"), as + * well as partial forms: "major", "major.minor", "major.minor.micro", "major.minor.micro.build". + * + * @param version the version string to parse; may be null/empty + * @param defaultValue returned when the string cannot be parsed + */ + public static Version parseVersion(String version, Version defaultValue) { + if (version == null || version.trim().isEmpty()) return defaultValue; + + version = version.trim(); + String[] arr = version.split("\\.", -1); + for (int i = 0; i < arr.length; i++) + arr[i] = arr[i].trim(); + + Integer major, minor, micro, build; + String qualifier; + + switch (arr.length) { + case 1: { + String[] hp = arr[0].split("-", 2); + major = Caster.toInteger(hp[0], null); + minor = null; + micro = null; + build = null; + qualifier = hp.length > 1 ? hp[1] : null; + break; + } + case 2: { + major = Caster.toInteger(arr[0], null); + String[] hp = arr[1].split("-", 2); + minor = Caster.toInteger(hp[0], null); + micro = null; + build = null; + qualifier = hp.length > 1 ? hp[1] : null; + break; + } + case 3: { + major = Caster.toInteger(arr[0], null); + minor = Caster.toInteger(arr[1], null); + String[] hp = arr[2].split("-", 2); + micro = Caster.toInteger(hp[0], null); + build = null; + qualifier = hp.length > 1 ? hp[1] : null; + break; + } + default: { + // 4 dot-parts: "major.minor.micro.build[-qualifier]" — 5+ segments are not supported and will return defaultValue + major = Caster.toInteger(arr[0], null); + minor = Caster.toInteger(arr[1], null); + micro = Caster.toInteger(arr[2], null); + String[] hp = arr[3].split("-", 2); + build = Caster.toInteger(hp[0], null); + qualifier = hp.length > 1 ? hp[1] : null; + break; + } + } + + if (major == null || (arr.length >= 2 && minor == null) || (arr.length >= 3 && micro == null) || (arr.length >= 4 && build == null)) + return defaultValue; + + return new Version(major, minor, micro, build, qualifier); + } + + /** + * Strict parse — throws {@code IOException} on failure. + * + * @throws IOException if {@code version} cannot be parsed + */ + public static Version parseVersion(String version) throws IOException { + Version v = parseVersion(version, null); + if (v != null) return v; + throw new IOException("Given version [" + version + "] is invalid, a valid version follows the pattern [.[.[.]]][-]"); + } + + // ----------------------------------------------------------------- helpers + + /** Returns minor as int, treating absent (null) as 0. */ + private int minor() { + return minor == null ? 0 : minor; + } + + /** Returns micro as int, treating absent (null) as 0. */ + private int micro() { + return micro == null ? 0 : micro; + } + + /** Returns build as int, treating absent (null) as 0. */ + private int build() { + return build == null ? 0 : build; + } + + /** Builds the canonical string from components. */ + private static String buildString(int major, Integer minor, Integer micro, Integer build, String qualifier) { + StringBuilder sb = new StringBuilder(24); + sb.append(major); + if (minor != null) { + sb.append('.').append(minor); + if (micro != null) { + sb.append('.').append(micro); + if (build != null) sb.append('.').append(build); + } + } + if (qualifier != null && !qualifier.isEmpty()) sb.append('-').append(qualifier); + return sb.toString(); + } + + /** + * Maven qualifier ordering: - empty (release) beats any qualifier - SNAPSHOT is always the lowest + * qualifier - everything else compared case-insensitively + */ + private static int compareQualifiers(String q1, String q2) { + boolean e1 = q1.isEmpty(), e2 = q2.isEmpty(); + if (e1 && e2) return 0; + if (e1) return 1; + if (e2) return -1; + boolean s1 = q1.equalsIgnoreCase("SNAPSHOT"); + boolean s2 = q2.equalsIgnoreCase("SNAPSHOT"); + if (s1 && s2) return 0; + if (s1) return -1; + if (s2) return 1; + return q1.compareToIgnoreCase(q2); + } +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java b/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java index 096e747f372..b1118eb2653 100644 --- a/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java +++ b/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java @@ -79,6 +79,7 @@ import lucee.commons.lang.Pair; import lucee.commons.lang.StringUtil; import lucee.commons.net.HTTPUtil; +import lucee.commons.net.http.HTTPDownloader; import lucee.commons.net.http.httpclient.HTTPEngine4Impl; import lucee.intergral.fusiondebug.server.FDControllerImpl; import lucee.loader.engine.CFMLEngine; @@ -1319,6 +1320,7 @@ public void reset(String configId) { // release HTTP Pool HTTPEngine4Impl.releaseConnectionManager(); + HTTPDownloader.releaseSharedClient(); releaseCache(getConfigServerImpl(null, false, true)); diff --git a/core/src/main/java/lucee/runtime/exp/NativeException.java b/core/src/main/java/lucee/runtime/exp/NativeException.java index 27d8bbfedab..2500a11d94a 100644 --- a/core/src/main/java/lucee/runtime/exp/NativeException.java +++ b/core/src/main/java/lucee/runtime/exp/NativeException.java @@ -18,6 +18,7 @@ **/ package lucee.runtime.exp; +import java.util.Collections; import java.util.Map; import org.apache.commons.collections4.map.LRUMap; @@ -41,7 +42,7 @@ public class NativeException extends PageExceptionImpl { private static final long serialVersionUID = 6221156691846424801L; private Throwable t; - private static final Map instances = new LRUMap(1000); + private static final Map instances = Collections.synchronizedMap( new LRUMap( 1000 ) ); /** * Standart constructor for native Exception class diff --git a/core/src/main/java/lucee/runtime/extension/ExtensionMetadata.java b/core/src/main/java/lucee/runtime/extension/ExtensionMetadata.java index bd6422c8493..7a1a68d09a3 100644 --- a/core/src/main/java/lucee/runtime/extension/ExtensionMetadata.java +++ b/core/src/main/java/lucee/runtime/extension/ExtensionMetadata.java @@ -15,6 +15,7 @@ import lucee.runtime.op.Decision; import lucee.runtime.osgi.BundleInfo; import lucee.runtime.osgi.VersionRange; +import lucee.runtime.type.dt.DateTime; import lucee.runtime.type.util.ListUtil; public final class ExtensionMetadata implements Serializable { @@ -92,6 +93,8 @@ public final class ExtensionMetadata implements Serializable { private transient List> eventGatewayInstances; private String eventGatewayInstancesRaw; + private DateTime builtDate; + public List> getEventGatewayInstances() { if (eventGatewayInstances == null) { if (!StringUtil.isEmpty(eventGatewayInstancesRaw, true)) { @@ -355,6 +358,14 @@ public void setDescription(String description) { this.description = description; } + public DateTime getBuiltDate() { + return builtDate; + } + + public void setBuiltDate(DateTime builtDate) { + this.builtDate = builtDate; + } + public String getSymbolicName() { return StringUtil.isEmpty(symbolicName) ? _getId() : symbolicName; } diff --git a/core/src/main/java/lucee/runtime/extension/RHExtension.java b/core/src/main/java/lucee/runtime/extension/RHExtension.java index 83035d5d7fa..e1bcfa10815 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.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.jar.Attributes; import java.util.jar.Manifest; @@ -98,6 +99,7 @@ import lucee.runtime.type.QueryImpl; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; +import lucee.runtime.type.dt.DateTime; import lucee.runtime.type.query.CurrentRow; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.KeyConstants; @@ -664,6 +666,11 @@ private static void readManifestConfig(Config config, ExtensionMetadata metadata label += " : " + metadata._getVersion(); metadata.setId(StringUtil.unwrap(attr.getValue("id")), label); metadata.setDescription(StringUtil.unwrap(attr.getValue("description"))); + String str = StringUtil.unwrap(attr.getValue("Built-Date")); + if (!StringUtil.isEmpty(str, true)) { + DateTime dt = Caster.toDate(str, false, TimeZone.getDefault(), null); + if (dt != null) metadata.setBuiltDate(dt); + } metadata.setTrial(Caster.toBooleanValue(StringUtil.unwrap(attr.getValue("trial")), false)); if (_img == null) _img = StringUtil.unwrap(attr.getValue("image")); metadata.setImage(_img); @@ -1583,4 +1590,4 @@ public static void removeDuplicates(Array arrExtensions) throws PageException, B } } -} +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/system/LuceeExtension.java b/core/src/main/java/lucee/runtime/functions/system/LuceeExtension.java index c09706eaea1..9f3967dd3be 100644 --- a/core/src/main/java/lucee/runtime/functions/system/LuceeExtension.java +++ b/core/src/main/java/lucee/runtime/functions/system/LuceeExtension.java @@ -3,18 +3,18 @@ import java.util.Map; import java.util.Map.Entry; -import org.osgi.framework.Version; - import lucee.commons.io.res.Resource; import lucee.runtime.PageContext; import lucee.runtime.config.ConfigPro; import lucee.runtime.config.maven.ExtensionProvider; import lucee.runtime.config.maven.MavenUpdateProvider; +import lucee.runtime.config.maven.Version; import lucee.runtime.exp.FunctionException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.function.BIF; +import lucee.runtime.extension.ExtensionMetadata; +import lucee.runtime.extension.RHExtension; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; import lucee.runtime.type.Struct; @@ -51,7 +51,7 @@ else if (args.length == 2) { else if (args.length == 3 || args.length == 4) { ExtensionProvider ep = new ExtensionProvider(Caster.toString(args[0]).trim()); String artifactId = Caster.toString(args[1]).trim(); - Version version = OSGiUtil.toVersion(Caster.toString(args[2]).trim()); + Version version = Version.parseVersion(Caster.toString(args[2]).trim()); // detail Struct sct = new StructImpl(); @@ -67,6 +67,16 @@ else if (args.length == 3 || args.length == 4) { if (download) { Resource local = ep.getResource((ConfigPro) pc.getConfig(), artifactId, version); sct.set(KeyConstants._local, local.getAbsolutePath()); + + RHExtension ext = RHExtension.getInstance(pc.getConfig(), local); + ExtensionMetadata em = ext.getMetadata(); + Struct meta = new StructImpl(); + meta.set(KeyConstants._id, em._getId()); + meta.set(KeyConstants._name, em.getName()); + meta.set(KeyConstants._description, em.getDescription()); + meta.set(KeyConstants._image, em.getImage()); + if (em.getBuiltDate() != null) meta.set("buildDate", em.getBuiltDate()); + sct.set(KeyConstants._metadata, meta); } return sct; diff --git a/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsDetailMvn.java b/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsDetailMvn.java index fc9625ffa81..b90056c9184 100644 --- a/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsDetailMvn.java +++ b/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsDetailMvn.java @@ -5,11 +5,11 @@ import lucee.runtime.PageContext; import lucee.runtime.config.maven.MavenUpdateProvider; +import lucee.runtime.config.maven.Version; import lucee.runtime.exp.FunctionException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; @@ -23,7 +23,7 @@ public static Struct call(PageContext pc, String version) throws PageException { try { MavenUpdateProvider mup = new MavenUpdateProvider(); - for (Entry e: mup.detail(OSGiUtil.toVersion(version), "jar", true).entrySet()) { + for (Entry e: mup.detail(Version.parseVersion(version), "jar", true).entrySet()) { sct.set(e.getKey(), e.getValue()); } diff --git a/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsListMvn.java b/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsListMvn.java index 4fda35088b6..9479792eb31 100644 --- a/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsListMvn.java +++ b/core/src/main/java/lucee/runtime/functions/system/LuceeVersionsListMvn.java @@ -4,16 +4,14 @@ import java.util.LinkedHashMap; import java.util.Map; -import org.osgi.framework.Version; - import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; import lucee.runtime.config.maven.MavenUpdateProvider; +import lucee.runtime.config.maven.Version; import lucee.runtime.exp.FunctionException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; -import lucee.runtime.osgi.OSGiUtil; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; @@ -63,7 +61,7 @@ else throw new FunctionException(pc, functionName, 1, "type", key = new StringBuilder().append(v.getMajor()).append('.').append(v.getMinor()).append('.').append(v.getMicro()).toString(); if (t == TYPE_ALL || (t == TYPE_SNAPSHOT && v.getQualifier().endsWith("-SNAPSHOT")) || (t == TYPE_RELEASE && !v.getQualifier().endsWith("-SNAPSHOT"))) { existing = map.get(key); - if (existing == null || OSGiUtil.compare(existing, v) < 0) { + if (existing == null || Version.compare(existing, v) < 0) { map.put(key, v); } } diff --git a/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java b/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java index aad1fd7bd22..d8dc9e589c5 100644 --- a/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java +++ b/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java @@ -172,7 +172,8 @@ private boolean accept(String name) { // packageBundleMapping.put("org.apache.log4j", "log4j"); packageBundleMapping.put("com.fasterxml.jackson.annotation", "com.fasterxml.jackson.core.jackson-annotations"); packageBundleMapping.put("org.apache.lucene.analysis", "apache.lucene"); - // Map packages from bundles removed from Lucee 7 core for backward compatibility with older extensions + // Map packages from bundles removed from Lucee 7 core for backward compatibility with older + // extensions packageBundleMapping.put("com.sun.jna", "com.sun.jna"); // packageBundleMapping.put("org.apache.commons.lang", "org.apache.commons.lang"); } @@ -295,6 +296,17 @@ else if (arr.length == 3) { minor = Caster.toInteger(arr[1], null); micro = Caster.toInteger(arr[2], null); qualifier = null; + if (micro == null) { + String[] arrMicro = ListUtil.listToStringArray(arr[2], '-'); + if (arrMicro.length == 2) { + Integer tmp = Caster.toInteger(arrMicro[0], null); + if (tmp != null) { + micro = tmp; + qualifier = arrMicro[1]; + } + } + } + } else { major = Caster.toInteger(arr[0], null); @@ -308,8 +320,7 @@ else if (arr.length == 3) { try { return new Version(version); } - catch (IllegalArgumentException e) { - } + catch (IllegalArgumentException e) {} } return defaultValue; } @@ -357,8 +368,7 @@ public static Class loadClass(String className, Class defaultValue) { return bc.core.loadClass(className); } } - catch (Exception e) { - } // class is not visible to the Lucee core + catch (Exception e) {} // class is not visible to the Lucee core // now we check all started bundled (not only bundles used by core) Bundle[] bundles = bc.getBundleContext().getBundles(); @@ -367,8 +377,7 @@ public static Class loadClass(String className, Class defaultValue) { try { return b.loadClass(className); } - catch (Exception e) { - } // class is not visible to that bundle + catch (Exception e) {} // class is not visible to that bundle } } @@ -381,8 +390,7 @@ public static Class loadClass(String className, Class defaultValue) { // print.e("loader:"); return cl.loadClass(className); } - catch (Exception e) { - } + catch (Exception e) {} } } @@ -407,8 +415,7 @@ public static Class loadClass(String className, Class defaultValue) { try { b = _loadBundle(bc.getBundleContext(), bf); } - catch (IOException e) { - } + catch (IOException e) {} if (b != null) { startIfNecessary(b); @@ -416,8 +423,7 @@ public static Class loadClass(String className, Class defaultValue) { try { return b.loadClass(className); } - catch (Exception e) { - } // class is not visible to that bundle + catch (Exception e) {} // class is not visible to that bundle } } } @@ -694,8 +700,8 @@ public static List loadBundles(BundleContext bc, final List List list = new ArrayList<>(); try { for (BundleRange br: bundleRanges) { - list.add(_loadBundle(bc == null ? CFMLEngineFactory.getInstance().getBundleContext() : bc, br, id, addional, startIfNecessary, parents, versionOnlyMattersForDownload, - downloadIfNecessary, printExceptions)); + list.add(_loadBundle(bc == null ? CFMLEngineFactory.getInstance().getBundleContext() : bc, br, id, addional, startIfNecessary, parents, + versionOnlyMattersForDownload, downloadIfNecessary, printExceptions)); } } @@ -738,7 +744,8 @@ public static Bundle _loadBundle(BundleContext bc, final BundleRange bundleRange if (bc == null) bc = engine.getBundleContext(); Bundle[] bundles = bc.getBundles(); - // Check for circular dependency - if this bundle is already being loaded in the call chain, find and return it if loaded + // Check for circular dependency - if this bundle is already being loaded in the call chain, find + // and return it if loaded if (parents != null && parents.contains(bundleRange.getName())) { log(Log.LEVEL_DEBUG, "Circular dependency detected for bundle [" + bundleRange.getName() + "], looking for existing bundle"); // Try to find the bundle that's already loaded @@ -852,15 +859,13 @@ public static Bundle _loadBundle(BundleContext bc, final BundleRange bundleRange try { localDir = " (" + factory.getBundleDirectory() + ")"; } - catch (IOException e) { - } + catch (IOException e) {} String upLoc = ""; if (!ThreadLocalPageContext.insideServerNewInstance()) { try { upLoc = " (" + factory.getUpdateLocation() + ")"; } - catch (IOException e) { - } + catch (IOException e) {} } else { upLoc = " (" + ConfigFactoryImpl.DEFAULT_LOCATION + ")"; @@ -996,6 +1001,15 @@ public static BundleFile getBundleFile(String name, Version version, Identificat throw new BundleException("The OSGi Bundle with name [" + name + "] is not available locally or from the update provider."); } + public static int compare(final String left, final String right) { + try { + return compare(toVersion(left, false), toVersion(right, false)); + } + catch (BundleException e) { + return left.compareTo(right); + } + } + /** * @return a negative integer, zero, or a positive integer as the first argument is less than, equal * to, or greater than the second. @@ -1359,8 +1373,7 @@ public static List getBundleDefinitions(BundleContext bc) { } } } - catch (IOException ioe) { - } + catch (IOException ioe) {} return list; } @@ -1409,8 +1422,7 @@ public static Bundle loadBundleFromLocal(BundleContext bc, String name, Version try { return _loadBundle(bc, bf); } - catch (Exception e) { - } + catch (Exception e) {} } return defaultValue; @@ -1451,8 +1463,7 @@ public static void removeLocalBundleSilently(String name, Version version, List< try { removeLocalBundle(name, version, addional, removePhysical, true); } - catch (Exception e) { - } + catch (Exception e) {} } // bundle stuff @@ -1519,7 +1530,8 @@ private static Bundle _start(Bundle bundle, Set parents) throws BundleEx List failedPD = new ArrayList(); try { if (!listBundlesPackages.getName().isEmpty()) { - loadBundles(bundle.getBundleContext(), listBundlesPackages.getName(), ThreadLocalPageContext.getConfig().getIdentification(), null, true, false, true, null, parents); + loadBundles(bundle.getBundleContext(), listBundlesPackages.getName(), ThreadLocalPageContext.getConfig().getIdentification(), null, true, false, true, null, + parents); } if (!listBundlesPackages.getValue().isEmpty()) { loadPackages(bundle.getBundleContext(), parents, loadedBundles, listBundlesPackages.getValue(), bundle, failedPD); @@ -2310,8 +2322,7 @@ public static String[] getBootdelegation() { bootDelegation = ListUtil.trimItems(ListUtil.listToStringArray(StringUtil.unwrap(bd), ',')); } } - catch (IOException ioe) { - } + catch (IOException ioe) {} finally { IOUtil.closeEL(is); } @@ -2669,8 +2680,7 @@ public static ClassLoader getEmptyBundleClassLoader(BundleContext bc) throws IOE try { existingBundle.uninstall(); } - catch (BundleException ignored) { - } + catch (BundleException ignored) {} } } @@ -2723,4 +2733,4 @@ private static void cleanupOldVersions(BundleContext bc, String symbolicName, Ve } } } -} +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/tag/Admin.java b/core/src/main/java/lucee/runtime/tag/Admin.java index bd5de5dc115..3368538dfd7 100755 --- a/core/src/main/java/lucee/runtime/tag/Admin.java +++ b/core/src/main/java/lucee/runtime/tag/Admin.java @@ -861,8 +861,7 @@ private boolean check2(short accessRW) throws SecurityException { private boolean check(String action, short access) { if (this.action.equalsIgnoreCase(action)) { - if (access == ACCESS_FREE) { - } + if (access == ACCESS_FREE) {} return true; } return false; @@ -897,11 +896,11 @@ private void doChangeVersionTo() throws PageException { private void doMvnChangeVersionTo() throws PageException { try { - Version version = OSGiUtil.toVersion(getString("admin", "changeVersionTo", "version")); + lucee.runtime.config.maven.Version version = lucee.runtime.config.maven.Version.parseVersion(getString("admin", "changeVersionTo", "version")); admin.mvnChangeVersionTo(version, password, pageContext.getConfig().getIdentification()); adminSync.broadcast(attributes, config); } - catch (BundleException e) { + catch (IOException e) { throw Caster.toPageException(e); } } @@ -1255,8 +1254,7 @@ private void doRemoveAPIKey() throws PageException { try { admin.removeAPIKey(); } - catch (Exception e) { - } + catch (Exception e) {} store(); ConfigUtil.getConfigServerImpl(config).resetIdentification(); } @@ -1269,8 +1267,7 @@ private void doUpdateAuthKey() throws PageException { try { admin.updateAuthKey(getString("key", null)); } - catch (Exception e) { - } + catch (Exception e) {} store(); } @@ -1278,8 +1275,7 @@ private void doRemoveAuthKey() throws PageException { try { admin.removeAuthKeys(getString("key", null)); } - catch (Exception e) { - } + catch (Exception e) {} store(); } @@ -2698,8 +2694,7 @@ private void doGetJars() throws PageException { try { qry.setAt(KeyConstants._info, i + 1, BundleFile.getInstance(children[i]).info()); } - catch (Exception e) { - } + catch (Exception e) {} } } pageContext.setVariable(getString("admin", action, "returnVariable"), qry); @@ -3497,8 +3492,7 @@ private void doGetBundle() throws PageException { headers = bf.getHeaders(); } - catch (BundleException e) { - } + catch (BundleException e) {} } @@ -3598,8 +3592,7 @@ private void doGetBundles() throws PageException { } } - catch (BundleException e) { - } + catch (BundleException e) {} } @@ -3688,8 +3681,7 @@ private void _findExtension(RHExtension[] extensions, BundleDefinition bd, Set s } } } - catch (Exception ex) { - } + catch (Exception ex) {} } } @@ -4307,9 +4299,9 @@ private void doUpdateScope() throws PageException { config.getFormUrlAsStruct(); store(); - ConfigUtil.getConfigServerImpl(config).resetLocalMode().resetCGIScopeReadonly().resetSessionType().resetScopeCascadingType().resetAllowImplicidQueryCall().resetMergeFormAndURL().resetClientStorage() - .resetSessionStorage().resetClientTimeout().resetSessionTimeout().resetApplicationTimeout().resetClientType().resetSessionManagement().resetClientManagement() - .resetClientCookies().resetDomainCookies().resetFormUrlAsStruct();// MUST + ConfigUtil.getConfigServerImpl(config).resetLocalMode().resetCGIScopeReadonly().resetSessionType().resetScopeCascadingType().resetAllowImplicidQueryCall() + .resetMergeFormAndURL().resetClientStorage().resetSessionStorage().resetClientTimeout().resetSessionTimeout().resetApplicationTimeout().resetClientType() + .resetSessionManagement().resetClientManagement().resetClientCookies().resetDomainCookies().resetFormUrlAsStruct();// MUST adminSync.broadcast(attributes, config); } @@ -5549,4 +5541,4 @@ private Object emptyIfNull(String str) { if (str == null) return ""; return str; } -} +} \ No newline at end of file diff --git a/loader/build.xml b/loader/build.xml index 35dca6c8145..3072541674c 100644 --- a/loader/build.xml +++ b/loader/build.xml @@ -2,7 +2,7 @@ - + diff --git a/loader/pom.xml b/loader/pom.xml index 3b408dde10e..b44011447ee 100644 --- a/loader/pom.xml +++ b/loader/pom.xml @@ -3,7 +3,7 @@ org.lucee lucee - 7.0.3.5-SNAPSHOT + 7.0.3.9-SNAPSHOT jar Lucee Loader Build From 80a62499c088fe927cdec9dcfd55a17cef0c5a64 Mon Sep 17 00:00:00 2001 From: CF Mitrah Date: Thu, 12 Mar 2026 21:53:45 +0530 Subject: [PATCH 3/3] Updated the font size for the extension title. --- core/src/main/cfml/context/res/css/admin.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/cfml/context/res/css/admin.css b/core/src/main/cfml/context/res/css/admin.css index 00b60c77641..ecce2f3239d 100644 --- a/core/src/main/cfml/context/res/css/admin.css +++ b/core/src/main/cfml/context/res/css/admin.css @@ -146,7 +146,7 @@ body, td, th { font-family: Arial, Helvetica, sans-serif; - font-size: 14px; + font-size: 12.7px; color: #333; line-height: 1.3; }