diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7577027a32..9883ba2abb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,7 @@ Please read our docs on working with the Lucee source code: https://docs.lucee.o # Branch Status -6.0 is the active development branch, any new cool stuff should be done against this branch +7.0 is the active development branch, any new cool stuff should be done against this branch 5.4 is our LTS branch, mainly only bugfixes diff --git a/core/src/main/cfml/context/admin/ext.functions.cfm b/core/src/main/cfml/context/admin/ext.functions.cfm index b70a9e83ea..1b4416083f 100644 --- a/core/src/main/cfml/context/admin/ext.functions.cfm +++ b/core/src/main/cfml/context/admin/ext.functions.cfm @@ -316,6 +316,7 @@ loop query="#locals#" { var row=queryAddRow(qry); qry.setCell("provider","local",row); + qry.setCell("otherVersions",[],row); loop list="#locals.columnlist()#" item="local.k" { qry.setCell(k,locals[k],row); } diff --git a/core/src/main/cfml/context/admin/resources.mapping.compileError.cfm b/core/src/main/cfml/context/admin/resources.mapping.compileError.cfm new file mode 100644 index 0000000000..7e7a55a403 --- /dev/null +++ b/core/src/main/cfml/context/admin/resources.mapping.compileError.cfm @@ -0,0 +1,26 @@ + +
+

#stText.Mappings.compileErrorsTitle#

+ +

#replace( stText.Mappings.compileErrorsIntro, "{context}", errorContext )#

+ +

#stText.Mappings.compileErrorsDesc#

+
+ + +
+
+ + #errorFile#:#errorInfo.line#,#errorInfo.column# + +
+ +
#HTMLEditFormat( errorInfo.message ?: "" )#
+ +
#HTMLEditFormat( errorInfo.detail )#
+
+
+
+
+
+
diff --git a/core/src/main/cfml/context/admin/resources.mappings.cfm b/core/src/main/cfml/context/admin/resources.mappings.cfm index 1aaf29e331..665be86d8b 100644 --- a/core/src/main/cfml/context/admin/resources.mappings.cfm +++ b/core/src/main/cfml/context/admin/resources.mappings.cfm @@ -28,7 +28,7 @@ Defaults ---> - + @@ -38,19 +38,20 @@ Defaults ---> - - + + + - + @@ -64,43 +65,46 @@ Defaults ---> - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - file="#target#" - virtual="#data.virtuals[idx]#" - addCFMLFiles="#data.addCFMLFiles[idx]#" - addNonCFMLFiles="#data.addNonCFMLFiles[idx]#" - append="#not doDownload#" - remoteClients="#request.getRemoteClients()#"> - - + + + + + + + + + + + + + + + + @@ -123,22 +127,21 @@ Defaults ---> - + @@ -148,29 +151,28 @@ Defaults ---> - - + +
#cfcatch.stacktrace#
- - + + + + - + + + - - - - - - - - - - - + + + + + + + + diff --git a/core/src/main/cfml/context/res/css/admin.css b/core/src/main/cfml/context/res/css/admin.css index 0cb33aca52..49bec1309c 100644 --- a/core/src/main/cfml/context/res/css/admin.css +++ b/core/src/main/cfml/context/res/css/admin.css @@ -148,6 +148,7 @@ th { font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333; + line-height: 1.3; } /* Typography */ @@ -882,10 +883,11 @@ div.warning, div.warningops, div.message { border: 1px solid red !important; - padding: 5px; + padding: 10px; margin: 10px 0px; color: red !important; border-radius: 5px; + line-height: 1.6; } div.error a, diff --git a/core/src/main/java/default.properties b/core/src/main/java/default.properties index 119ae6710e..f18bdc6336 100755 --- a/core/src/main/java/default.properties +++ b/core/src/main/java/default.properties @@ -31,7 +31,7 @@ org.osgi.framework.bootdelegation= \ jakarta.websocket,jakarta.websocket.*,\ jakarta.xml.*,\ \ - java.nio.charset,java.nio.charset.*,\ + java.*,\ \ javax.accessibility,\ javax.annotation,javax.annotation.processing,\ diff --git a/core/src/main/java/lucee/commons/io/FileUtil.java b/core/src/main/java/lucee/commons/io/FileUtil.java index 6c987f1be3..7c68c715bd 100644 --- a/core/src/main/java/lucee/commons/io/FileUtil.java +++ b/core/src/main/java/lucee/commons/io/FileUtil.java @@ -40,6 +40,8 @@ * Helper methods for file objects */ public final class FileUtil { + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + /** * Field FILE_SEPERATOR */ @@ -163,7 +165,7 @@ public static final String URIToFilename(String str) { */ public static boolean isLocked(Resource res) { - if (!(res instanceof File) || !SystemUtil.isWindows() || !res.exists()) return false; + if (!(res instanceof File) || !IS_WINDOWS || !res.exists()) return false; try { InputStream is = res.getInputStream(); is.close(); 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 fc74dc9940..841f70e69f 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 @@ -64,6 +64,8 @@ public final class FileResource extends File implements Resource { private static final long serialVersionUID = -6856656594615376447L; private static final CopyOption[] COPY_OPTIONS = new CopyOption[] { StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES }; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private static final boolean IS_UNIX = SystemUtil.isUnix(); private final FileResourceProvider provider; @@ -461,7 +463,7 @@ public boolean isSystem() { @Override public int getMode() { if (!exists()) return 0; - if (SystemUtil.isUnix()) { + if (IS_UNIX) { try { PosixFileAttributes attrs = Files.readAttributes(toPath(), PosixFileAttributes.class); Set permissions = attrs.permissions(); @@ -472,7 +474,7 @@ public int getMode() { } } - int mode = SystemUtil.isWindows() && exists() ? 0111 : 0; + int mode = IS_WINDOWS && exists() ? 0111 : 0; if (super.canRead()) mode += 0444; if (super.canWrite()) mode += 0222; return mode; @@ -480,7 +482,7 @@ public int getMode() { public static int getMode(Path path) { if (!Files.exists(path)) return 0; - if (SystemUtil.isUnix()) { + if (IS_UNIX) { try { PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class); Set permissions = attrs.permissions(); @@ -491,7 +493,7 @@ public static int getMode(Path path) { } } - int mode = SystemUtil.isWindows() ? 0111 : 0; + int mode = IS_WINDOWS ? 0111 : 0; if (Files.isReadable(path)) mode += 0444; if (Files.isWritable(path)) mode += 0222; return mode; @@ -500,7 +502,7 @@ public static int getMode(Path path) { @Override public void setMode(int mode) throws IOException { // TODO for windows do it with help of NIO functions - if (!SystemUtil.isUnix()) return; + if (!IS_UNIX) return; mode = ModeUtil.extractPermissions(mode, true); // we only need the permission part try { provider.lock(this); @@ -536,7 +538,7 @@ public void setSystem(boolean value) throws IOException { @Override public boolean setReadable(boolean value) { - if (!SystemUtil.isUnix()) return false; + if (!IS_UNIX) return false; try { setMode(ModeUtil.setReadable(getMode(), value)); return true; @@ -563,7 +565,7 @@ public boolean setWritable(boolean value) { return true; } - if (SystemUtil.isUnix()) { + if (IS_UNIX) { // need no lock because get/setmode has one try { setMode(ModeUtil.setWritable(getMode(), value)); @@ -794,7 +796,7 @@ public boolean setReadOnly() { @Override public boolean getAttribute(short attribute) { - if (!SystemUtil.isWindows()) return false; + if (!IS_WINDOWS) return false; try { provider.lock(this); @@ -822,7 +824,7 @@ else if (attribute == ATTRIBUTE_SYSTEM) { @Override public void setAttribute(short attribute, boolean value) throws IOException { - if (!SystemUtil.isWindows()) return; + if (!IS_WINDOWS) return; provider.lock(this); try { diff --git a/core/src/main/java/lucee/commons/io/res/type/file/FileResourceProvider.java b/core/src/main/java/lucee/commons/io/res/type/file/FileResourceProvider.java index e46101a451..53b8e91e90 100644 --- a/core/src/main/java/lucee/commons/io/res/type/file/FileResourceProvider.java +++ b/core/src/main/java/lucee/commons/io/res/type/file/FileResourceProvider.java @@ -34,6 +34,9 @@ public final class FileResourceProvider implements ResourceProviderPro { + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private static final boolean IS_UNIX = SystemUtil.isUnix(); + private String scheme = "file"; private long lockTimeout = 10000; @@ -95,7 +98,7 @@ public void read(Resource res) throws IOException { @Override public boolean isAttributesSupported() { - return SystemUtil.isWindows(); + return IS_WINDOWS; } @Override @@ -105,7 +108,7 @@ public boolean isCaseSensitive() { @Override public boolean isModeSupported() { - return SystemUtil.isUnix(); + return IS_UNIX; } @Override diff --git a/core/src/main/java/lucee/commons/io/res/type/http/HTTPResource.java b/core/src/main/java/lucee/commons/io/res/type/http/HTTPResource.java index ddfa556bf8..4e59c6b13e 100755 --- a/core/src/main/java/lucee/commons/io/res/type/http/HTTPResource.java +++ b/core/src/main/java/lucee/commons/io/res/type/http/HTTPResource.java @@ -205,7 +205,7 @@ public long lastModified() { if (cl != null && exists()) last = Caster.toIntValue(cl.getValue(), 0); } catch (Exception e) { - LogUtil.warn("ftp", e); + LogUtil.warn("http", e); } finally { HTTPEngine.closeEL(rsp); 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 ebf32a7284..f80e156bc4 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 @@ -452,7 +452,6 @@ public void createFile(boolean createParentWhenNotExists) throws IOException { try { ResourceUtil.checkCreateFileOK(this, createParentWhenNotExists); - // client.unregisterFTPFile(this); IOUtil.copy(new ByteArrayInputStream(new byte[0]), getOutputStream(), true, true); } catch (SmbException e) { 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 3656d35f0c..f5146312ce 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 @@ -75,6 +75,7 @@ public final class ResourceUtil { public static final int MIMETYPE_CHECK_EXTENSION = 1; public static final int MIMETYPE_CHECK_HEADER = 2; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); /** * Field FILE_SEPERATOR @@ -354,7 +355,7 @@ public static Resource toResourceNotExisting(PageContext pc, String destination, Config cw = pc.getConfig(); Resource[] sources = ((ConfigPro) cw).getResources(pci, ExpandPath.mergeMappings(pc.getApplicationContext().getMappings(), pc.getApplicationContext().getComponentMappings()), destination, false, - pci.useSpecialMappings(), SystemUtil.isWindows(), checkComponentMappings, false); + pci.useSpecialMappings(), IS_WINDOWS, checkComponentMappings, false); if (!ArrayUtil.isEmpty(sources)) { for (int i = 0; i < sources.length; i++) { res = sources[i]; @@ -396,11 +397,11 @@ private static Resource getRealResource(PageContext pc, String destination, Reso } public static boolean isUNCPath(String path) { - return SystemUtil.isWindows() && (path.startsWith("//") || path.startsWith("\\\\")); + return IS_WINDOWS && (path.startsWith("//") || path.startsWith("\\\\")); } public static boolean isWindowsPath(String path) { - return SystemUtil.isWindows() && path.length() > 1 && path.charAt(1) == ':'; + return IS_WINDOWS && path.length() > 1 && path.charAt(1) == ':'; } /** diff --git a/core/src/main/java/lucee/commons/io/res/util/WildcardPatternFilter.java b/core/src/main/java/lucee/commons/io/res/util/WildcardPatternFilter.java index ca8d0b9a41..07e79ee067 100644 --- a/core/src/main/java/lucee/commons/io/res/util/WildcardPatternFilter.java +++ b/core/src/main/java/lucee/commons/io/res/util/WildcardPatternFilter.java @@ -23,6 +23,8 @@ public final class WildcardPatternFilter implements ResourceAndResourceNameFilter { + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private final WildcardPattern matcher; public WildcardPatternFilter(String patt, boolean ignoreCase, String patternDelimiters) { @@ -32,7 +34,7 @@ public WildcardPatternFilter(String patt, boolean ignoreCase, String patternDeli public WildcardPatternFilter(String pattern, String patternDelimiters) { - this(pattern, SystemUtil.isWindows(), patternDelimiters); + this(pattern, IS_WINDOWS, patternDelimiters); } @Override diff --git a/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java b/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java index 959b2f559e..b670a37e5b 100644 --- a/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java +++ b/core/src/main/java/lucee/commons/lang/MemoryClassLoader.java @@ -134,7 +134,7 @@ public Class loadClass(String name, byte[] barr) throws UnmodifiableClassExce } private Class rename(Class clazz, byte[] barr) { - String newName = clazz.getName() + "$" + PhysicalClassLoader.uid(); + String newName = clazz.getName() + "$" + PhysicalClassLoaderFactory.uid(); return _loadClass(newName, ClassRenamer.rename(barr, newName)); } diff --git a/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java b/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java index e0a8985fb4..f0c036b73c 100644 --- a/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java +++ b/core/src/main/java/lucee/commons/lang/PhysicalClassLoader.java @@ -18,49 +18,32 @@ */ package lucee.commons.lang; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.instrument.UnmodifiableClassException; import java.net.URL; import java.net.URLClassLoader; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicLong; -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.Log; import lucee.commons.io.log.LogUtil; import lucee.commons.io.res.Resource; -import lucee.commons.io.res.type.file.FileResource; -import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.ClassUtil.ClassLoading; 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.listener.JavaSettings; -import lucee.runtime.listener.JavaSettingsImpl; -import lucee.runtime.listener.SerializationSettings; +import lucee.runtime.op.Caster; import lucee.runtime.osgi.OSGiUtil; -import lucee.runtime.type.Struct; -import lucee.runtime.type.StructImpl; -import lucee.runtime.type.util.KeyConstants; import lucee.transformer.bytecode.util.ASMUtil; import lucee.transformer.bytecode.util.ClassRenamer; +import lucee.transformer.dynamic.DynamicInvoker; +import lucee.transformer.dynamic.meta.dynamic.ClazzDynamic; /** * Directory ClassLoader @@ -71,18 +54,17 @@ public final class PhysicalClassLoader extends URLClassLoader implements Extenda boolean res = registerAsParallelCapable(); } - private static RC rc = new RC(); - - private static Map classLoaders = new ConcurrentHashMap<>(); + private static final double CLASSLOADER_INSPECTION_SIZE = Caster.toIntValue(SystemUtil.getSystemPropOrEnvVar("lucee.template.classloader.inspection.size", null), 2000); + private static final double CLASSLOADER_INSPECTION_RATIO = Caster.toIntValue(SystemUtil.getSystemPropOrEnvVar("lucee.template.classloader.inspection.ratio", null), 3); private final Resource directory; private ConfigPro config; private final ClassLoader addionalClassLoader; - private final Collection resources; + private final List resources; - private Map loadedClasses = new ConcurrentHashMap(); - private Map allLoadedClasses = new ConcurrentHashMap(); // this includes all renames - private Map unavaiClasses = new ConcurrentHashMap(); + private Map loadedClasses = new ConcurrentHashMap<>(); + private Map allLoadedClasses = new ConcurrentHashMap<>(); // this includes all renames + private Map unavaiClasses = new ConcurrentHashMap<>(); private PageSourcePool pageSourcePool; @@ -92,123 +74,59 @@ public final class PhysicalClassLoader extends URLClassLoader implements Extenda public final String id; - private static final AtomicLong counter = new AtomicLong(0); - private static long _start = 0L; - private static String start = Long.toString(_start, Character.MAX_RADIX); - private static Object countToken = new Object(); - - public static String uid() { - long currentCounter = counter.incrementAndGet(); // Increment and get atomically - if (currentCounter < 0) { - synchronized (countToken) { - currentCounter = counter.incrementAndGet(); - if (currentCounter < 0) { - counter.set(0L); - currentCounter = 0L; - start = Long.toString(++_start, Character.MAX_RADIX); - } - } - } - if (_start == 0L) return Long.toString(currentCounter, Character.MAX_RADIX); - return start + "_" + Long.toString(currentCounter, Character.MAX_RADIX); - } - - public static PhysicalClassLoader getPhysicalClassLoader(Config c, Resource directory, boolean reload) throws IOException { - String key = HashUtil.create64BitHashAsString(directory.getAbsolutePath()); - - PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key); - if (rpccl == null) { - synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) { - rpccl = reload ? null : classLoaders.get(key); - if (rpccl == null) { - // if we have a reload, clear the existing before set a new one - if (reload) { - PhysicalClassLoader existing = classLoaders.get(key); - if (existing != null) existing.clear(); - } - classLoaders.put(key, rpccl = new PhysicalClassLoader(key, c, new ArrayList(), directory, SystemUtil.getCoreClassLoader(), null, null, false)); - } - } - } - return rpccl; - } - - public static PhysicalClassLoader getRPCClassLoader(Config c, BundleClassLoader bcl, boolean reload) throws IOException { - return getRPCClassLoader(c, null, bcl, SystemUtil.getCoreClassLoader(), reload); - // return CombinedClassLoader.getInstance(getRPCClassLoader(c, null, bcl, - // SystemUtil.getLoaderClassLoader(), reload), - // getRPCClassLoader(c, null, bcl, SystemUtil.getCoreClassLoader(), reload), reload); - } - - public static PhysicalClassLoader getRPCClassLoader(Config c, JavaSettings js, boolean reload) throws IOException { - return getRPCClassLoader(c, js, null, SystemUtil.getCoreClassLoader(), reload); - - // return CombinedClassLoader.getInstance(getRPCClassLoader(c, js, null, - // SystemUtil.getLoaderClassLoader(), reload), - // getRPCClassLoader(c, js, null, SystemUtil.getCoreClassLoader(), reload), reload); - } - - private static PhysicalClassLoader getRPCClassLoader(Config c, JavaSettings js, BundleClassLoader bcl, ClassLoader parent, boolean reload) throws IOException { - String key = js == null ? "orphan" : ((JavaSettingsImpl) js).id(); - - if (parent == null) parent = SystemUtil.getCoreClassLoader(); - if (parent instanceof PhysicalClassLoader) { - key += ":" + ((PhysicalClassLoader) parent).id; - } - else { - key += ":" + parent.getClass().getName() + parent.hashCode(); - } + PhysicalClassLoader(String key, Config c, List resources, Resource directory, ClassLoader parentClassLoader, ClassLoader addionalClassLoader, + PageSourcePool pageSourcePool, boolean rpc) throws IOException { + this(key, c, PhysicalClassLoaderFactory.doURLs(resources), resources, directory, + parentClassLoader == null ? (parentClassLoader = SystemUtil.getCoreClassLoader()) : parentClassLoader, addionalClassLoader, pageSourcePool, rpc); - if (bcl != null) { - key += ":" + bcl; - } - key = HashUtil.create64BitHashAsString(key); - - PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key); - if (rpccl == null) { - synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) { - rpccl = reload ? null : classLoaders.get(key); - if (rpccl == null) { - // if we have a reload, clear the existing before set a new one - if (reload) { - PhysicalClassLoader existing = classLoaders.get(key); - if (existing != null) existing.clear(); - } - List resources; - if (js == null) { - resources = new ArrayList(); - } - else { - resources = toSortedList(((JavaSettingsImpl) js).getAllResources()); - } - Resource dir = storeResourceMeta(c, key, js, resources); - // (Config config, String key, JavaSettings js, Collection _resources) - classLoaders.put(key, rpccl = new PhysicalClassLoader(key, c, resources, dir, parent, bcl, null, true)); - } - } - } - return rpccl; + // 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"); } - private PhysicalClassLoader(String key, Config c, List resources, Resource directory, ClassLoader parentClassLoader, ClassLoader addionalClassLoader, - PageSourcePool pageSourcePool, boolean rpc) throws IOException { - super(doURLs(resources), parentClassLoader == null ? (parentClassLoader = SystemUtil.getCoreClassLoader()) : parentClassLoader); + private PhysicalClassLoader(String key, Config c, URL[] urls, List resources, Resource directory, ClassLoader parentClassLoader, ClassLoader addionalClassLoader, + PageSourcePool pageSourcePool, boolean rpc) { + super(urls, parentClassLoader); this.resources = resources; - config = (ConfigPro) c; this.addionalClassLoader = addionalClassLoader; this.birthplace = ExceptionUtil.getStacktrace(new Throwable(), false); this.pageSourcePool = pageSourcePool; - // 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; id = key; } + public static PhysicalClassLoader flush(PhysicalClassLoader existing, Config config) { + if (existing.pageSourcePool != null) existing.pageSourcePool.clearPages(existing); + PhysicalClassLoader clone = new PhysicalClassLoader(existing.id, config, existing.getURLs(), existing.resources, existing.directory, existing.getParent(), + existing.addionalClassLoader, null, existing.rpc); + DynamicInvoker instance = DynamicInvoker.getExistingInstance(); + int count = 0; + if (instance != null) count += instance.remove(existing); + count += ClazzDynamic.remove(existing); + int all = existing.allLoadedClasses.size(); + int unique = existing.loadedClasses.size(); + LogUtil.log(Log.LEVEL_INFO, "physical-classloader", "flush physical classloader [" + existing.getDirectory() + "] because we reached the size limit (all loaded classes: " + + all + "; unique loaded classes: " + unique + "; ratio: " + (all / unique) + "), removed " + count + " cache elements from dynamic invoker"); + + return clone; + } + + public static PhysicalClassLoader flushIfNecessary(PhysicalClassLoader existing, Config config) { + double all; + + // check size + if ((all = existing.allLoadedClasses.size()) > CLASSLOADER_INSPECTION_SIZE) { + if ((all / existing.loadedClasses.size()) > CLASSLOADER_INSPECTION_RATIO) { + return flush(existing, config); + } + } + return null; + } + public String getBirthplace() { return birthplace; } @@ -423,7 +341,7 @@ private byte[] read(String name, byte[] defaultValue) { } private Class rename(Class clazz, byte[] barr) { - String newName = clazz.getName() + "$" + uid(); + String newName = clazz.getName() + "$" + PhysicalClassLoaderFactory.uid(); return _loadClass(newName, ClassRenamer.rename(barr, newName), true); } @@ -432,8 +350,8 @@ private Class _loadClass(String name, byte[] barr, boolean rename) { Class clazz = defineClass(name, barr, 0, barr.length); if (clazz != null) { - if (!rename) loadedClasses.put(name, barr); - allLoadedClasses.put(name, barr); + if (!rename) loadedClasses.put(name, name); + allLoadedClasses.put(name, name); resolveClass(clazz); } @@ -471,32 +389,33 @@ public InputStream getResourceAsStream(String name) { InputStream is = super.getResourceAsStream(name); if (is != null) return is; - if (name.endsWith(".class")) { - // MUST store the barr in a less memory intensive way - String className = name.substring(0, name.length() - 6).replace('/', '.').replace('\\', '.'); - byte[] barr = allLoadedClasses.get(className); - if (barr != null) return new ByteArrayInputStream(barr); - } + /* + * if (name.endsWith(".class")) { // MUST store the barr in a less memory intensive way String + * className = name.substring(0, name.length() - 6).replace('/', '.').replace('\\', '.'); byte[] + * barr = allLoadedClasses.get(className); if (barr != null) return new ByteArrayInputStream(barr); + * } + */ - URL url = super.getResource(name); - if (url != null) { + Resource f = _getResource(name); + if (f != null) { try { - return IOUtil.toBufferedInputStream(url.openStream()); + return IOUtil.toBufferedInputStream(f.getInputStream()); } catch (IOException e) { LogUtil.trace("physical-classloader", e); } } - Resource f = _getResource(name); - if (f != null) { + URL url = super.getResource(name); + if (url != null) { try { - return IOUtil.toBufferedInputStream(f.getInputStream()); + return IOUtil.toBufferedInputStream(url.openStream()); } catch (IOException e) { LogUtil.trace("physical-classloader", e); } } + return null; } @@ -532,55 +451,17 @@ public Resource getDirectory() { return directory; } - public void clear() { + private void clear() { clear(true); } - public void clear(boolean clearPagePool) { + private void clear(boolean clearPagePool) { if (clearPagePool && pageSourcePool != null) pageSourcePool.clearPages(this); this.loadedClasses.clear(); this.allLoadedClasses.clear(); 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] - * - * @param name - * @return - * @throws ApplicationException - */ - public static String substractAppendix(String name) throws ApplicationException { - if (name.endsWith("$cf")) return name; - int index = name.lastIndexOf('$'); - if (index != -1) { - name = name.substring(0, index); - } - if (name.endsWith("$cf")) return name; - throw new ApplicationException("could not remove appendix from [" + name + "]"); - } - @Override public void finalize() throws Throwable { try { @@ -592,49 +473,4 @@ 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 ("jar".equalsIgnoreCase(ResourceUtil.getExtension(r, null)) || r.isDirectory()) 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/lang/PhysicalClassLoaderFactory.java b/core/src/main/java/lucee/commons/lang/PhysicalClassLoaderFactory.java new file mode 100644 index 0000000000..a7a7cfda63 --- /dev/null +++ b/core/src/main/java/lucee/commons/lang/PhysicalClassLoaderFactory.java @@ -0,0 +1,225 @@ +package lucee.commons.lang; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicLong; + +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.res.Resource; +import lucee.commons.io.res.type.file.FileResource; +import lucee.commons.io.res.util.ResourceUtil; +import lucee.runtime.config.Config; +import lucee.runtime.converter.ConverterException; +import lucee.runtime.converter.JSONConverter; +import lucee.runtime.converter.JSONDateFormat; +import lucee.runtime.exp.ApplicationException; +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; + +public class PhysicalClassLoaderFactory { + private static final AtomicLong counter = new AtomicLong(0); + private static long _start = 0L; + private static String start = Long.toString(_start, Character.MAX_RADIX); + private static Object countToken = new Object(); + + private static RC rc = new RC(); + + private static Map classLoaders = new ConcurrentHashMap<>(); + + static String uid() { + long currentCounter = counter.incrementAndGet(); // Increment and get atomically + if (currentCounter < 0) { + synchronized (countToken) { + currentCounter = counter.incrementAndGet(); + if (currentCounter < 0) { + counter.set(0L); + currentCounter = 0L; + start = Long.toString(++_start, Character.MAX_RADIX); + } + } + } + if (_start == 0L) return Long.toString(currentCounter, Character.MAX_RADIX); + return start + "_" + Long.toString(currentCounter, Character.MAX_RADIX); + } + + static URL[] doURLs(Collection reses) throws IOException { + List list = new ArrayList(); + for (Resource r: reses) { + if ("jar".equalsIgnoreCase(ResourceUtil.getExtension(r, null)) || r.isDirectory()) list.add(doURL(r)); + } + return list.toArray(new URL[list.size()]); + } + + static URL doURL(Resource res) throws IOException { + if (!(res instanceof FileResource)) { + return ResourceUtil.toFile(res).toURL(); + } + return ((FileResource) res).toURL(); + } + + public static PhysicalClassLoader getPhysicalClassLoader(Config c, Resource directory, boolean reload) throws IOException { + String key = HashUtil.create64BitHashAsString(directory.getAbsolutePath()); + + PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key); + if (rpccl == null) { + synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) { + rpccl = reload ? null : classLoaders.get(key); + if (rpccl == null) { + // if we have a reload, clear the existing before set a new one + if (reload) { + PhysicalClassLoader existing = classLoaders.get(key); + if (existing != null) PhysicalClassLoader.flush(existing, c); + } + classLoaders.put(key, rpccl = new PhysicalClassLoader(key, c, new ArrayList(), directory, SystemUtil.getCoreClassLoader(), null, null, false)); + return rpccl; + } + } + } + + // at this point we know we had an existing one + PhysicalClassLoader flushed = PhysicalClassLoader.flushIfNecessary(rpccl, c); + if (flushed != null) { + classLoaders.put(key, rpccl = flushed); + } + return rpccl; + } + + public static PhysicalClassLoader getRPCClassLoader(Config c, BundleClassLoader bcl, boolean reload) throws IOException { + return getRPCClassLoader(c, null, bcl, SystemUtil.getCoreClassLoader(), reload); + } + + public static PhysicalClassLoader getRPCClassLoader(Config c, JavaSettings js, boolean reload) throws IOException { + return getRPCClassLoader(c, js, null, SystemUtil.getCoreClassLoader(), reload); + } + + private static PhysicalClassLoader getRPCClassLoader(Config c, JavaSettings js, BundleClassLoader bcl, ClassLoader parent, boolean reload) throws IOException { + String key = js == null ? "orphan" : ((JavaSettingsImpl) js).id(); + + if (parent == null) parent = SystemUtil.getCoreClassLoader(); + if (parent instanceof PhysicalClassLoader) { + key += ":" + ((PhysicalClassLoader) parent).id; + } + else { + key += ":" + parent.getClass().getName() + parent.hashCode(); + } + + if (bcl != null) { + key += ":" + bcl; + } + key = HashUtil.create64BitHashAsString(key); + + PhysicalClassLoader rpccl = reload ? null : classLoaders.get(key); + if (rpccl == null) { + synchronized (SystemUtil.createToken("PhysicalClassLoader", key)) { + rpccl = reload ? null : classLoaders.get(key); + if (rpccl == null) { + // if we have a reload, clear the existing before set a new one + if (reload) { + PhysicalClassLoader existing = classLoaders.get(key); + if (existing != null) PhysicalClassLoader.flush(existing, c); + } + List resources; + if (js == null) { + resources = new ArrayList(); + } + else { + resources = toSortedList(((JavaSettingsImpl) js).getAllResources()); + } + Resource dir = storeResourceMeta(c, key, js, resources); + classLoaders.put(key, rpccl = new PhysicalClassLoader(key, c, resources, dir, parent, bcl, null, true)); + return rpccl; + } + } + } + + // at this point we know we had an existing one + PhysicalClassLoader flushed = PhysicalClassLoader.flushIfNecessary(rpccl, c); + if (flushed != null) { + classLoaders.put(key, rpccl = flushed); + } + return rpccl; + } + + 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] + * + * @param name + * @return + * @throws ApplicationException + */ + public static String substractAppendix(String name) throws ApplicationException { + if (name.endsWith("$cf")) return name; + int index = name.lastIndexOf('$'); + if (index != -1) { + name = name.substring(0, index); + } + if (name.endsWith("$cf")) return name; + throw new ApplicationException("could not remove appendix from [" + name + "]"); + } + + 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; + } + + 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 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/net/http/httpclient/HTTPEngine4Impl.java b/core/src/main/java/lucee/commons/net/http/httpclient/HTTPEngine4Impl.java index eb5c421e2f..b57a532bef 100644 --- a/core/src/main/java/lucee/commons/net/http/httpclient/HTTPEngine4Impl.java +++ b/core/src/main/java/lucee/commons/net/http/httpclient/HTTPEngine4Impl.java @@ -307,13 +307,25 @@ public static HttpClientBuilder getHttpClientBuilder(boolean pooling, String cli } public static void setTimeout(HttpClientBuilder builder, TimeSpan timeout) { - if (timeout == null || timeout.getMillis() <= 0) return; - - int ms = (int) timeout.getMillis(); - if (ms < 0) ms = Integer.MAX_VALUE; + int ms = -1; + if (timeout != null && timeout.getMillis() > 0) { + ms = (int) timeout.getMillis(); + // if overflow occurred (value was > Integer.MAX_VALUE), use 0 which means infinite timeout in HttpClient + if (ms < 0) ms = 0; + + SocketConfig sc = SocketConfig.custom().setSoTimeout(ms).build(); + builder.setDefaultSocketConfig(sc); + } - SocketConfig sc = SocketConfig.custom().setSoTimeout(ms).build(); - builder.setDefaultSocketConfig(sc); + // Set RequestConfig with timeout values (if provided) and cookie spec + // This ensures the timeout is enforced during the actual data transfer, not just connection + RequestConfig.Builder rcBuilder = RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD); // LDEV-2321 + if (ms > 0) { + rcBuilder.setSocketTimeout(ms) + .setConnectTimeout(ms) + .setConnectionRequestTimeout(ms); + } + builder.setDefaultRequestConfig(rcBuilder.build()); } private static Registry createRegistry() throws GeneralSecurityException { @@ -382,15 +394,13 @@ private static HTTPResponse invoke(URL url, HttpUriRequest request, String usern HttpClientBuilder builder = getHttpClientBuilder(pooling, null, null, String.valueOf(redirect)); - // LDEV-2321 - builder.setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()); - HttpHost hh = new HttpHost(url.getHost(), url.getPort()); setHeader(request, headers); if (CollectionUtil.isEmpty(formfields)) setContentType(request, charset); setFormFields(request, formfields, charset); setUserAgent(request, useragent); - if (timeout > 0) Http.setTimeout(builder, TimeSpanImpl.fromMillis(timeout)); + // Always call setTimeout to ensure RequestConfig is set properly (includes LDEV-2321 cookie spec fix) + Http.setTimeout(builder, timeout > 0 ? TimeSpanImpl.fromMillis(timeout) : null); HttpContext context = setCredentials(builder, hh, username, password, false); setProxy(url.getHost(), builder, request, proxy); client = builder.build(); diff --git a/core/src/main/java/lucee/runtime/ComponentImpl.java b/core/src/main/java/lucee/runtime/ComponentImpl.java index f5fe2332e6..4b560480c6 100755 --- a/core/src/main/java/lucee/runtime/ComponentImpl.java +++ b/core/src/main/java/lucee/runtime/ComponentImpl.java @@ -53,6 +53,7 @@ import lucee.runtime.component.AbstractFinal; import lucee.runtime.component.AbstractFinal.UDFB; import lucee.runtime.component.ComponentLoader; +import lucee.runtime.component.ComponentPageRef; import lucee.runtime.component.DataMember; import lucee.runtime.component.ImportDefintion; import lucee.runtime.component.Member; @@ -144,7 +145,7 @@ public final class ComponentImpl extends StructSupport implements Externalizable ComponentImpl top = this; ComponentImpl base; private PageSource pageSource; - private ComponentPageImpl cp; + private ComponentPageRef cpRef; private ComponentScope scope; // for all the same @@ -207,7 +208,7 @@ public ComponentImpl(ComponentPageImpl componentPage, Boolean output, boolean _s this.properties = new ComponentProperties(componentPage.getComponentName(), dspName, extend.trim(), implement, hint, output, callPath + appendix, realPath, componentPage.getSubname(), _synchronized, null, persistent, accessors, modifier, meta); - this.cp = componentPage; + this.cpRef = new ComponentPageRef(componentPage); this.pageSource = componentPage.getPageSource(); this.importDefintions = componentPage.getImportDefintions(); // if(modifier!=0) @@ -217,11 +218,17 @@ public ComponentImpl(ComponentPageImpl componentPage, Boolean output, boolean _s } public JavaSettings getJavaSettings(PageContext pc) throws IOException { - - boolean is = this.cp.isJavaSettingsInitialized(); + ComponentPageImpl cp; + try { + cp = this.cpRef.get(pc); + } + catch (PageException e) { + throw ExceptionUtil.toIOException(e); + } + boolean is = cp.isJavaSettingsInitialized(); if (!is) { synchronized (cp) { - is = this.cp.isJavaSettingsInitialized(); + is = cp.isJavaSettingsInitialized(); if (!is) { JavaSettings js = null; @@ -259,11 +266,16 @@ public JavaSettings getJavaSettings(PageContext pc) throws IOException { // current js = JavaSettingsImpl.merge(pc.getConfig(), js, JavaSettingsImpl.readJavaSettings(pc, properties.meta)); - return this.cp.setJavaSettings(js); + return cp.setJavaSettings(js); } } } - return this.cp.getJavaSettings(); + return cp.getJavaSettings(); + } + + @Override + public final int hashCode() { + return java.util.Objects.hash(base, _data, pageSource); } public boolean hasJavaSettings(PageContext pc) { @@ -293,7 +305,7 @@ public ComponentImpl _duplicate(boolean deepCopy, boolean isTop) { try { // attributes trg.pageSource = pageSource; - trg.cp = cp; + trg.cpRef = cpRef; // trg._triggerDataMember=_triggerDataMember; trg.useShadow = useShadow; trg._static = _static; @@ -467,7 +479,7 @@ public void init(PageContext pageContext, ComponentPageImpl componentPage, boole if (base != null) { this.dataMemberDefaultAccess = base.dataMemberDefaultAccess; - this._static = new StaticScope(base._static, this, componentPage, dataMemberDefaultAccess); + this._static = new StaticScope(base._static, this, cpRef, dataMemberDefaultAccess); this.absFin = base.absFin; _data = base._data; _udfs = isRestEnabled ? new LinkedHashMap(base._udfs) : new HashMap(base._udfs); @@ -478,7 +490,7 @@ public void init(PageContext pageContext, ComponentPageImpl componentPage, boole } else { this.dataMemberDefaultAccess = pageContext.getConfig().getComponentDataMemberDefaultAccess(); - this._static = new StaticScope(null, this, componentPage, dataMemberDefaultAccess); + this._static = new StaticScope(null, this, cpRef, dataMemberDefaultAccess); // TODO get per CFC setting // this._triggerDataMember=pageContext.getConfig().getTriggerComponentDataMember(); _udfs = isRestEnabled ? new LinkedHashMap() : new HashMap(); @@ -493,7 +505,7 @@ public void init(PageContext pageContext, ComponentPageImpl componentPage, boole long indexBase = 0; if (base != null) { - indexBase = base.cp.getStaticStruct().index(); + indexBase = base.cpRef.get(pageContext).getStaticStruct().index(); } // scope @@ -969,21 +981,21 @@ private Collection.Key[] keysPreservingOrder(int access) { if (_udfs.isEmpty() && _data.isEmpty()) { return new Collection.Key[0]; } - + List orderedKeys = new ArrayList(_udfs.size() + _data.size()); - - for (Entry entry : _udfs.entrySet()) { + + for (Entry entry: _udfs.entrySet()) { if (entry.getValue().getAccess() <= access) { orderedKeys.add(entry.getKey()); } } - for (Entry entry : _data.entrySet()) { + for (Entry entry: _data.entrySet()) { Member member = entry.getValue(); if (member.getAccess() <= access && !(member instanceof UDF)) { orderedKeys.add(entry.getKey()); } } - + return orderedKeys.toArray(new Collection.Key[orderedKeys.size()]); } @@ -1023,7 +1035,7 @@ public void clear() { public Member getMember(int access, Collection.Key key, boolean dataMember, boolean superAccess) { // check super if (dataMember && access == ACCESS_PRIVATE && key.equalsIgnoreCase(KeyConstants._super)) { - Component ac = ComponentUtil.getActiveComponent(ThreadLocalPageContext.get(), this); + Component ac = ComponentUtil.getCurrentComponent(ThreadLocalPageContext.get(), this); return SuperComponent.superMember((ComponentImpl) ac.getBaseComponent()); // return SuperComponent . superMember(base); } @@ -1058,7 +1070,7 @@ public Member getMember(int access, Collection.Key key, boolean dataMember, bool protected Member getMember(PageContext pc, Collection.Key key, boolean dataMember, boolean superAccess) { // check super if (dataMember && key.equalsIgnoreCase(KeyConstants._super) && isPrivate(pc)) { - Component ac = ComponentUtil.getActiveComponent(pc, this); + Component ac = ComponentUtil.getCurrentComponent(pc, this); return SuperComponent.superMember((ComponentImpl) ac.getBaseComponent()); } if (superAccess) { @@ -1372,8 +1384,8 @@ public PageSource _getPageSource() { return pageSource; } - public ComponentPageImpl _getComponentPageImpl() { - return cp; + public ComponentPageImpl _getComponentPageImpl(PageContext pc) throws PageException { + return cpRef.get(pc); } public ImportDefintion[] _getImportDefintions() { @@ -1708,7 +1720,7 @@ protected static Struct getMetaData(int access, PageContext pc, ComponentImpl co if (!StringUtil.isEmpty(displayname)) sct.set(KeyConstants._displayname, displayname); sct.set(KeyConstants._persistent, comp.properties.persistent); - sct.set(KeyConstants._hashCode, comp.hashCode()); + // sct.set(KeyConstants._hashCode, comp.hashCode()); sct.set(KeyConstants._accessors, comp.properties.accessors); sct.set(KeyConstants._synchronized, comp.properties._synchronized); sct.set(KeyConstants._inline, comp.properties.inline); @@ -1968,9 +1980,14 @@ private Object _set(PageContext pc, Collection.Key key, Object value, int access "enable [trigger data member] in administrator to also invoke getters and setters"); if (existing != null) { if (existing.getModifier() == Member.MODIFIER_FINAL) { - throw new ExpressionException("Attempt to modify a 'final' member [" + key + "] within the 'this' scope of the component [" + cp.getComponentName() - + "]. This member is declared as 'final' in the base component [" + base.cp.getComponentName() - + "] or a component extended by it, and cannot be overridden."); + ComponentPageImpl tmp = cpRef.get(pc, null); + String componentName = tmp != null ? tmp.getComponentName() : ""; + + tmp = base.cpRef.get(pc, null); + String baseComponentName = tmp != null ? tmp.getComponentName() : ""; + + throw new ExpressionException("Attempt to modify a 'final' member [" + key + "] within the 'this' scope of the component [" + componentName + + "]. This member is declared as 'final' in the base component [" + baseComponentName + "] or a component extended by it, and cannot be overridden."); } } @@ -2379,16 +2396,74 @@ public boolean isAccessors() { @Override public void setProperty(Property property) throws PageException { - top.properties.properties.put(StringUtil.toLowerCase(property.getName()), property); - // FUTURE getDefaultAsObject was added in Beta pahse of Lucee 7, so we keep the checkcast in place - if (((PropertyImpl) property).getDefaultAsObject() != null) scope.setEL(KeyImpl.init(property.getName()), ((PropertyImpl) property).getDefaultAsObject()); - if (top.properties.persistent || top.properties.accessors) { - PropertyFactory.createPropertyUDFs(this, property); + // LDEV-3335: Handle property inheritance and overrides + PropertyImpl propImpl = (PropertyImpl) property; + PageSource propOwnerPS = propImpl.getOwnerPageSource(); + + // Check if this property is overriding an existing property (same name already registered) + String propNameLower = StringUtil.toLowerCase(propImpl.getName()); + PropertyImpl existing = (PropertyImpl) top.properties.properties.get(propNameLower); + boolean isOverride = existing != null && propOwnerPS == null; + + boolean isInherited = propOwnerPS != null && !propOwnerPS.equals(getPageSource()); + + if (isInherited && !isOverride) { + // Property is from a parent component - duplicate it to avoid sharing/mutation + propImpl = (PropertyImpl) propImpl.duplicate(false); + } + else if (propOwnerPS == null) { + // LDEV-3335: Property doesn't have owner set yet - set it to this component + // This happens for properties from __staticProperties that haven't been initialized + propImpl.setOwnerName(getAbsName(), getPageSource()); + } + + top.properties.properties.put(propNameLower, propImpl); + if (propImpl.getDefaultAsObject() != null) { + scope.setEL(propImpl.getNameAsKey(), propImpl.getDefaultAsObject()); + } + // Create accessor UDFs if: + // 1. Component has accessors enabled, OR + // 2. Component is persistent, OR + // 3. Property is inherited and has accessors (need to create new UDFs with duplicated property) + // 4. Property is an override with accessors (child re-declaring parent property) + if (top.properties.persistent || top.properties.accessors || (isInherited && (propImpl.getGetter() || propImpl.getSetter())) + || (isOverride && (propImpl.getGetter() || propImpl.getSetter()))) { + PropertyFactory.createPropertyUDFs(this, propImpl); } } private void initProperties() throws PageException { top.properties.properties = new LinkedHashMap(); + // Call generated stub to initialize properties from static registry (zero overhead!) + if (top.cpRef != null) { + ComponentPageImpl cp = top.cpRef.get(null, null); + if (cp != null) { + cp.initPropertiesStub(this); + } + } + + // LDEV-3335: Add static flyweight accessor UDFs to _data and scope + Map staticAccessorUDFs = null; + if (top.cpRef != null) { + ComponentPageImpl cp = top.cpRef.get(null, null); + if (cp != null) { + staticAccessorUDFs = cp.getStaticAccessorUDFs(); + } + } + if (staticAccessorUDFs != null && !staticAccessorUDFs.isEmpty()) { + Iterator> it = staticAccessorUDFs.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = it.next(); + Key key = entry.getKey(); + UDF udf = entry.getValue(); + + // Only add if not manually overridden + if (!_data.containsKey(key)) { + _data.put(key, udf); + scope.put(key, udf); + } + } + } // MappedSuperClass if (isPersistent() && !isBasePeristent() && top.base != null && top.base.properties.properties != null && top.base.properties.meta != null) { @@ -2399,8 +2474,11 @@ private void initProperties() throws PageException { while (it.hasNext()) { p = it.next().getValue(); if (p.isPeristent()) { - - setProperty(p); + // LDEV-87: Don't override properties that child component has already declared + String propNameLower = StringUtil.toLowerCase(p.getName()); + if (!top.properties.properties.containsKey(propNameLower)) { + setProperty(p); + } } } } diff --git a/core/src/main/java/lucee/runtime/ComponentPageImpl.java b/core/src/main/java/lucee/runtime/ComponentPageImpl.java index e42e965d76..b29704371f 100755 --- a/core/src/main/java/lucee/runtime/ComponentPageImpl.java +++ b/core/src/main/java/lucee/runtime/ComponentPageImpl.java @@ -24,14 +24,19 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lucee.commons.io.CharsetUtil; import lucee.commons.io.IOUtil; +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.CFTypes; @@ -101,6 +106,10 @@ public abstract class ComponentPageImpl extends ComponentPage { public static final lucee.runtime.type.Collection.Key REMOTE_PERSISTENT_ID = KeyConstants._Id16hohohh; + // Note: Static property registry is now generated per-class in bytecode, not in base class + // Each generated component class has its own __staticProperties field and __getStaticProperties() method + // See PageImpl.writeOutStatic() for bytecode generation + private long lastCheck = -1; private StaticScope staticScope; @@ -430,7 +439,8 @@ private void callRest(PageContext pc, Component component, String path, Result r msg = prefix; } RestUtil.setStatus(pc, 404, msg, true); - ThreadLocalPageContext.getLog(pc, "rest").info("REST", prefix + " in" + addDetail); + Log log = ThreadLocalPageContext.getLog(pc, "rest"); + if (LogUtil.doesInfo(log)) log.info("REST", prefix + " in" + addDetail); } else if (status == 405) { String prefix = "Unsupported Media Type"; @@ -441,7 +451,8 @@ else if (status == 405) { msg = prefix; } RestUtil.setStatus(pc, 405, msg, true); - ThreadLocalPageContext.getLog(pc, "rest").info("REST", prefix + " for" + addDetail); + Log log = ThreadLocalPageContext.getLog(pc, "rest"); + if (LogUtil.doesInfo(log)) log.info("REST", prefix + " for" + addDetail); } else if (status == 406) { String prefix = "Not Acceptable"; @@ -452,7 +463,8 @@ else if (status == 406) { msg = prefix; } RestUtil.setStatus(pc, 406, msg, true); - ThreadLocalPageContext.getLog(pc, "rest").info("REST", prefix + " for" + addDetail); + Log log = ThreadLocalPageContext.getLog(pc, "rest"); + if (LogUtil.doesInfo(log)) log.info("REST", prefix + " for" + addDetail); } } @@ -1143,6 +1155,40 @@ public StaticStruct getStaticStruct() { return new StaticStruct(); } + /** + * Returns the static properties map for this component class. + * Components with properties will override this method to return their static property registry. + * Default implementation returns null for components without properties. + * + * @return Map of property names to PropertyImpl instances, or null if no properties + */ + public Map getStaticProperties() { + return null; + } + + /** + * LDEV-3335: Returns the static flyweight accessor UDF map for this component class. + * Components with accessors will override this to return their static UDF registry. + * Default implementation returns null for components without accessor UDFs. + * + * @return Map of accessor names to UDF instances, or null if no accessor UDFs + */ + public Map getStaticAccessorUDFs() { + return null; + } + + /** + * Initializes component properties from the static property registry. + * Components with properties will override this method to provide optimized property initialization. + * Default implementation does nothing (no-op for components without properties). + * + * @param impl The ComponentImpl instance to initialize properties for + * @throws PageException if property initialization fails + */ + public void initPropertiesStub(ComponentImpl impl) throws PageException { + // No-op for components without properties + } + public abstract void initComponent(PageContext pc, ComponentImpl c, boolean executeDefaultConstructor) throws PageException; public void ckecked() { diff --git a/core/src/main/java/lucee/runtime/ComponentProperties.java b/core/src/main/java/lucee/runtime/ComponentProperties.java index dff09a46b9..eb34478112 100644 --- a/core/src/main/java/lucee/runtime/ComponentProperties.java +++ b/core/src/main/java/lucee/runtime/ComponentProperties.java @@ -29,22 +29,28 @@ public final class ComponentProperties implements Serializable { private static final Collection.Key WSDL_FILE = KeyConstants._wsdlfile; + + // Reference fields (8 bytes each) - group together to minimize padding final String dspName; final String extend; final String hint; - final Boolean output; final String callPath; - final boolean realPath; - final boolean _synchronized; + final String implement; + final String subName; + final String name; + final Boolean output; Class javaAccessClass; Map properties; Struct meta; - final String implement; + + // int field (4 bytes) + final int modifier; + + // Boolean fields (1 byte each) - group at end to minimize padding + final boolean realPath; + final boolean _synchronized; final boolean persistent; final boolean accessors; - final int modifier; - final String subName; - final String name; public boolean inline; public ComponentProperties(String name, String dspName, String extend, String implement, String hint, Boolean output, String callPath, boolean realPath, String subName, diff --git a/core/src/main/java/lucee/runtime/ComponentScopeShadow.java b/core/src/main/java/lucee/runtime/ComponentScopeShadow.java index 93a9df0567..324c3483fb 100755 --- a/core/src/main/java/lucee/runtime/ComponentScopeShadow.java +++ b/core/src/main/java/lucee/runtime/ComponentScopeShadow.java @@ -139,7 +139,7 @@ public Object get(Key key, Object defaultValue) { @Override public Object get(PageContext pc, Key key, Object defaultValue) { if (key.equalsIgnoreCase(KeyConstants._SUPER)) { - Component ac = ComponentUtil.getActiveComponent(ThreadLocalPageContext.get(pc), component); + Component ac = ComponentUtil.getCurrentComponent(ThreadLocalPageContext.get(pc), component); return SuperComponent.superInstance((ComponentImpl) ac.getBaseComponent()); } if (key.equalsIgnoreCase(KeyConstants._THIS)) return component.top; @@ -396,4 +396,9 @@ public void setBind(boolean bind) { public boolean isBind() { return true; } + + @Override + public final int hashCode() { + return java.util.Objects.hash(component, shadow); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/ComponentScopeThis.java b/core/src/main/java/lucee/runtime/ComponentScopeThis.java index f76e585076..98a4746fc5 100644 --- a/core/src/main/java/lucee/runtime/ComponentScopeThis.java +++ b/core/src/main/java/lucee/runtime/ComponentScopeThis.java @@ -310,4 +310,9 @@ public void setBind(boolean bind) { public boolean isBind() { return true; } + + @Override + public final int hashCode() { + return java.util.Objects.hash(component); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/ComponentSpecificAccess.java b/core/src/main/java/lucee/runtime/ComponentSpecificAccess.java index 64c347c5cb..46168193ce 100755 --- a/core/src/main/java/lucee/runtime/ComponentSpecificAccess.java +++ b/core/src/main/java/lucee/runtime/ComponentSpecificAccess.java @@ -545,4 +545,9 @@ public int getType() { } return Struct.TYPE_REGULAR; } + + @Override + public int hashCode() { + return java.util.Objects.hash(component); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/MappingImpl.java b/core/src/main/java/lucee/runtime/MappingImpl.java index 7d54a21706..58e5553543 100755 --- a/core/src/main/java/lucee/runtime/MappingImpl.java +++ b/core/src/main/java/lucee/runtime/MappingImpl.java @@ -42,6 +42,7 @@ import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.MappingUtil; import lucee.commons.lang.PhysicalClassLoader; +import lucee.commons.lang.PhysicalClassLoaderFactory; import lucee.commons.lang.StringUtil; import lucee.loader.engine.CFMLEngine; import lucee.runtime.config.Config; @@ -74,7 +75,6 @@ public final class MappingImpl implements Mapping { private final int inspectTemplateAutoIntervalFast; private boolean physicalFirst; - // private transient Map loaders = new HashMap<>(); private Resource archive; private final Config config; @@ -262,21 +262,14 @@ public Class loadClass(String className) { } private Class loadClass(String className, byte[] code) throws IOException, ClassNotFoundException { - 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)); } } - */ + PhysicalClassLoader pcl = PhysicalClassLoaderFactory.getPhysicalClassLoader(config, getClassRootDirectory(), false); if (code != null) { try { return pcl.loadClass(className, code); } catch (UnmodifiableClassException e) { - pcl = PhysicalClassLoader.getPhysicalClassLoader(config, getClassRootDirectory(), true); + pcl = PhysicalClassLoaderFactory.getPhysicalClassLoader(config, getClassRootDirectory(), true); try { return pcl.loadClass(className, code); } @@ -293,10 +286,7 @@ 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); } } - */ + } @Override @@ -382,9 +372,11 @@ public boolean hasPhysical() { @Override public Resource getClassRootDirectory() { if (classRootDirectory == null) { - String path = getPhysical() != null ? getPhysical().getAbsolutePath() : getArchive().getAbsolutePath(); - - classRootDirectory = config.getClassDirectory().getRealResource(StringUtil.toIdentityVariableName(path)); + Resource tmp = getPhysical(); + if (tmp == null) tmp = getArchive(); + if (tmp != null) { + classRootDirectory = config.getClassDirectory().getRealResource(StringUtil.toIdentityVariableName(tmp.getAbsolutePath())); + } } return classRootDirectory; } @@ -744,20 +736,4 @@ public static CIPage loadCIPage(PageSource ps, String className) { } } - private static class PhysicalClassLoaderReference extends SoftReference { - - private long lastModified; - - public PhysicalClassLoaderReference(PhysicalClassLoader pcl) { - super(pcl); - this.lastModified = System.currentTimeMillis(); - } - - @Override - public PhysicalClassLoader get() { - this.lastModified = System.currentTimeMillis(); - return super.get(); - } - } - } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/PageContextImpl.java b/core/src/main/java/lucee/runtime/PageContextImpl.java index c4fe499183..2e25322251 100644 --- a/core/src/main/java/lucee/runtime/PageContextImpl.java +++ b/core/src/main/java/lucee/runtime/PageContextImpl.java @@ -1718,7 +1718,12 @@ public Session sessionScope(boolean checkExpires) throws PageException { public boolean hasCFSession() { if (session != null) return true; if (!getApplicationContext().hasName() || !getApplicationContext().isSetSessionManagement()) return false; - return scopeContext.hasExistingSessionScope(this); + Object tmp = scopeContext.getExistingSessionScope(this); + if (tmp instanceof Session) { + session = (Session) tmp; + return true; + } + return false; } public void invalidateUserScopes(boolean migrateSessionData, boolean migrateClientData) throws PageException { @@ -3600,25 +3605,36 @@ public boolean initApplicationContext(ApplicationListener listener) throws PageE } // Session - initSession = getApplicationContext().isSetSessionManagement() && listener.hasOnSessionStart(this) && !scopeContext.hasExistingSessionScope(this); + initSession = getApplicationContext().isSetSessionManagement() && listener.hasOnSessionStart(this); if (initSession) { - String token = name + ":" + getCFID(); - Lock tokenLock = lock.lock(token, getRequestTimeout()); - try { + Object tmp = scopeContext.getExistingSessionScope(this); + if (tmp != null) { + initSession = false; + if (session == null && tmp instanceof Session) session = (Session) tmp; + } + } + + if (initSession) { + synchronized (SystemUtil.createToken("PageContext", name + ":" + getCFID())) { + // we need to check it again within the lock, to make sure the call is exclusive - initSession = getApplicationContext().isSetSessionManagement() && listener.hasOnSessionStart(this) && !scopeContext.hasExistingSessionScope(this); + initSession = getApplicationContext().isSetSessionManagement() && listener.hasOnSessionStart(this); + + if (initSession) { + Object tmp = scopeContext.getExistingSessionScope(this); + if (tmp != null) { + initSession = false; + if (session == null && tmp instanceof Session) session = (Session) tmp; + } + } // init session if (initSession) { // session must be initlaized here - listener.onSessionStart(this, scopeContext.getSessionScope(this)); + listener.onSessionStart(this, sessionScope()); } } - finally { - // print.o("outer-unlock:"+token); - lock.unlock(tokenLock); - } } return true; } diff --git a/core/src/main/java/lucee/runtime/PageSourceImpl.java b/core/src/main/java/lucee/runtime/PageSourceImpl.java index 12dfb67894..e837ca2f22 100755 --- a/core/src/main/java/lucee/runtime/PageSourceImpl.java +++ b/core/src/main/java/lucee/runtime/PageSourceImpl.java @@ -30,7 +30,6 @@ import lucee.commons.io.log.LogUtil; import lucee.commons.io.res.Resource; import lucee.commons.io.res.util.ResourceUtil; -import lucee.commons.lang.ClassException; import lucee.commons.lang.ClassUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; @@ -42,7 +41,7 @@ import lucee.runtime.compiler.CFMLCompilerImpl.Result; import lucee.runtime.config.Config; import lucee.runtime.config.ConfigPro; -import lucee.runtime.config.ConfigWeb; +import lucee.runtime.config.ConfigUtil; import lucee.runtime.config.ConfigWebPro; import lucee.runtime.config.Constants; import lucee.runtime.engine.ThreadLocalPageContext; @@ -53,7 +52,6 @@ import lucee.runtime.exp.TemplateException; import lucee.runtime.functions.system.GetDirectoryFromPath; import lucee.runtime.op.Caster; -import lucee.runtime.type.dt.DateTimeImpl; import lucee.runtime.type.util.ArrayUtil; import lucee.runtime.type.util.ListUtil; import lucee.transformer.util.PageSourceCode; @@ -76,6 +74,7 @@ public final class PageSourceImpl implements PageSource { private boolean isOutSide; + private String dspPath; private String relPath; private String packageName; private String javaName; @@ -114,38 +113,6 @@ public void set(Page page) { // if (logAccessDirectory != null) dump(); } - private void dump() { - Resource res = getResource(); - if (res != null && res.isFile()) { - try { - File file = createPath(); - IOUtil.write(file, new DateTimeImpl() + " " + res.getAbsolutePath() + "\n", "UTF-8", true); - } - catch (IOException ioe) { - ioe.printStackTrace(); - } - } - } - - private static File createPath() throws IOException { - File log = new File(logAccessDirectory, "access.log"); - - if (log.isFile()) { - if (log.length() > MAX) { - File backup; - int count = 0; - do { - backup = new File(logAccessDirectory, "access-" + (++count) + ".log"); - } - while (backup.isFile()); - log.renameTo(backup); - (log = new File(logAccessDirectory, "access.log")).createNewFile(); - } - } - else log.createNewFile(); - return log; - } - /** * return page when already loaded, otherwise null * @@ -296,11 +263,23 @@ private Page loadArchive(Page page, Page defaultValue) { * @return * @throws PageException */ - private Page loadPhysical(PageContext pc, Page page) throws TemplateException { + private Page loadPhysical(PageContext pcMayNull, Page page) throws TemplateException { if (!mapping.hasPhysical()) return null; - ConfigWeb config = pc.getConfig(); - PageContextImpl pci = (PageContextImpl) pc; - if ((mapping.getInspectTemplate() == Config.INSPECT_NEVER || mapping.getInspectTemplate() == ConfigPro.INSPECT_AUTO || pci.isTrusted(page)) && isLoad(LOAD_PHYSICAL)) + + pcMayNull = ThreadLocalPageContext.get(pcMayNull); + Config config; + PageContextImpl pci = null; + if (pcMayNull != null) { + config = pcMayNull.getConfig(); + pci = (PageContextImpl) pcMayNull; + + } + else { + config = ThreadLocalPageContext.getConfig(); + } + + if ((mapping.getInspectTemplate() == Config.INSPECT_NEVER || mapping.getInspectTemplate() == ConfigPro.INSPECT_AUTO || (pci != null && pci.isTrusted(page))) + && isLoad(LOAD_PHYSICAL)) return page; Resource srcFile = getPhyscalFile(); @@ -324,15 +303,15 @@ private Page loadPhysical(PageContext pc, Page page) throws TemplateException { } if (!same) { - LogUtil.log(pc, Log.LEVEL_DEBUG, "compile", "recompile [" + getDisplayPath() + "] because loaded page has changed"); - pcn.set(page = compile(config, mapping.getClassRootDirectory(), page, false, pc.ignoreScopes())); + LogUtil.log(config, Log.LEVEL_DEBUG, "compile", "recompile [" + getDisplayPath() + "] because loaded page has changed"); + pcn.set(page = compile(config, mapping.getClassRootDirectory(), page, false, pci != null && pci.ignoreScopes())); page.setPageSource(this); } } } } page.setLoadType(LOAD_PHYSICAL); - pci.setPageUsed(page); // + if (pci != null) pci.setPageUsed(page); // return page; } @@ -344,9 +323,9 @@ private Page loadPhysical(PageContext pc, Page page) throws TemplateException { // synchronized (SystemUtil.createToken("PageSource", getRealpathWithVirtual())) { // new class if (flush || !classFile.exists()) { - LogUtil.log(pc, Log.LEVEL_DEBUG, "compile", "compile [" + getDisplayPath() + "] no previous class file or flush"); + LogUtil.log(config, Log.LEVEL_DEBUG, "compile", "compile [" + getDisplayPath() + "] no previous class file or flush"); - pcn.set(page = compile(config, classRootDir, null, false, pc.ignoreScopes())); + pcn.set(page = compile(config, classRootDir, null, false, pci != null && pci.ignoreScopes())); flush = false; isNew = true; } @@ -357,48 +336,48 @@ private Page loadPhysical(PageContext pc, Page page) throws TemplateException { boolean done = false; if (cn != null) { try { - LogUtil.log(pc, Log.LEVEL_DEBUG, "compile", "load class from ClassLoader [" + getDisplayPath() + "]"); + LogUtil.log(config, Log.LEVEL_DEBUG, "compile", "load class from ClassLoader [" + getDisplayPath() + "]"); pcn.set(page = newInstance(mapping.getPhysicalClass(cn))); done = true; } catch (ClassNotFoundException cnfe) { - LogUtil.log(pc, "compile", cnfe); + LogUtil.log(config, "compile", cnfe); } } if (!done) { - LogUtil.log(pc, Log.LEVEL_DEBUG, "compile", "load class from binary [" + getDisplayPath() + "]"); + LogUtil.log(config, Log.LEVEL_DEBUG, "compile", "load class from binary [" + getDisplayPath() + "]"); byte[] bytes = IOUtil.toBytes(classFile); if (ClassUtil.isBytecode(bytes)) pcn.set(page = newInstance(mapping.getPhysicalClass(this.getClassName(), bytes))); } } catch (ClassFormatError cfe) { - LogUtil.log(pc, Log.LEVEL_ERROR, "compile", "size of the class file:" + classFile.length()); - LogUtil.log(pc, "compile", cfe); + LogUtil.log(config, Log.LEVEL_ERROR, "compile", "size of the class file:" + classFile.length()); + LogUtil.log(config, "compile", cfe); pcn.reset(); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(pc, "compile", t); + LogUtil.log(config, "compile", t); pcn.reset(); } if (page == null) { - LogUtil.log(pc, Log.LEVEL_DEBUG, "compile", "compile [" + getDisplayPath() + "] in case loading of the class fails"); - pcn.set(page = compile(config, classRootDir, null, false, pc.ignoreScopes())); + LogUtil.log(config, Log.LEVEL_DEBUG, "compile", "compile [" + getDisplayPath() + "] in case loading of the class fails"); + pcn.set(page = compile(config, classRootDir, null, false, pci != null && pci.ignoreScopes())); isNew = true; } } // check if version changed or lasMod - if (!isNew && (srcLastModified != page.getSourceLastModified() || page.getVersion() != pc.getConfig().getFactory().getEngine().getInfo().getFullVersionInfo())) { + if (!isNew && (srcLastModified != page.getSourceLastModified() || page.getVersion() != ConfigUtil.getCFMLEngine(config).getInfo().getFullVersionInfo())) { isNew = true; - LogUtil.log(pc, Log.LEVEL_DEBUG, "compile", "recompile [" + getDisplayPath() + "] because unloaded page has changed"); - pcn.set(page = compile(config, classRootDir, page, false, pc.ignoreScopes())); + LogUtil.log(config, Log.LEVEL_DEBUG, "compile", "recompile [" + getDisplayPath() + "] because unloaded page has changed"); + pcn.set(page = compile(config, classRootDir, page, false, pci != null && pci.ignoreScopes())); } page.setPageSource(this); page.setLoadType(LOAD_PHYSICAL); } - pci.setPageUsed(page); + if (pci != null) pci.setPageUsed(page); return page; } @@ -434,7 +413,7 @@ private boolean isLoad(byte load) { return page != null && load == page.getLoadType(); } - private Page compile(ConfigWeb config, Resource classRootDir, Page existing, boolean returnValue, boolean ignoreScopes) throws TemplateException { + private Page compile(Config config, Resource classRootDir, Page existing, boolean returnValue, boolean ignoreScopes) throws TemplateException { try { return _compile(config, classRootDir, existing, returnValue, ignoreScopes, false); } @@ -463,7 +442,7 @@ private Page compile(ConfigWeb config, Resource classRootDir, Page existing, boo } } - private Page _compile(ConfigWeb config, Resource classRootDir, Page existing, boolean returnValue, boolean ignoreScopes, boolean split) + private Page _compile(Config config, Resource classRootDir, Page existing, boolean returnValue, boolean ignoreScopes, boolean split) throws IOException, SecurityException, IllegalArgumentException, PageException { ConfigWebPro cwi = (ConfigWebPro) config; @@ -504,16 +483,13 @@ private Page _compile(ConfigWeb config, Resource classRootDir, Page existing, bo } } - private Page newInstance(Class clazz) - throws ClassException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException { - // PATCH Lucee add an appendix to cfml templates classes when updated, this causes problems with - // DynamicClassloader createding an sload of class files in development, this is normally just a - // problem in development - if (clazz.getName().indexOf("$cf$") != -1) { - Constructor c = clazz.getConstructor(new Class[] { PageSource.class }); - return (Page) c.newInstance(new Object[] { this }); - } - return (Page) ClassUtil.loadInstance(clazz, new Object[] { this }); + public Page newInstance(Class clazz) + throws InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException { + // if (clazz.getName().indexOf("$cf$") != -1) { + Constructor c = clazz.getConstructor(new Class[] { PageSource.class }); + return (Page) c.newInstance(new Object[] { this }); + // } + // return (Page) ClassUtil.loadInstance(clazz, new Object[] { this }); } /** @@ -523,27 +499,29 @@ private Page newInstance(Class clazz) */ @Override public String getDisplayPath() { + if (dspPath != null) return dspPath; + if (!mapping.hasArchive()) { - return StringUtil.toString(getPhyscalFile(), null); + return dspPath = StringUtil.toString(getPhyscalFile(), null); } else if (isLoad(LOAD_PHYSICAL)) { - return StringUtil.toString(getPhyscalFile(), null); + return dspPath = StringUtil.toString(getPhyscalFile(), null); } else if (isLoad(LOAD_ARCHIVE)) { - return StringUtil.toString(getArchiveSourcePath(), null); + return dspPath = StringUtil.toString(getArchiveSourcePath(), null); } else { boolean pse = physcalExists(); boolean ase = archiveExists(); if (mapping.isPhysicalFirst()) { - if (pse) return getPhyscalFile().toString(); - else if (ase) return getArchiveSourcePath(); - return getPhyscalFile().toString(); + if (pse) return dspPath = getPhyscalFile().toString(); + else if (ase) return dspPath = getArchiveSourcePath(); + return dspPath = getPhyscalFile().toString(); } - if (ase) return getArchiveSourcePath(); - else if (pse) return getPhyscalFile().toString(); - return getArchiveSourcePath(); + if (ase) return dspPath = getArchiveSourcePath(); + else if (pse) return dspPath = getPhyscalFile().toString(); + return dspPath = getArchiveSourcePath(); } } @@ -1140,4 +1118,8 @@ public void resetLoaded() { if (p != null) p.setLoadType((byte) 0); } + @Override + public final int hashCode() { + return getDisplayPath().hashCode(); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/StaticScope.java b/core/src/main/java/lucee/runtime/StaticScope.java index 749f0fbff9..225a8a8651 100644 --- a/core/src/main/java/lucee/runtime/StaticScope.java +++ b/core/src/main/java/lucee/runtime/StaticScope.java @@ -24,6 +24,7 @@ import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.Pair; +import lucee.runtime.component.ComponentPageRef; import lucee.runtime.component.DataMember; import lucee.runtime.component.Member; import lucee.runtime.component.StaticStruct; @@ -37,6 +38,7 @@ import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.ExpressionException; import lucee.runtime.exp.PageException; +import lucee.runtime.exp.PageRuntimeException; import lucee.runtime.type.Collection; import lucee.runtime.type.Objects; import lucee.runtime.type.Struct; @@ -51,35 +53,36 @@ public final class StaticScope extends StructSupport implements Variables, Objec private static final long serialVersionUID = -2692540782121852340L; private final StaticScope base; - private ComponentPageImpl cp; + private ComponentPageRef cpr; private final int dataMemberDefaultAccess; private final ComponentImpl c; - public StaticScope(StaticScope base, ComponentImpl c, ComponentPageImpl cp, int dataMemberDefaultAccess) { + public StaticScope(StaticScope base, ComponentImpl c, ComponentPageRef cpr, int dataMemberDefaultAccess) { this.base = base; - this.cp = cp; + this.cpr = cpr; this.c = c; this.dataMemberDefaultAccess = dataMemberDefaultAccess; } - public PageSource getPageSource() { - return this.cp.getPageSource(); + public PageSource getPageSource(PageContext pc) throws PageException { + return this.cpr.get(pc).getPageSource(); } @Override public int size() { - int s = cp.getStaticStruct().size(); + ComponentPageImpl cp = cpr.get(null, null); + int s = cp == null ? 0 : cp.getStaticStruct().size(); return (base == null) ? s : base.size() + s; } public Member _remove(PageContext pc, Key key) throws PageException { // does the current struct has this key - StaticStruct ss = cp.getStaticStruct(); + StaticStruct ss = cpr.get(pc).getStaticStruct(); Member m = ss.get(key); if (m != null) { - if (m.getModifier() == Member.MODIFIER_FINAL) - throw new ExpressionException("Cannot remove key [" + key + "] in static scope from component [" + cp.getComponentName() + "], that member is set to final"); + if (m.getModifier() == Member.MODIFIER_FINAL) throw new ExpressionException( + "Cannot remove key [" + key + "] in static scope from component [" + cpr.get(pc).getComponentName() + "], that member is set to final"); if (!c.isAccessible(ThreadLocalPageContext.get(pc), m.getAccess())) throw notExisting(key); return ss.remove(key); @@ -111,13 +114,15 @@ public Object removeEL(Key key) { @Override public void clear() { if (base != null) base.clear(); - cp.getStaticStruct().clear(); + ComponentPageImpl cp = cpr.get(null, null); + if (cp != null) cp.getStaticStruct().clear(); } private Member _get(PageContext pc, Key key, Member defaultValue) { // does the current struct has this key - StaticStruct ss = cp.getStaticStruct(); - if (!ss.isEmpty()) { + ComponentPageImpl cp = cpr.get(pc, null); + StaticStruct ss = cp == null ? null : cp.getStaticStruct(); + if (ss != null && !ss.isEmpty()) { Member m = ss.get(key); if (m != null) { @@ -132,8 +137,9 @@ private Member _get(PageContext pc, Key key, Member defaultValue) { private Pair _getWithBase(PageContext pc, Key key, Member defaultValue) { // does the current struct has this key - StaticStruct ss = cp.getStaticStruct(); - if (!ss.isEmpty()) { + ComponentPageImpl cp = cpr.get(pc, null); + StaticStruct ss = cp == null ? null : cp.getStaticStruct(); + if (ss != null && !ss.isEmpty()) { Member m = ss.get(key); if (m != null) { @@ -197,16 +203,17 @@ private Member _setIfExists(PageContext pc, Key key, Object value, int access, i return null; } - private Member _set(PageContext pc, Member existing, Key key, Object value, int access, int modifier) throws ExpressionException { + private Member _set(PageContext pc, Member existing, Key key, Object value, int access, int modifier) throws PageException { if (value instanceof Member) { - return cp.getStaticStruct().put(key, (Member) value); + return cpr.get(pc).getStaticStruct().put(key, (Member) value); } // check if user has access if (!c.isAccessible(pc, existing != null ? existing.getAccess() : dataMemberDefaultAccess)) throw notExisting(key); // set - return cp.getStaticStruct().put(key, new DataMember(existing != null ? existing.getAccess() : access, existing != null ? existing.getModifier() : modifier, value)); + return cpr.get(pc).getStaticStruct().put(key, + new DataMember(existing != null ? existing.getAccess() : access, existing != null ? existing.getModifier() : modifier, value)); } @Override @@ -259,13 +266,15 @@ public Collection duplicate(boolean deepCopy) { @Override public final boolean containsKey(Key key) { if (base != null && base.containsKey(key)) return true; - return cp.getStaticStruct().containsKey(key); + ComponentPageImpl cp = cpr.get(null, null); + return cp != null && cp.getStaticStruct().containsKey(key); } @Override public final boolean containsKey(PageContext pc, Key key) { if (base != null && base.containsKey(pc, key)) return true; - return cp.getStaticStruct().containsKey(key); + ComponentPageImpl cp = cpr.get(null, null); + return cp != null && cp.getStaticStruct().containsKey(key); } @Override @@ -298,12 +307,15 @@ Map _entries(Map map, int access) { if (base != null) base._entries(map, access); // fill accessable keys - StaticStruct ss = cp.getStaticStruct(); - Iterator> it = ss.entrySet().iterator(); - Entry e; - while (it.hasNext()) { - e = it.next(); - if (e.getValue().getAccess() <= access) map.put(e.getKey(), e.getValue().getValue()); + ComponentPageImpl cp = cpr.get(null, null); + StaticStruct ss = cp == null ? null : cp.getStaticStruct(); + if (ss != null) { + Iterator> it = ss.entrySet().iterator(); + Entry e; + while (it.hasNext()) { + e = it.next(); + if (e.getValue().getAccess() <= access) map.put(e.getKey(), e.getValue().getValue()); + } } return map; } @@ -313,12 +325,15 @@ private Map all(Map map) { if (base != null) base.all(map); // fill accessable keys - StaticStruct ss = cp.getStaticStruct(); - Iterator> it = ss.entrySet().iterator(); - Entry e; - while (it.hasNext()) { - e = it.next(); - map.put(e.getKey(), e.getValue()); + ComponentPageImpl cp = cpr.get(null, null); + StaticStruct ss = cp == null ? null : cp.getStaticStruct(); + if (ss != null) { + Iterator> it = ss.entrySet().iterator(); + Entry e; + while (it.hasNext()) { + e = it.next(); + map.put(e.getKey(), e.getValue()); + } } return map; } @@ -350,7 +365,7 @@ Object _call(PageContext pc, Collection.Key calledName, UDF udf, Struct namedArg // debug yes if (((PageContextImpl) pc).hasDebugOptions(ConfigPro.DEBUG_TEMPLATE)) { - DebugEntryTemplate debugEntry = pc.getDebugger().getEntry(pc, cp.getPageSource(), udf.getFunctionName());// new DebugEntry(src,udf.getFunctionName()); + DebugEntryTemplate debugEntry = pc.getDebugger().getEntry(pc, cpr.get(pc).getPageSource(), udf.getFunctionName());// new DebugEntry(src,udf.getFunctionName()); long currTime = pc.getExecutionTime(); long time = System.nanoTime(); @@ -418,8 +433,12 @@ public boolean isBind() { @Override public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) { int access = c.getAccess(pageContext); + + ComponentPageImpl cp = cpr.get(pageContext, null); + String cfcName = cp != null ? cp.getComponentName() : "unknown"; // unlikely ever happen + DumpTable table = new DumpTable("component", "#99cc99", "#ccffcc", "#000000"); - table.setTitle("Static Scope from Component " + cp.getComponentName()); + table.setTitle("Static Scope from Component " + cfcName); table.setComment("Only the functions and data members that are accessible from your location are displayed"); DumpTable content = _toDumpData(c.top, pageContext, maxlevel, dp, access); @@ -518,11 +537,23 @@ public static void afterStaticCall(PageContext pc, ComponentImpl c, Variables pa } private ExpressionException notExisting(Collection.Key key) { - return new ExpressionException( - ExceptionUtil.similarKeyMessage(this, key.getString(), "static member", "static members", "Component [" + cp.getComponentName() + "]", true)); + ComponentPageImpl cp = cpr.get(null, null); + String cfcName = cp != null ? cp.getComponentName() : "unknown"; // unlikely ever happen + return new ExpressionException(ExceptionUtil.similarKeyMessage(this, key.getString(), "static member", "static members", "Component [" + cfcName + "]", true)); } public long index() { - return cp.getStaticStruct().index(); + try { + ComponentPageImpl cp = cpr.get(null); + return cp.getStaticStruct().index(); + } + catch (PageException e) { + throw new PageRuntimeException(e); + } + } + + @Override + public final int hashCode() { + return java.util.Objects.hash(c); } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/SuperComponent.java b/core/src/main/java/lucee/runtime/SuperComponent.java index b04d36f638..fd388e49c1 100755 --- a/core/src/main/java/lucee/runtime/SuperComponent.java +++ b/core/src/main/java/lucee/runtime/SuperComponent.java @@ -53,6 +53,11 @@ private SuperComponent(ComponentImpl comp) { this.comp = comp; } + @Override + protected final int hash() { + return java.util.Objects.hash(comp); + } + public static Member superMember(ComponentImpl comp) { if (comp == null) return new DataMember(Component.ACCESS_PRIVATE, Member.MODIFIER_NONE, new StructImpl()); return new SuperComponent(comp); diff --git a/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java b/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java index f94f207d25..bcd83a25e3 100644 --- a/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java +++ b/core/src/main/java/lucee/runtime/ai/AIEngineFactory.java @@ -54,7 +54,7 @@ public static AIEngine getInstance(Config config, String name, Struct data) thro ClassDefinition cd; - cd = ConfigFactoryImpl.getClassDefinition(data, "", config.getIdentification()); + cd = ConfigFactoryImpl.getClassDefinition(config, data, "", config.getIdentification()); if (cd.hasClass()) { Struct custom = Caster.toStruct(data.get(KeyConstants._custom, null), null); diff --git a/core/src/main/java/lucee/runtime/com/COMObject.java b/core/src/main/java/lucee/runtime/com/COMObject.java index f43e4ca94b..d459ba53a1 100644 --- a/core/src/main/java/lucee/runtime/com/COMObject.java +++ b/core/src/main/java/lucee/runtime/com/COMObject.java @@ -52,11 +52,13 @@ public final class COMObject implements Objects, Iteratorable { private static boolean setup; private String name; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private Dispatch dispatch; private Variant parent; public static void setupWindowsDLL(Config config) { - if (SystemUtil.isWindows()) { + if (IS_WINDOWS) { Resource binDir = config.getConfigDir().getRealResource("bin"); if (binDir != null) { String name = (SystemUtil.getJREArch() == SystemUtil.ARCH_64) ? "jacob-x64.dll" : "jacob-i586.dll"; diff --git a/core/src/main/java/lucee/runtime/compiler/CFMLCompilerImpl.java b/core/src/main/java/lucee/runtime/compiler/CFMLCompilerImpl.java index 66b621bbdc..216d5e6801 100755 --- a/core/src/main/java/lucee/runtime/compiler/CFMLCompilerImpl.java +++ b/core/src/main/java/lucee/runtime/compiler/CFMLCompilerImpl.java @@ -66,6 +66,8 @@ */ public final class CFMLCompilerImpl implements CFMLCompiler { + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private CFMLTransformer cfmlTagTransformer; private CFMLScriptTransformer cfmlScriptTransformer; private ConcurrentLinkedQueue watched = new ConcurrentLinkedQueue(); @@ -239,7 +241,7 @@ private Result _compile(ConfigPro config, PageSource ps, SourceCode sc, String c final Resource classFile = classRootDir.getRealResource(page.getClassName() + ".class"); Resource classFileDirectory = classFile.getParentResource(); if (!classFileDirectory.exists()) classFileDirectory.mkdirs(); - else if (classFile.exists() && !SystemUtil.isWindows()) { + else if (classFile.exists() && !IS_WINDOWS) { final String prefix = page.getClassName() + "$"; classRootDir.list(new ResourceNameFilter() { @Override diff --git a/core/src/main/java/lucee/runtime/component/ComponentLoader.java b/core/src/main/java/lucee/runtime/component/ComponentLoader.java index 43a115a889..6f138bc5b5 100755 --- a/core/src/main/java/lucee/runtime/component/ComponentLoader.java +++ b/core/src/main/java/lucee/runtime/component/ComponentLoader.java @@ -27,7 +27,7 @@ import lucee.commons.io.res.filter.ResourceFilter; import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.MappingUtil; -import lucee.commons.lang.PhysicalClassLoader; +import lucee.commons.lang.PhysicalClassLoaderFactory; import lucee.commons.lang.StringUtil; import lucee.runtime.CIObject; import lucee.runtime.CIPage; @@ -125,7 +125,8 @@ public static StaticScope getStaticScope(PageContext pc, PageSource loadingLocat Component c = ss.getComponent(); while ((bc = (ComponentImpl) c.getBaseComponent()) != null) { ComponentPageImpl bcp = (ComponentPageImpl) ((PageSourceImpl) bc._getPageSource()).loadPage(pc, false, null); - if (bcp.getStaticStruct() != null) { + // bcp can be null during concurrent class initialization (race condition) + if (bcp != null && bcp.getStaticStruct() != null) { long idx = bcp.getStaticStruct().index(); if (idx == 0 || idx > index) { reload = true; @@ -256,15 +257,17 @@ private static Object _search(PageContext pc, PageSource loadingLocation, String // CACHE // check local in cache String localCacheName = null; - if (searchLocal == null) searchLocal = Caster.toBoolean(rawPath.indexOf('.') == -1 ? true : config.getComponentLocalSearch()); - if (searchLocal && isRealPath && currPS != null) { - localCacheName = currPS.getDisplayPath().replace('\\', '/'); - localCacheName = localCacheName.substring(0, localCacheName.lastIndexOf('/') + 1).concat(pathWithCFC); - if (doCache) { - page = config.getCachedPage(pc, localCacheName); - if (page != null) { - return returnType == RETURN_TYPE_PAGE ? page - : load(pc, page, trim(path.replace('/', '.')), sub, isRealPath, returnType, isExtendedComponent, executeConstr, validate); + if (isRealPath && currPS != null) { + if (searchLocal == null) searchLocal = Caster.toBoolean(rawPath.indexOf('.') == -1 ? true : config.getComponentLocalSearch()); + if (searchLocal.booleanValue()) { + localCacheName = currPS.getDisplayPath().replace('\\', '/'); + localCacheName = localCacheName.substring(0, localCacheName.lastIndexOf('/') + 1).concat(pathWithCFC); + if (doCache) { + page = config.getCachedPage(pc, localCacheName); + if (page != null) { + return returnType == RETURN_TYPE_PAGE ? page + : load(pc, page, trim(path.replace('/', '.')), sub, isRealPath, returnType, isExtendedComponent, executeConstr, validate); + } } } } @@ -311,7 +314,7 @@ else if ((currP = currPS.loadPage(pc, false, null)) != null) { // SEARCH // search from local - if (searchLocal && isRealPath) { + if (searchLocal != null && searchLocal && isRealPath) { // check realpath PageSource[] arr = ((PageContextImpl) pc).getRelativePageSources(pathWithCFC); page = toCIPage(PageSourceImpl.loadPage(pc, arr, null)); @@ -548,7 +551,7 @@ private static CIPage loadSub(CIPage page, String sub) throws ApplicationExcepti CIPage[] subs = page.getSubPages(); for (int i = 0; i < subs.length; i++) { - if (PhysicalClassLoader.substractAppendix(subs[i].getClass().getName()).equals(subClassName)) { + if (PhysicalClassLoaderFactory.substractAppendix(subs[i].getClass().getName()).equals(subClassName)) { return subs[i]; } } diff --git a/core/src/main/java/lucee/runtime/component/ComponentPageRef.java b/core/src/main/java/lucee/runtime/component/ComponentPageRef.java new file mode 100644 index 0000000000..cc103ef67b --- /dev/null +++ b/core/src/main/java/lucee/runtime/component/ComponentPageRef.java @@ -0,0 +1,45 @@ +package lucee.runtime.component; + +import java.lang.ref.WeakReference; + +import lucee.commons.io.SystemUtil; +import lucee.runtime.ComponentPageImpl; +import lucee.runtime.PageContext; +import lucee.runtime.PageSource; +import lucee.runtime.exp.PageException; + +public class ComponentPageRef { + + private PageSource ps; + private WeakReference ref; + + public ComponentPageRef(ComponentPageImpl cp) { + this.ref = new WeakReference(cp); + ps = cp.getPageSource(); + } + + public ComponentPageImpl get(PageContext pc) throws PageException { + + ComponentPageImpl cp = ref.get(); + if (cp == null) { + synchronized (SystemUtil.createToken("ComponentPageRef", "" + ps.hashCode())) { + cp = ref.get(); + if (cp == null) { + cp = (ComponentPageImpl) ps.loadPage(null, false); + this.ref = new WeakReference(cp); + } + } + + } + return cp; + } + + public ComponentPageImpl get(PageContext pc, ComponentPageImpl defaultValue) { + try { + return get(pc); + } + catch (Exception e) { + return defaultValue; + } + } +} diff --git a/core/src/main/java/lucee/runtime/component/DataMember.java b/core/src/main/java/lucee/runtime/component/DataMember.java index 05816ff0c9..ae00c5f7ca 100644 --- a/core/src/main/java/lucee/runtime/component/DataMember.java +++ b/core/src/main/java/lucee/runtime/component/DataMember.java @@ -37,4 +37,9 @@ public Object getValue() { public Object duplicate(boolean deepCopy) { return new DataMember(getAccess(), getModifier(), Duplicator.duplicate(value, deepCopy)); } + + @Override + protected final int hash() { + return value.hashCode(); + } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/component/MemberSupport.java b/core/src/main/java/lucee/runtime/component/MemberSupport.java index 941bd6694c..1949b47bb4 100644 --- a/core/src/main/java/lucee/runtime/component/MemberSupport.java +++ b/core/src/main/java/lucee/runtime/component/MemberSupport.java @@ -60,6 +60,13 @@ public int getAccess() { return access; } + @Override + public final int hashCode() { + return hash(); + } + + protected abstract int hash(); + /** * @param access */ diff --git a/core/src/main/java/lucee/runtime/component/MetadataUtil.java b/core/src/main/java/lucee/runtime/component/MetadataUtil.java index 04264ebf63..3b64e2d985 100644 --- a/core/src/main/java/lucee/runtime/component/MetadataUtil.java +++ b/core/src/main/java/lucee/runtime/component/MetadataUtil.java @@ -31,7 +31,7 @@ public final class MetadataUtil { public static Page getPageWhenMetaDataStillValid(PageContext pc, ComponentImpl comp, boolean ignoreCache) throws PageException { - Page page = comp._getComponentPageImpl(); + Page page = comp._getComponentPageImpl(pc); if (page == null) page = getPage(pc, comp._getPageSource()); if (ignoreCache) return page; diff --git a/core/src/main/java/lucee/runtime/component/PropertyImpl.java b/core/src/main/java/lucee/runtime/component/PropertyImpl.java index 9f81a7f947..4ba2fa264b 100644 --- a/core/src/main/java/lucee/runtime/component/PropertyImpl.java +++ b/core/src/main/java/lucee/runtime/component/PropertyImpl.java @@ -22,6 +22,7 @@ import lucee.commons.lang.StringUtil; import lucee.runtime.Component; +import lucee.runtime.PageSource; import lucee.runtime.converter.ConverterException; import lucee.runtime.converter.ScriptConverter; import lucee.runtime.engine.ThreadLocalPageContext; @@ -29,6 +30,7 @@ import lucee.runtime.exp.PageRuntimeException; import lucee.runtime.op.Caster; import lucee.runtime.op.Duplicator; +import lucee.runtime.type.Collection; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; import lucee.runtime.type.util.KeyConstants; @@ -40,20 +42,26 @@ public final class PropertyImpl extends MemberSupport implements Property, ASMPr private static final long serialVersionUID = 3206074213415946902L; + // Reference fields (8 bytes each) - group together to minimize padding private String type = "any"; private String name; - private boolean required; - private boolean setter = true; - private boolean getter = true; - + private Collection.Key nameAsKey; // Cached key for property name + private Collection.Key getterKey; // Cached key for "getName" + private Collection.Key setterKey; // Cached key for "setName" + private String getterName; // Cached "getName" string + private String setterName; // Cached "setName" string private Object _default; private String displayname = ""; private String hint = ""; - private Struct dynAttrs = new StructImpl(); + private Struct dynAttrs; // lazy-init to avoid allocating ConcurrentHashMap(32) per property private Struct metadata; - private String ownerName; + private PageSource ownerPageSource; + // Boolean fields (1 byte each) - group at end to minimize padding + private boolean required; + private boolean setter = true; + private boolean getter = true; private boolean axisType; public PropertyImpl() { @@ -65,6 +73,11 @@ public PropertyImpl(boolean axisType) { this.axisType = axisType; } + @Override + protected final int hash() { + return java.util.Objects.hash(name); + } + @Override public String getDefault() { try { @@ -135,6 +148,58 @@ public String getName() { */ public void setName(String name) { this.name = name; + this.nameAsKey = null; // Clear cached key when name changes + this.getterKey = null; // Clear cached getter key + this.setterKey = null; // Clear cached setter key + this.getterName = null; // Clear cached getter name + this.setterName = null; // Clear cached setter name + } + + public Collection.Key getNameAsKey() { + if (nameAsKey == null && name != null) { + nameAsKey = lucee.runtime.type.KeyImpl.init(name); + } + return nameAsKey; + } + + public void setNameAsKey(Collection.Key key) { + this.nameAsKey = key; + } + + public Collection.Key getGetterKey() { + if (getterKey == null && name != null) { + getterKey = lucee.runtime.type.KeyImpl.init("get" + name); + } + return getterKey; + } + + public void setGetterKey(Collection.Key key) { + this.getterKey = key; + } + + public Collection.Key getSetterKey() { + if (setterKey == null && name != null) { + setterKey = lucee.runtime.type.KeyImpl.init("set" + name); + } + return setterKey; + } + + public void setSetterKey(Collection.Key key) { + this.setterKey = key; + } + + public String getGetterName() { + if (getterName == null && name != null) { + getterName = "get" + StringUtil.ucFirst(name); + } + return getterName; + } + + public String getSetterName() { + if (setterName == null && name != null) { + setterName = "set" + StringUtil.ucFirst(name); + } + return setterName; } /** @@ -207,9 +272,20 @@ public void setGetter(boolean getter) { this.getter = getter; } + /** + * Lazy-init helper for dynAttrs - creates HashMap with minimal capacity only when needed + */ + private Struct ensureDynAttrs() { + if (dynAttrs == null) { + dynAttrs = new StructImpl(StructImpl.TYPE_REGULAR, 8); + } + return dynAttrs; + } + @Override public Object getMetaData() { - Struct sct = new StructImpl(); + // Typical size: name + hint + displayname + type + default + dynAttrs + metadata = ~16 + Struct sct = new StructImpl(StructImpl.TYPE_REGULAR, 16); // meta if (metadata != null) StructUtil.copy(metadata, sct, true); @@ -220,8 +296,8 @@ public Object getMetaData() { if (!StringUtil.isEmpty(type, true)) sct.setEL(KeyConstants._type, type); if (_default != null) sct.setEL(KeyConstants._default, _default); - // dyn attributes - StructUtil.copy(dynAttrs, sct, true); + // dyn attributes (includes 'required' when explicitly set) + if (dynAttrs != null) StructUtil.copy(dynAttrs, sct, true); return sct; } @@ -232,7 +308,7 @@ public void setDynamicAttributes(Struct dynAttrs) { @Override public Struct getDynamicAttributes() { - return dynAttrs; + return ensureDynAttrs(); } public void setMetaData(Struct metadata) { @@ -241,36 +317,47 @@ public void setMetaData(Struct metadata) { @Override public Struct getMeta() { - if (metadata == null) metadata = new StructImpl(); + if (metadata == null) metadata = new StructImpl(StructImpl.TYPE_REGULAR, 8); return metadata; } @Override - public Class getClazz() { + public Class getClazz() { return null; } @Override public boolean isPeristent() { - return Caster.toBooleanValue(dynAttrs.get(KeyConstants._persistent, Boolean.TRUE), true); + return dynAttrs == null ? true : Caster.toBooleanValue(dynAttrs.get(KeyConstants._persistent, Boolean.TRUE), true); } public void setOwnerName(String ownerName) { this.ownerName = ownerName; } + public void setOwnerName(String ownerName, PageSource ownerPageSource) { + this.ownerName = ownerName; + this.ownerPageSource = ownerPageSource; + } + @Override public String getOwnerName() { return ownerName; } + public PageSource getOwnerPageSource() { + return ownerPageSource; + } + @Override public String toString() { String strDynAttrs = ""; - try { - strDynAttrs = new ScriptConverter().serialize(dynAttrs); - } - catch (ConverterException ce) { + if (dynAttrs != null) { + try { + strDynAttrs = new ScriptConverter().serialize(dynAttrs); + } + catch (ConverterException ce) { + } } return "default:" + this._default + ";displayname:" + this.displayname + ";hint:" + this.hint + ";name:" + this.name + ";type:" + this.type + ";ownerName:" + ownerName @@ -280,10 +367,46 @@ public String toString() { @Override public boolean equals(Object obj) { if (this == obj) return true; - if (!(obj instanceof Property)) return false; - Property other = (Property) obj; + if (!(obj instanceof PropertyImpl)) return false; + PropertyImpl other = (PropertyImpl) obj; + + // Compare actual fields instead of allocating strings via toString() + if (!StringUtil.emptyIfNull(name).equals(StringUtil.emptyIfNull(other.name))) return false; + if (!StringUtil.emptyIfNull(type).equals(StringUtil.emptyIfNull(other.type))) return false; + if (!StringUtil.emptyIfNull(displayname).equals(StringUtil.emptyIfNull(other.displayname))) return false; + if (!StringUtil.emptyIfNull(hint).equals(StringUtil.emptyIfNull(other.hint))) return false; + if (!StringUtil.emptyIfNull(ownerName).equals(StringUtil.emptyIfNull(other.ownerName))) return false; + if (required != other.required) return false; + if (getter != other.getter) return false; + if (setter != other.setter) return false; + + // Compare default value + if (_default == null) { + if (other._default != null) return false; + } + else if (!_default.equals(other._default)) return false; - return toString().equals(other.toString()); + // Compare dynamic attributes + if (dynAttrs == null) { + if (other.dynAttrs != null && other.dynAttrs.size() > 0) return false; + } + else if (other.dynAttrs == null) { + if (dynAttrs.size() > 0) return false; + } + else { + // Both have dynAttrs - use toString() comparison as fallback for Struct comparison + try { + String thisDynAttrs = new ScriptConverter().serialize(dynAttrs); + String otherDynAttrs = new ScriptConverter().serialize(other.dynAttrs); + if (!thisDynAttrs.equals(otherDynAttrs)) return false; + } + catch (ConverterException ce) { + // If serialization fails, fall back to reference equality + if (dynAttrs != other.dynAttrs) return false; + } + } + + return true; } @Override @@ -293,14 +416,19 @@ public Object duplicate(boolean deepCopy) { other.displayname = displayname; other.getter = getter; other.hint = hint; - other.dynAttrs = deepCopy ? (Struct) Duplicator.duplicate(dynAttrs, deepCopy) : dynAttrs; + other.dynAttrs = dynAttrs == null ? null : (deepCopy ? (Struct) Duplicator.duplicate(dynAttrs, deepCopy) : dynAttrs); other.name = name; other.ownerName = ownerName; + other.ownerPageSource = ownerPageSource; other.required = required; other.setter = setter; other.type = type; + // Copy cached keys and names to avoid recalculation + other.nameAsKey = nameAsKey; + other.getterKey = getterKey; + other.setterKey = setterKey; + other.getterName = getterName; + other.setterName = setterName; return other; - } - -} \ No newline at end of file + }} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/config/ComponentPathCache.java b/core/src/main/java/lucee/runtime/config/ComponentPathCache.java index 5c02de1fc0..eaf6cba45a 100644 --- a/core/src/main/java/lucee/runtime/config/ComponentPathCache.java +++ b/core/src/main/java/lucee/runtime/config/ComponentPathCache.java @@ -1,64 +1,54 @@ package lucee.runtime.config; +import java.lang.ref.Reference; import java.lang.ref.SoftReference; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; -import java.util.concurrent.ConcurrentHashMap; import lucee.runtime.CIPage; import lucee.runtime.Page; import lucee.runtime.PageContext; import lucee.runtime.PageSource; import lucee.runtime.exp.PageException; -import lucee.runtime.exp.TemplateException; import lucee.runtime.type.KeyImpl; import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; public class ComponentPathCache { - private Map> componentPathCache = null;// new ArrayList(); + private final Map> componentPathCache = new HashMap<>(); - public CIPage getPage(PageContext pc, String pathWithCFC) throws TemplateException { - if (componentPathCache == null) return null; - SoftReference tmp = componentPathCache.get(pathWithCFC.toLowerCase()); + public CIPage getPage(PageContext pc, String pathWithCFC) throws PageException { + Reference tmp = componentPathCache.get(pathWithCFC.toLowerCase()); PageSource ps = tmp == null ? null : tmp.get(); if (ps == null) return null; - - try { - return (CIPage) ps.loadPageThrowTemplateException(pc, false, (Page) null); - } - catch (PageException pe) { - throw (TemplateException) pe; - } + return (CIPage) ps.loadPageThrowTemplateException(pc, false, (Page) null); } public void put(String pathWithCFC, PageSource ps) { - if (componentPathCache == null) componentPathCache = new ConcurrentHashMap>();// MUSTMUST new // ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT); componentPathCache.put(pathWithCFC.toLowerCase(), new SoftReference(ps)); } public void flush() { - if (componentPathCache != null) componentPathCache.clear(); + componentPathCache.clear(); } public void clear() { - if (componentPathCache == null) return; componentPathCache.clear(); } public Struct list() { Struct sct = new StructImpl(); - if (componentPathCache == null) return sct; - Iterator>> it = componentPathCache.entrySet().iterator(); + Iterator>> it = componentPathCache.entrySet().iterator(); - Entry> entry; + Entry> entry; while (it.hasNext()) { entry = it.next(); String k = entry.getKey(); if (k == null) continue; - SoftReference v = entry.getValue(); + Reference v = entry.getValue(); if (v == null) continue; PageSource ps = v.get(); if (ps == null) continue; diff --git a/core/src/main/java/lucee/runtime/config/ConfigAdmin.java b/core/src/main/java/lucee/runtime/config/ConfigAdmin.java index 1acbda5368..2cbbf61081 100755 --- a/core/src/main/java/lucee/runtime/config/ConfigAdmin.java +++ b/core/src/main/java/lucee/runtime/config/ConfigAdmin.java @@ -275,13 +275,13 @@ public void removePassword(String contextPath) throws PageException, IOException private ConfigAdmin(ConfigPro config, Password password) throws IOException, PageException { this.config = ConfigUtil.getConfigServerImpl(config); this.password = password; - root = ConfigFactoryImpl.loadDocument(config.getConfigFile()); + root = ConfigFactoryImpl.loadDocument(config, config.getConfigFile()); } private ConfigAdmin(ConfigPro config, Password password, boolean optionalPW) throws IOException, PageException { this.config = ConfigUtil.getConfigServerImpl(config); this.password = password; - root = ConfigFactoryImpl.loadDocument(config.getConfigFile()); + root = ConfigFactoryImpl.loadDocument(config, config.getConfigFile()); this.optionalPW = optionalPW; } @@ -700,7 +700,7 @@ public void updateMapping(String virtual, String physical, String archive, Strin private void _updateMaven(GAVSO mvnEntry) throws PageException { Struct javasettings = _getRootElement("javasettings"); - Array maven = ConfigUtil.getAsArray(javasettings, false, "maven", "mvn"); + Array maven = ConfigUtil.getAsArray(config, javasettings, false, "maven", "mvn"); // update GAVSO ex; @@ -1016,7 +1016,7 @@ public void removeRestMapping(String virtual) throws ExpressionException, Securi public void removeCustomTag(String virtual) throws SecurityException { checkWriteAccess(); - Array mappings = ConfigUtil.getAsArray(root, true, KeyConstants._virtual, KeyConstants._physical, false, "customTagMappings", "customTagPaths"); + Array mappings = ConfigUtil.getAsArray(config, root, true, KeyConstants._virtual, KeyConstants._physical, false, "customTagMappings", "customTagPaths"); Key[] keys = mappings.keys(); Struct data; String v; @@ -1024,7 +1024,7 @@ public void removeCustomTag(String virtual) throws SecurityException { Key key = keys[i]; data = Caster.toStruct(mappings.get(key, null), null); if (data == null) continue; - v = createVirtual(data); + v = createVirtual(config, data); if (virtual.equals(v)) { mappings.removeEL(key); @@ -1055,7 +1055,7 @@ private void _removeScheduledTask(String name) throws ExpressionException { public void removeComponentMapping(String virtual) throws SecurityException { checkWriteAccess(); - Array mappings = ConfigUtil.getAsArray(root, true, KeyConstants._virtual, KeyConstants._physical, false, "componentMappings", "componentPaths"); + Array mappings = ConfigUtil.getAsArray(config, root, true, KeyConstants._virtual, KeyConstants._physical, false, "componentMappings", "componentPaths"); Key[] keys = mappings.keys(); Struct data; String v; @@ -1063,7 +1063,7 @@ public void removeComponentMapping(String virtual) throws SecurityException { Key key = keys[i]; data = Caster.toStruct(mappings.get(key, null), null); if (data == null) continue; - v = createVirtual(data); + v = createVirtual(config, data); if (virtual.equals(v)) { mappings.removeEL(key); @@ -1108,7 +1108,7 @@ private void _updateCustomTag(String virtual, String physical, String archive, S throw new ExpressionException("physical must have a value when primary has value physical"); } - Array mappings = ConfigUtil.getAsArray(root, true, KeyConstants._virtual, KeyConstants._physical, false, "customTagMappings", "customTagPaths"); + Array mappings = ConfigUtil.getAsArray(config, root, true, KeyConstants._virtual, KeyConstants._physical, false, "customTagMappings", "customTagPaths"); Key[] keys = mappings.keys(); // Update String v; @@ -1117,7 +1117,7 @@ private void _updateCustomTag(String virtual, String physical, String archive, S Key key = keys[i]; Struct el = Caster.toStruct(mappings.get(key, null), null); if (el == null) continue; - v = createVirtual(el); + v = createVirtual(config, el); if (virtual.equals(v)) { el.setEL("virtual", v); el.setEL("physical", physical); @@ -1151,7 +1151,7 @@ private void _updateCustomTag(String virtual, String physical, String archive, S el.setEL("inspectTemplateIntervalSlow", Caster.toString(inspectTemplateIntervalSlow, "")); if (ConfigPro.INSPECT_INTERVAL_FAST != inspectTemplateIntervalFast && ConfigPro.INSPECT_INTERVAL_UNDEFINED != inspectTemplateIntervalFast) el.setEL("inspectTemplateIntervalFast", Caster.toString(inspectTemplateIntervalFast, "")); - el.setEL("virtual", StringUtil.isEmpty(virtual) ? createVirtual(el) : virtual); + el.setEL("virtual", StringUtil.isEmpty(virtual) ? createVirtual(config, el) : virtual); config.resetCustomTagMappings(); } @@ -1209,7 +1209,7 @@ private void _updateComponentMapping(String virtual, String physical, String arc throw new ExpressionException("physical must have a value when primary has value physical"); } - Array componentMappings = ConfigUtil.getAsArray(root, true, KeyConstants._virtual, KeyConstants._physical, false, "componentMappings", "componentPaths"); + Array componentMappings = ConfigUtil.getAsArray(config, root, true, KeyConstants._virtual, KeyConstants._physical, false, "componentMappings", "componentPaths"); Key[] keys = componentMappings.keys(); Struct el; @@ -1221,7 +1221,7 @@ private void _updateComponentMapping(String virtual, String physical, String arc data = Caster.toStruct(componentMappings.get(key, null), null); if (data == null) continue; - v = createVirtual(data); + v = createVirtual(config, data); if (virtual.equals(v)) { data.setEL("virtual", v); @@ -1254,14 +1254,14 @@ private void _updateComponentMapping(String virtual, String physical, String arc el.setEL("inspectTemplateIntervalSlow", Caster.toString(inspectTemplateIntervalSlow, "")); if (ConfigPro.INSPECT_INTERVAL_FAST != inspectTemplateIntervalFast && ConfigPro.INSPECT_INTERVAL_UNDEFINED != inspectTemplateIntervalFast) el.setEL("inspectTemplateIntervalFast", Caster.toString(inspectTemplateIntervalFast, "")); - el.setEL("virtual", StringUtil.isEmpty(virtual) ? createVirtual(el) : virtual); + el.setEL("virtual", StringUtil.isEmpty(virtual) ? createVirtual(config, el) : virtual); config.resetComponentMappings(); } - public static String createVirtual(Struct data) { - String str = ConfigFactoryImpl.getAttr(data, "virtual"); + public static String createVirtual(Config config, Struct data) { + String str = ConfigFactoryImpl.getAttr(config, data, "virtual"); if (!StringUtil.isEmpty(str)) return str; - return createVirtual(ConfigFactoryImpl.getAttr(data, "physical"), ConfigFactoryImpl.getAttr(data, "archive")); + return createVirtual(ConfigFactoryImpl.getAttr(config, data, "physical"), ConfigFactoryImpl.getAttr(config, data, "archive")); } public static String createVirtual(String physical, String archive) { @@ -6347,12 +6347,6 @@ protected static void _removeRHExtension(ConfigPro config, RHExtension rhext, RH } } - protected static void deleteExtensionFile(RHExtension ext, Resource r) throws IOException { - synchronized (SystemUtil.createToken("updateExtension", ext.getId())) { - r.remove(true); - } - } - public static void removeRHExtensions(ConfigPro config, Log log, String[] extensionIDs, boolean removePhysical) throws IOException, PageException, BundleException, ConverterException { ConfigAdmin admin = new ConfigAdmin(config, null); @@ -6455,8 +6449,7 @@ private BundleDefinition[] removeExtensionX(ConfigPro config, String extensionID String version = Caster.toString(el.get(KeyConstants._version, null), null); Resource file = RHExtension.getMetaDataFile(config, id, version); if (file.isFile()) file.delete(); - file = RHExtension.getExtensionInstalledFile(config, id, version, false); - if (file.isFile()) file.delete(); + RHExtension.removeExtensionInstalledFile(config, id, version); return bundles; } diff --git a/core/src/main/java/lucee/runtime/config/ConfigFactory.java b/core/src/main/java/lucee/runtime/config/ConfigFactory.java index f07d8e2fef..038364c410 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigFactory.java +++ b/core/src/main/java/lucee/runtime/config/ConfigFactory.java @@ -200,19 +200,19 @@ public static boolean isRequiredExtension(CFMLEngine engine, Resource contextDir * @throws PageException * @throws NoSuchAlgorithmException */ - static Struct loadDocument(Resource file) throws IOException, PageException { + static Struct loadDocument(Config config, Resource file) throws IOException, PageException { InputStream is = null; try { - return _loadDocument(file); + return _loadDocument(config, file); } finally { IOUtil.close(is); } } - static Struct loadDocumentCreateIfFails(Resource configFile, String type) throws IOException, PageException { + static Struct loadDocumentCreateIfFails(Config config, Resource configFile, String type) throws IOException, PageException { try { - return _loadDocument(configFile); + return _loadDocument(config, configFile); } catch (Exception e) { // rename buggy config files @@ -233,7 +233,7 @@ static Struct loadDocumentCreateIfFails(Resource configFile, String type) throws configFile.delete(); } ConfigFile.createConfigFile(type, configFile); - return loadDocument(configFile); + return loadDocument(config, configFile); } } @@ -258,7 +258,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 = ConfigUtil.getAsStruct(reader.getData(), false, "cfLuceeConfiguration", "luceeConfiguration", "lucee-configuration"); + Struct root = ConfigUtil.getAsStruct(config, reader.getData(), false, "cfLuceeConfiguration", "luceeConfiguration", "lucee-configuration"); //////////////////// charset //////////////////// { @@ -741,8 +741,8 @@ else if (old instanceof InputSource) { Struct scheduler = ConfigUtil.getAsStruct("scheduler", root); // set scheduler - Resource schedulerDir = ConfigUtil.getFile(config.getRootDirectory(), ConfigFactoryImpl.getAttr(scheduler, "directory"), "scheduler", configDir, FileUtil.TYPE_DIR, - ResourceUtil.LEVEL_GRAND_PARENT_FILE, config); + Resource schedulerDir = ConfigUtil.getFile(config.getRootDirectory(), ConfigFactoryImpl.getAttr(null, scheduler, "directory"), "scheduler", configDir, + FileUtil.TYPE_DIR, ResourceUtil.LEVEL_GRAND_PARENT_FILE, config); Resource schedulerFile = schedulerDir.getRealResource("scheduler.xml"); if (schedulerFile.isFile()) { Struct schedulerRoot = new XMLConfigReader(schedulerFile, true, new ReadRule(), new NameRule()).getData(); @@ -971,22 +971,22 @@ private static void copy(String fromKey, String toKey, Struct from, Struct to) { if (val != null) to.setEL(KeyImpl.init(toKey), val); } - static String createVirtual(Struct data) { - String str = ConfigFactoryImpl.getAttr(data, "virtual"); + static String createVirtual(Config config, Struct data) { + String str = ConfigFactoryImpl.getAttr(config, data, "virtual"); if (!StringUtil.isEmpty(str)) return str; - return createVirtual(ConfigFactoryImpl.getAttr(data, "physical"), ConfigFactoryImpl.getAttr(data, "archive")); + return createVirtual(ConfigFactoryImpl.getAttr(config, data, "physical"), ConfigFactoryImpl.getAttr(config, data, "archive")); } private static String createVirtual(String physical, String archive) { return "/" + MD5.getDigestAsString(physical + ":" + archive, ""); } - private static Struct _loadDocument(Resource res) throws IOException, PageException { + private static Struct _loadDocument(Config config, Resource res) throws IOException, PageException { String name = res.getName(); // That step is not necessary anymore TODO remove if (StringUtil.endsWithIgnoreCase(name, ".xml.cfm") || StringUtil.endsWithIgnoreCase(name, ".xml")) { try { - return ConfigUtil.getAsStruct(new XMLConfigReader(res, true, new ReadRule(), new NameRule()).getData(), false, "cfLuceeConfiguration", "luceeConfiguration", + return ConfigUtil.getAsStruct(config, new XMLConfigReader(res, true, new ReadRule(), new NameRule()).getData(), false, "cfLuceeConfiguration", "luceeConfiguration", "lucee-configuration"); } catch (SAXException e) { @@ -1001,8 +1001,8 @@ private static Struct _loadDocument(Resource res) throws IOException, PageExcept Resource dir = res.getParentResource(); Resource ls = dir.getRealResource("lucee-server.xml"); Resource lw = dir.getRealResource("lucee-web.xml.cfm"); - if (ls.isFile()) return _loadDocument(ls); - else if (lw.isFile()) return _loadDocument(lw); + if (ls.isFile()) return _loadDocument(config, ls); + else if (lw.isFile()) return _loadDocument(config, lw); else throw fnfe; } /* diff --git a/core/src/main/java/lucee/runtime/config/ConfigFactoryImpl.java b/core/src/main/java/lucee/runtime/config/ConfigFactoryImpl.java index e7888b0713..110d8c2766 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigFactoryImpl.java +++ b/core/src/main/java/lucee/runtime/config/ConfigFactoryImpl.java @@ -42,6 +42,7 @@ import java.util.Set; import java.util.TimeZone; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -323,7 +324,7 @@ public static ConfigServerImpl newInstanceServer(CFMLEngineImpl engine, Map 0) { Struct defaultProvider = Caster.toStruct(defaultProviders.getE(defaultProviders.size())); - ClassDefinition defProv = getClassDefinition(defaultProvider, "", config.getIdentification()); + ClassDefinition defProv = getClassDefinition(config, defaultProvider, "", config.getIdentification()); - String strDefaultProviderComponent = getAttr(defaultProvider, "component"); - if (StringUtil.isEmpty(strDefaultProviderComponent)) strDefaultProviderComponent = getAttr(defaultProvider, "class"); + String strDefaultProviderComponent = getAttr(config, defaultProvider, "component"); + if (StringUtil.isEmpty(strDefaultProviderComponent)) strDefaultProviderComponent = getAttr(config, defaultProvider, "class"); // class if (defProv.hasClass()) { @@ -570,11 +571,11 @@ public static void loadResourceProvider(ConfigImpl config, Struct root) { provider = Caster.toStruct(pit.next(), null); if (provider == null) continue; try { - prov = getClassDefinition(provider, "", config.getIdentification()); - strProviderCFC = getAttr(provider, "component"); - if (StringUtil.isEmpty(strProviderCFC)) strProviderCFC = getAttr(provider, "class"); + prov = getClassDefinition(config, provider, "", config.getIdentification()); + strProviderCFC = getAttr(config, provider, "component"); + if (StringUtil.isEmpty(strProviderCFC)) strProviderCFC = getAttr(config, provider, "class"); - strProviderScheme = getAttr(provider, "scheme"); + strProviderScheme = getAttr(config, provider, "scheme"); // class if (prov.hasClass() && !StringUtil.isEmpty(strProviderScheme)) { strProviderScheme = strProviderScheme.trim().toLowerCase(); @@ -650,17 +651,17 @@ else if (!StringUtil.isEmpty(strProviderCFC) && !StringUtil.isEmpty(strProviderS } } - public static ClassDefinition getClassDefinition(Struct data, String prefix, Identification id) throws PageException { + public static ClassDefinition getClassDefinition(Config config, Struct data, String prefix, Identification id) throws PageException { String attrName; String cn; if (StringUtil.isEmpty(prefix)) { - cn = getAttr(data, "class"); + cn = getAttr(config, data, "class"); attrName = "class"; } else { if (prefix.endsWith("-")) prefix = prefix.substring(0, prefix.length() - 1); - cn = getAttr(data, prefix + "Class"); + cn = getAttr(config, data, prefix + "Class"); attrName = prefix + "Class"; } @@ -696,7 +697,7 @@ public static HashMap> loadCacheHandler(ConfigImpl c handler = Caster.toStruct(entry.getValue(), null); if (handler == null) continue; - cd = getClassDefinition(handler, "", config.getIdentification()); + cd = getClassDefinition(config, handler, "", config.getIdentification()); strId = entry.getKey().getString(); if (cd.hasClass() && !StringUtil.isEmpty(strId)) { strId = strId.trim().toLowerCase(); @@ -735,7 +736,7 @@ private static void addCacheHandler(HashMap> cacheHa public static Map loadAI(ConfigImpl config, Struct root, Map defaultValue) { try { // we only load this for the server context - Struct ai = ConfigUtil.getAsStruct(root, false, "ai"); + Struct ai = ConfigUtil.getAsStruct(config, root, false, "ai"); if (ai != null) { return _loadAI(config, ai); } @@ -760,7 +761,7 @@ public static Map _loadAI(Config config, Struct ai) { if (data == null) continue; strId = entry.getKey().getString(); if (!StringUtil.isEmpty(strId)) { - data = (Struct) ConfigUtil.replaceConfigPlaceHolders(data); + data = (Struct) ConfigUtil.replaceConfigPlaceHolders(config, data); strId = strId.trim().toLowerCase(); engines.put(strId, AIEngineFactory.getInstance(config, strId, data)); } @@ -775,7 +776,8 @@ public static Map _loadAI(Config config, Struct ai) { public static Map loadSecretProviders(ConfigImpl config, Struct root, Map defaultValue) { try { // we only load this for the server context - Struct secretProvider = ConfigUtil.getAsStruct(root, false, "secretProvider"); + // we do not give config here as first argument, to prevent a infiniti loop + Struct secretProvider = ConfigUtil.getAsStruct(null, root, false, "secretProvider"); if (secretProvider != null) { return _loadSecretProviders(config, secretProvider); } @@ -833,9 +835,9 @@ public static DumpWriterEntry[] loadDumpWriter(ConfigImpl config, Struct root, D writer = Caster.toStruct(it.next(), null); if (writer == null) continue; - cd = getClassDefinition(writer, "", config.getIdentification()); - strName = getAttr(writer, "name"); - strDefault = getAttr(writer, "default"); + cd = getClassDefinition(config, writer, "", config.getIdentification()); + strName = getAttr(config, writer, "name"); + strDefault = getAttr(config, writer, "default"); clazz = cd.getClazz(null); if (clazz != null && !StringUtil.isEmpty(strName)) { if (StringUtil.isEmpty(strDefault)) def = HTMLDumpWriter.DEFAULT_NONE; @@ -937,8 +939,8 @@ private static String dec(String str, boolean decode) { public static ConfigListener loadListener(ConfigServerImpl config, Struct root, ConfigListener defaultValue) { try { Struct listener = ConfigUtil.getAsStruct("listener", root); - ClassDefinition cd = listener != null ? getClassDefinition(listener, "", config.getIdentification()) : null; - String strArguments = getAttr(listener, "arguments"); + ClassDefinition cd = listener != null ? getClassDefinition(config, listener, "", config.getIdentification()) : null; + String strArguments = getAttr(config, listener, "arguments"); if (strArguments == null) strArguments = ""; if (cd != null && cd.hasClass()) { @@ -984,7 +986,7 @@ public static IdentificationServerImpl loadId(ConfigServerImpl config, Struct ro // API Key String apiKey = null; - String str = root != null ? getAttr(root, "apiKey") : null; + String str = root != null ? getAttr(config, root, "apiKey") : null; if (!StringUtil.isEmpty(str, true)) apiKey = str.trim(); return new IdentificationServerImpl(config, securityKey, apiKey); @@ -1007,8 +1009,8 @@ public static int loadSecurity(ConfigImpl config, Struct root) { Struct security = ConfigUtil.getAsStruct("security", root); int vu = ConfigPro.QUERY_VAR_USAGE_UNDEFINED; if (security != null) { - vu = AppListenerUtil.toVariableUsage(getAttr(security, "variableUsage"), ConfigPro.QUERY_VAR_USAGE_UNDEFINED); - if (vu == ConfigPro.QUERY_VAR_USAGE_UNDEFINED) vu = AppListenerUtil.toVariableUsage(getAttr(security, "varUsage"), ConfigPro.QUERY_VAR_USAGE_UNDEFINED); + vu = AppListenerUtil.toVariableUsage(getAttr(config, security, "variableUsage"), ConfigPro.QUERY_VAR_USAGE_UNDEFINED); + if (vu == ConfigPro.QUERY_VAR_USAGE_UNDEFINED) vu = AppListenerUtil.toVariableUsage(getAttr(config, security, "varUsage"), ConfigPro.QUERY_VAR_USAGE_UNDEFINED); } if (vu == ConfigPro.QUERY_VAR_USAGE_UNDEFINED) { vu = ConfigPro.QUERY_VAR_USAGE_IGNORE; @@ -1035,7 +1037,7 @@ private static Resource[] _loadFileAccess(Config config, Array fileAccesses) { fa = Caster.toStruct(it.next(), null); if (fa == null) continue; - path = getAttr(fa, "path"); + path = getAttr(config, fa, "path"); if (!StringUtil.isEmpty(path)) { res = config.getResource(path); if (res.isDirectory()) reses.add(res); @@ -1051,39 +1053,42 @@ private static Resource[] _loadFileAccess(Config config, Array fileAccesses) { return reses.toArray(new Resource[reses.size()]); } - private static SecurityManagerImpl _toSecurityManager(Struct el) { - SecurityManagerImpl sm = new SecurityManagerImpl(_attr(el, "setting", SecurityManager.VALUE_YES), _attr(el, "file", SecurityManager.VALUE_ALL), - _attr(el, "direct_java_access", SecurityManager.VALUE_YES), _attr(el, "mail", SecurityManager.VALUE_YES), _attr(el, "datasource", SecurityManager.VALUE_YES), - _attr(el, "mapping", SecurityManager.VALUE_YES), _attr(el, "remote", SecurityManager.VALUE_YES), _attr(el, "custom_tag", SecurityManager.VALUE_YES), - _attr(el, "cfx_setting", SecurityManager.VALUE_YES), _attr(el, "cfx_usage", SecurityManager.VALUE_YES), _attr(el, "debugging", SecurityManager.VALUE_YES), - _attr(el, "search", SecurityManager.VALUE_YES), _attr(el, "scheduled_task", SecurityManager.VALUE_YES), _attr(el, "tag_execute", SecurityManager.VALUE_YES), - _attr(el, "tag_import", SecurityManager.VALUE_YES), _attr(el, "tag_object", SecurityManager.VALUE_YES), _attr(el, "tag_registry", SecurityManager.VALUE_YES), - _attr(el, "cache", SecurityManager.VALUE_YES), _attr(el, "gateway", SecurityManager.VALUE_YES), _attr(el, "orm", SecurityManager.VALUE_YES), - _attr2(el, "access_read", SecurityManager.ACCESS_PROTECTED), _attr2(el, "access_write", SecurityManager.ACCESS_PROTECTED)); + private static SecurityManagerImpl _toSecurityManager(Config config, Struct el) { + SecurityManagerImpl sm = new SecurityManagerImpl(_attr(config, el, "setting", SecurityManager.VALUE_YES), _attr(config, el, "file", SecurityManager.VALUE_ALL), + _attr(config, el, "direct_java_access", SecurityManager.VALUE_YES), _attr(config, el, "mail", SecurityManager.VALUE_YES), + _attr(config, el, "datasource", SecurityManager.VALUE_YES), _attr(config, el, "mapping", SecurityManager.VALUE_YES), + _attr(config, el, "remote", SecurityManager.VALUE_YES), _attr(config, el, "custom_tag", SecurityManager.VALUE_YES), + _attr(config, el, "cfx_setting", SecurityManager.VALUE_YES), _attr(config, el, "cfx_usage", SecurityManager.VALUE_YES), + _attr(config, el, "debugging", SecurityManager.VALUE_YES), _attr(config, el, "search", SecurityManager.VALUE_YES), + _attr(config, el, "scheduled_task", SecurityManager.VALUE_YES), _attr(config, el, "tag_execute", SecurityManager.VALUE_YES), + _attr(config, el, "tag_import", SecurityManager.VALUE_YES), _attr(config, el, "tag_object", SecurityManager.VALUE_YES), + _attr(config, el, "tag_registry", SecurityManager.VALUE_YES), _attr(config, el, "cache", SecurityManager.VALUE_YES), + _attr(config, el, "gateway", SecurityManager.VALUE_YES), _attr(config, el, "orm", SecurityManager.VALUE_YES), + _attr2(config, el, "access_read", SecurityManager.ACCESS_PROTECTED), _attr2(config, el, "access_write", SecurityManager.ACCESS_PROTECTED)); return sm; } - public static SecurityManagerImpl _toSecurityManagerSingle(Struct el) { + public static SecurityManagerImpl _toSecurityManagerSingle(Config config, Struct el) { SecurityManagerImpl sm = (SecurityManagerImpl) SecurityManagerImpl.getOpenSecurityManager(); - sm.setAccess(SecurityManager.TYPE_ACCESS_READ, _attr2(el, "access_read", SecurityManager.ACCESS_PROTECTED)); - sm.setAccess(SecurityManager.TYPE_ACCESS_WRITE, _attr2(el, "access_write", SecurityManager.ACCESS_PROTECTED)); - sm.setAccess(SecurityManager.TYPE_REMOTE, _attr(el, "remote", SecurityManager.VALUE_YES)); - sm.setAccess(SecurityManager.TYPE_FILE, _attr(el, "file", SecurityManager.VALUE_ALL)); - sm.setAccess(SecurityManager.TYPE_TAG_EXECUTE, _attr(el, "tag_execute", SecurityManager.VALUE_YES)); - sm.setAccess(SecurityManager.TYPE_TAG_IMPORT, _attr(el, "tag_import", SecurityManager.VALUE_YES)); - sm.setAccess(SecurityManager.TYPE_TAG_OBJECT, _attr(el, "tag_object", SecurityManager.VALUE_YES)); - sm.setAccess(SecurityManager.TYPE_TAG_REGISTRY, _attr(el, "tag_registry", SecurityManager.VALUE_YES)); - sm.setAccess(SecurityManager.TYPE_DIRECT_JAVA_ACCESS, _attr(el, "direct_java_access", SecurityManager.VALUE_YES)); - sm.setAccess(SecurityManager.TYPE_CFX_USAGE, _attr(el, "cfx_usage", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_ACCESS_READ, _attr2(config, el, "access_read", SecurityManager.ACCESS_PROTECTED)); + sm.setAccess(SecurityManager.TYPE_ACCESS_WRITE, _attr2(config, el, "access_write", SecurityManager.ACCESS_PROTECTED)); + sm.setAccess(SecurityManager.TYPE_REMOTE, _attr(config, el, "remote", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_FILE, _attr(config, el, "file", SecurityManager.VALUE_ALL)); + sm.setAccess(SecurityManager.TYPE_TAG_EXECUTE, _attr(config, el, "tag_execute", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_TAG_IMPORT, _attr(config, el, "tag_import", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_TAG_OBJECT, _attr(config, el, "tag_object", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_TAG_REGISTRY, _attr(config, el, "tag_registry", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_DIRECT_JAVA_ACCESS, _attr(config, el, "direct_java_access", SecurityManager.VALUE_YES)); + sm.setAccess(SecurityManager.TYPE_CFX_USAGE, _attr(config, el, "cfx_usage", SecurityManager.VALUE_YES)); return sm; } - private static short _attr(Struct el, String attr, short _default) { - return SecurityManagerImpl.toShortAccessValue(getAttr(el, attr), _default); + private static short _attr(Config config, Struct el, String attr, short _default) { + return SecurityManagerImpl.toShortAccessValue(getAttr(config, el, attr), _default); } - private static short _attr2(Struct el, String attr, short _default) { - String strAccess = getAttr(el, attr); + private static short _attr2(Config config, Struct el, String attr, short _default) { + String strAccess = getAttr(config, el, attr); if (StringUtil.isEmpty(strAccess)) return _default; strAccess = strAccess.trim().toLowerCase(); if ("open".equals(strAccess)) return SecurityManager.ACCESS_OPEN; @@ -1266,19 +1271,19 @@ public static Mapping[] loadMappings(ConfigImpl config, Struct root) { if (el == null) continue; String virtual = e.getKey().getString(); - String physical = getAttr(el, "physical"); - String archive = getAttr(el, "archive"); - String strListType = getAttr(el, "listenerType"); - if (StringUtil.isEmpty(strListType)) strListType = getAttr(el, "listener-type"); - if (StringUtil.isEmpty(strListType)) strListType = getAttr(el, "listenertype"); + String physical = getAttr(config, el, "physical"); + String archive = getAttr(config, el, "archive"); + String strListType = getAttr(config, el, "listenerType"); + if (StringUtil.isEmpty(strListType)) strListType = getAttr(config, el, "listener-type"); + if (StringUtil.isEmpty(strListType)) strListType = getAttr(config, el, "listenertype"); - String strListMode = getAttr(el, "listenerMode"); - if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(el, "listener-mode"); - if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(el, "listenermode"); + String strListMode = getAttr(config, el, "listenerMode"); + if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(config, el, "listener-mode"); + if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(config, el, "listenermode"); - boolean readonly = toBoolean(getAttr(el, "readonly"), false); - boolean hidden = toBoolean(getAttr(el, "hidden"), false); - boolean toplevel = toBoolean(getAttr(el, "toplevel"), true); + boolean readonly = toBoolean(getAttr(config, el, "readonly"), false); + boolean hidden = toBoolean(getAttr(config, el, "hidden"), false); + boolean toplevel = toBoolean(getAttr(config, el, "toplevel"), true); { if ("/lucee-server/".equalsIgnoreCase(virtual) || "/lucee-server-context/".equalsIgnoreCase(virtual)) { @@ -1315,16 +1320,16 @@ else if ("/lucee/".equalsIgnoreCase(virtual)) { // physical!=null && if ((physical != null || archive != null)) { - short insTemp = inspectTemplate(el); + short insTemp = inspectTemplate(config, el); - int insTempSlow = Caster.toIntValue(getAttr(el, "inspectTemplateIntervalSlow"), ConfigPro.INSPECT_INTERVAL_UNDEFINED); - int insTempFast = Caster.toIntValue(getAttr(el, "inspectTemplateIntervalFast"), ConfigPro.INSPECT_INTERVAL_UNDEFINED); + int insTempSlow = Caster.toIntValue(getAttr(config, el, "inspectTemplateIntervalSlow"), ConfigPro.INSPECT_INTERVAL_UNDEFINED); + int insTempFast = Caster.toIntValue(getAttr(config, el, "inspectTemplateIntervalFast"), ConfigPro.INSPECT_INTERVAL_UNDEFINED); if ("/lucee/".equalsIgnoreCase(virtual) || "/lucee".equalsIgnoreCase(virtual) || "/lucee-server/".equalsIgnoreCase(virtual) || "/lucee-server-context".equalsIgnoreCase(virtual)) insTemp = ConfigPro.INSPECT_AUTO; - String primary = getAttr(el, "primary"); + String primary = getAttr(config, el, "primary"); boolean physicalFirst = primary == null || !"archive".equalsIgnoreCase(primary); tmp = new MappingImpl(config, virtual, physical, archive, insTemp, insTempSlow, insTempFast, physicalFirst, hidden, readonly, toplevel, false, false, @@ -1380,12 +1385,12 @@ else if ("/lucee/".equalsIgnoreCase(virtual)) { return mappings.values().toArray(new Mapping[mappings.size()]); } - private static short inspectTemplate(Struct data) { + private static short inspectTemplate(Config config, Struct data) { String strInsTemp = SystemUtil.getSystemPropOrEnvVar("lucee.inspect.template", null); - if (StringUtil.isEmpty(strInsTemp, true)) strInsTemp = getAttr(data, "inspectTemplate"); - if (StringUtil.isEmpty(strInsTemp, true)) strInsTemp = getAttr(data, "inspect"); + if (StringUtil.isEmpty(strInsTemp, true)) strInsTemp = getAttr(config, data, "inspectTemplate"); + if (StringUtil.isEmpty(strInsTemp, true)) strInsTemp = getAttr(config, data, "inspect"); if (StringUtil.isEmpty(strInsTemp, true)) { - Boolean trusted = Caster.toBoolean(getAttr(data, "trusted"), null); + Boolean trusted = Caster.toBoolean(getAttr(config, data, "trusted"), null); if (trusted != null) { if (trusted.booleanValue()) return ConfigPro.INSPECT_AUTO; return ConfigPro.INSPECT_ALWAYS; @@ -1416,11 +1421,11 @@ public static lucee.runtime.rest.Mapping[] loadRestMappings(ConfigImpl config, S el = Caster.toStruct(it.next()); if (el == null) continue; - String physical = getAttr(el, "physical"); - String virtual = getAttr(el, "virtual"); - boolean readonly = toBoolean(getAttr(el, "readonly"), false); - boolean hidden = toBoolean(getAttr(el, "hidden"), false); - boolean _default = toBoolean(getAttr(el, "default"), false); + String physical = getAttr(config, el, "physical"); + String virtual = getAttr(config, el, "virtual"); + boolean readonly = toBoolean(getAttr(config, el, "readonly"), false); + boolean hidden = toBoolean(getAttr(config, el, "hidden"), false); + boolean _default = toBoolean(getAttr(config, el, "default"), false); if (physical != null) { tmp = new lucee.runtime.rest.Mapping(config, virtual, physical, hidden, readonly, _default); if (_default) hasDefault = true; @@ -1488,9 +1493,9 @@ public static Map loadLoggers(ConfigImpl config, St // appender if (forceLogAppender != null) cdAppender = config.getLogEngine().appenderClassDefintion(forceLogAppender); - else cdAppender = getClassDefinition(child, "appender", config.getIdentification()); + else cdAppender = getClassDefinition(config, child, "appender", config.getIdentification()); if (!cdAppender.hasClass()) { - tmp = StringUtil.trim(getAttr(child, "appender"), ""); + tmp = StringUtil.trim(getAttr(config, child, "appender"), ""); cdAppender = config.getLogEngine().appenderClassDefintion(tmp); } else if (!cdAppender.isBundle()) { @@ -1499,9 +1504,9 @@ else if (!cdAppender.isBundle()) { appenderArgs = toArguments(child, "appenderArguments", true, false); // layout - cdLayout = getClassDefinition(child, "layout", config.getIdentification()); + cdLayout = getClassDefinition(config, child, "layout", config.getIdentification()); if (!cdLayout.hasClass()) { - tmp = StringUtil.trim(getAttr(child, "layout"), ""); + tmp = StringUtil.trim(getAttr(config, child, "layout"), ""); cdLayout = config.getLogEngine().layoutClassDefintion(tmp); } else if (!cdLayout.isBundle()) { @@ -1509,11 +1514,11 @@ else if (!cdLayout.isBundle()) { } layoutArgs = toArguments(child, "layoutArguments", true, false); - String strLevel = getAttr(child, "level"); + String strLevel = getAttr(config, child, "level"); if (forceLogLevel != null) strLevel = forceLogLevel; - if (StringUtil.isEmpty(strLevel, true)) strLevel = getAttr(child, "logLevel"); + if (StringUtil.isEmpty(strLevel, true)) strLevel = getAttr(config, child, "logLevel"); level = LogUtil.toLevel(StringUtil.trim(strLevel, ""), Log.LEVEL_ERROR); - readOnly = Caster.toBooleanValue(getAttr(child, "readOnly"), false); + readOnly = Caster.toBooleanValue(getAttr(config, child, "readOnly"), false); // ignore when no appender/name is defined if (cdAppender.hasClass() && !StringUtil.isEmpty(name)) { existing.add(name.toLowerCase()); @@ -1586,14 +1591,14 @@ else if (!IOUtil.toString(exeLog, SystemUtil.getCharset()).equals(val)) { } // class - String strClass = getAttr(el, "class"); + String strClass = getAttr(config, el, "class"); Class clazz; if (!StringUtil.isEmpty(strClass)) { try { if ("console".equalsIgnoreCase(strClass)) clazz = ConsoleExecutionLog.class; else if ("debug".equalsIgnoreCase(strClass)) clazz = DebugExecutionLog.class; else { - ClassDefinition cd = el != null ? getClassDefinition(el, "", config.getIdentification()) : null; + ClassDefinition cd = el != null ? getClassDefinition(config, el, "", config.getIdentification()) : null; Class c = cd != null ? cd.getClazz() : null; if (c != null && (ClassUtil.newInstance(c) instanceof ExecutionLog)) { @@ -1658,7 +1663,7 @@ else if (access >= SecurityManager.VALUE_1 && access <= SecurityManager.VALUE_10 // Databases // Data Sources - Struct dataSources = ConfigUtil.getAsStruct(root, false, "dataSources"); + Struct dataSources = ConfigUtil.getAsStruct(config, root, false, "dataSources"); if (accessCount == -1) accessCount = dataSources.size(); if (dataSources.size() < accessCount) accessCount = dataSources.size(); @@ -1677,17 +1682,17 @@ else if (access >= SecurityManager.VALUE_1 && access <= SecurityManager.VALUE_10 if (dataSource.containsKey(KeyConstants._database)) { try { // do we have an id? - jdbc = config.getJDBCDriverById(getAttr(dataSource, "id"), null); + jdbc = config.getJDBCDriverById(getAttr(config, dataSource, "id"), null); if (jdbc != null && jdbc.cd != null) { cd = jdbc.cd; } else { - cd = getClassDefinition(dataSource, "", config.getIdentification()); + cd = getClassDefinition(config, dataSource, "", config.getIdentification()); } // we have no class if (!cd.hasClass()) { - jdbc = config.getJDBCDriverById(getAttr(dataSource, "type"), null); + jdbc = config.getJDBCDriverById(getAttr(config, dataSource, "type"), null); if (jdbc != null && jdbc.cd != null) { cd = jdbc.cd; } @@ -1700,15 +1705,15 @@ else if (!cd.isBundle()) { // still no bundle! if (!cd.isBundle()) cd = patchJDBCClass(config, cd); - int idle = Caster.toIntValue(getAttr(dataSource, "idleTimeout"), -1); - if (idle == -1) idle = Caster.toIntValue(getAttr(dataSource, "connectionTimeout"), -1); + int idle = Caster.toIntValue(getAttr(config, dataSource, "idleTimeout"), -1); + if (idle == -1) idle = Caster.toIntValue(getAttr(config, dataSource, "connectionTimeout"), -1); int defLive = 15; if (idle > 0) defLive = idle * 5;// for backward compatibility - String dsn = getAttr(dataSource, "connectionString"); - if (StringUtil.isEmpty(dsn, true)) dsn = getAttr(dataSource, "dsn"); - if (StringUtil.isEmpty(dsn, true)) dsn = getAttr(dataSource, "connStr"); - if (StringUtil.isEmpty(dsn, true)) dsn = getAttr(dataSource, "url"); + String dsn = getAttr(config, dataSource, "connectionString"); + if (StringUtil.isEmpty(dsn, true)) dsn = getAttr(config, dataSource, "dsn"); + if (StringUtil.isEmpty(dsn, true)) dsn = getAttr(config, dataSource, "connStr"); + if (StringUtil.isEmpty(dsn, true)) dsn = getAttr(config, dataSource, "url"); if (StringUtil.isEmpty(dsn, true)) { if (jdbc == null && cd.hasClass()) { jdbc = config.getJDBCDriverByClassName(cd.getClassName(), null); @@ -1718,21 +1723,22 @@ else if (!cd.isBundle()) { } } - String bundleName = getAttr(dataSource, "bundleName"); - String bundleVersion = getAttr(dataSource, "bundleVersion"); - - setDatasource(config, datasources, e.getKey().getString(), cd, getAttr(dataSource, "host"), getAttr(dataSource, "database"), - Caster.toIntValue(getAttr(dataSource, "port"), -1), dsn, bundleName, bundleVersion, getAttr(dataSource, "username"), - ConfigUtil.decrypt(getAttr(dataSource, "password")), null, Caster.toIntValue(getAttr(dataSource, "connectionLimit"), DEFAULT_MAX_CONNECTION), idle, - Caster.toIntValue(getAttr(dataSource, "liveTimeout"), defLive), Caster.toIntValue(getAttr(dataSource, "minIdle"), 0), - Caster.toIntValue(getAttr(dataSource, "maxIdle"), 0), Caster.toIntValue(getAttr(dataSource, "maxTotal"), 0), - 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"), - ConfigUtil.getAsStruct(dataSource, true, "custom"), getAttr(dataSource, "dbdriver"), - ParamSyntaxImpl.toParamSyntax(dataSource, ParamSyntaxImpl.DEFAULT), toBoolean(getAttr(dataSource, "literalTimestampWithTSOffset"), false), - toBoolean(getAttr(dataSource, "alwaysSetTimeout"), false), toBoolean(getAttr(dataSource, "requestExclusive"), false), - toBoolean(getAttr(dataSource, "alwaysResetConnections"), false) + String bundleName = getAttr(config, dataSource, "bundleName"); + String bundleVersion = getAttr(config, dataSource, "bundleVersion"); + + setDatasource(config, datasources, e.getKey().getString(), cd, getAttr(config, dataSource, "host"), getAttr(config, dataSource, "database"), + Caster.toIntValue(getAttr(config, dataSource, "port"), -1), dsn, bundleName, bundleVersion, getAttr(config, dataSource, "username"), + ConfigUtil.decrypt(getAttr(config, dataSource, "password")), null, + Caster.toIntValue(getAttr(config, dataSource, "connectionLimit"), DEFAULT_MAX_CONNECTION), idle, + Caster.toIntValue(getAttr(config, dataSource, "liveTimeout"), defLive), Caster.toIntValue(getAttr(config, dataSource, "minIdle"), 0), + Caster.toIntValue(getAttr(config, dataSource, "maxIdle"), 0), Caster.toIntValue(getAttr(config, dataSource, "maxTotal"), 0), + Caster.toLongValue(getAttr(config, dataSource, "metaCacheTimeout"), 60000), toBoolean(getAttr(config, dataSource, "blob"), true), + toBoolean(getAttr(config, dataSource, "clob"), true), Caster.toIntValue(getAttr(config, dataSource, "allow"), DataSource.ALLOW_ALL), + toBoolean(getAttr(config, dataSource, "validate"), false), toBoolean(getAttr(config, dataSource, "storage"), false), + getAttr(config, dataSource, "timezone"), ConfigUtil.getAsStruct(config, dataSource, true, "custom"), getAttr(config, dataSource, "dbdriver"), + ParamSyntaxImpl.toParamSyntax(dataSource, ParamSyntaxImpl.DEFAULT), toBoolean(getAttr(config, dataSource, "literalTimestampWithTSOffset"), false), + toBoolean(getAttr(config, dataSource, "alwaysSetTimeout"), false), toBoolean(getAttr(config, dataSource, "requestExclusive"), false), + toBoolean(getAttr(config, dataSource, "alwaysResetConnections"), false) ); } @@ -1806,7 +1812,7 @@ public static JDBCDriver[] loadJDBCDrivers(ConfigImpl config, Struct root) { // class definition driver.setEL(KeyConstants._class, e.getKey().getString()); - cd = getClassDefinition(driver, "", config.getIdentification()); + cd = getClassDefinition(config, driver, "", config.getIdentification()); if (StringUtil.isEmpty(cd.getClassName()) && !StringUtil.isEmpty(cd.getName())) { try { Bundle bundle = OSGiUtil.loadBundle(cd.getName(), cd.getVersion(), config.getIdentification(), null, false); @@ -1818,9 +1824,9 @@ public static JDBCDriver[] loadJDBCDrivers(ConfigImpl config, Struct root) { } } - label = getAttr(driver, "label"); - id = getAttr(driver, "id"); - connStr = getAttr(driver, "connectionString"); + label = getAttr(config, driver, "label"); + id = getAttr(config, driver, "id"); + connStr = getAttr(config, driver, "connectionString"); // check if label exists if (StringUtil.isEmpty(label)) { log(config, Log.LEVEL_INFO, "missing label for jdbc driver [" + cd.getClassName() + "]"); @@ -1862,7 +1868,7 @@ public static Map loadCacheDefintions(ConfigImpl config try { cache = Caster.toStruct(it.next()); if (cache == null) continue; - cd = getClassDefinition(cache, "", config.getIdentification()); + cd = getClassDefinition(config, cache, "", config.getIdentification()); // check if it is a bundle if (!cd.isBundle()) { @@ -1893,8 +1899,8 @@ public static Map loadCacheDefaultConnectionNames(ConfigImpl co // default cache for (int i = 0; i < ConfigPro.CACHE_TYPES_MAX.length; i++) { try { - String def = getAttr(defaultCache, "default" + StringUtil.ucFirst(ConfigPro.STRING_CACHE_TYPES_MAX[i])); - if (StringUtil.isEmpty(def, true)) def = getAttr(root, "cacheDefault" + StringUtil.ucFirst(ConfigPro.STRING_CACHE_TYPES_MAX[i])); + String def = getAttr(config, defaultCache, "default" + StringUtil.ucFirst(ConfigPro.STRING_CACHE_TYPES_MAX[i])); + if (StringUtil.isEmpty(def, true)) def = getAttr(config, root, "cacheDefault" + StringUtil.ucFirst(ConfigPro.STRING_CACHE_TYPES_MAX[i])); if (!StringUtil.isEmpty(def, true)) { names.put(ConfigPro.CACHE_TYPES_MAX[i], def.trim()); @@ -1935,14 +1941,14 @@ public static Map loadCacheCacheConnections(ConfigImpl entry = it.next(); name = entry.getKey(); data = Caster.toStruct(entry.getValue(), null); - cd = getClassDefinition(data, "", config.getIdentification()); + cd = getClassDefinition(config, data, "", config.getIdentification()); if (!cd.isBundle()) { ClassDefinition _cd = config.getCacheDefinition(cd.getClassName()); if (_cd != null) cd = _cd; } { - Struct custom = ConfigUtil.getAsStruct(data, true, "custom"); + Struct custom = ConfigUtil.getAsStruct(config, data, true, "custom"); // Workaround for old EHCache class definitions if (cd.getClassName() != null && cd.getClassName().endsWith(".EHCacheLite")) { cd = new ClassDefinitionImpl("org.lucee.extension.cache.eh.EHCache"); @@ -1955,8 +1961,8 @@ else if (cd.getClassName() != null && (cd.getClassName().endsWith(".extension.io.cache.eh.EHCache") || cd.getClassName().endsWith("lucee.runtime.cache.eh.EHCache"))) { cd = new ClassDefinitionImpl("org.lucee.extension.cache.eh.EHCache"); } - cc = new CacheConnectionImpl(config, name.getString(), cd, custom, Caster.toBooleanValue(getAttr(data, "readOnly"), false), - Caster.toBooleanValue(getAttr(data, "storage"), false)); + cc = new CacheConnectionImpl(config, name.getString(), cd, custom, Caster.toBooleanValue(getAttr(config, data, "readOnly"), false), + Caster.toBooleanValue(getAttr(config, data, "storage"), false)); if (!StringUtil.isEmpty(name)) { caches.put(name.getLowerString(), cc); } @@ -2046,9 +2052,9 @@ public static GatewayMap loadGateway(final ConfigImpl config, Struct root) { if (eConnection == null) continue; id = e.getKey().getLowerString(); - ge = new GatewayEntryImpl(id, getClassDefinition(eConnection, "", config.getIdentification()), getAttr(eConnection, "cfcPath"), - getAttr(eConnection, "listenerCFCPath"), getAttr(eConnection, "startupMode"), ConfigUtil.getAsStruct(eConnection, true, "custom"), - Caster.toBooleanValue(getAttr(eConnection, "readOnly"), false)); + ge = new GatewayEntryImpl(id, getClassDefinition(config, eConnection, "", config.getIdentification()), getAttr(config, eConnection, "cfcPath"), + getAttr(config, eConnection, "listenerCFCPath"), getAttr(config, eConnection, "startupMode"), + ConfigUtil.getAsStruct(config, eConnection, true, "custom"), Caster.toBooleanValue(getAttr(config, eConnection, "readOnly"), false)); if (!StringUtil.isEmpty(id)) { mapGateways.put(id.toLowerCase(), ge); @@ -2096,7 +2102,7 @@ private static void setDatasource(ConfigImpl config, Map dat public static Mapping[] loadCustomTagsMappings(ConfigImpl config, Struct root) { Mapping[] mappings = null; try { - Array ctMappings = ConfigUtil.getAsArray(root, true, KeyConstants._virtual, KeyConstants._physical, true, "customTagMappings", "customTagPaths"); + Array ctMappings = ConfigUtil.getAsArray(config, root, true, KeyConstants._virtual, KeyConstants._physical, true, "customTagMappings", "customTagPaths"); boolean hasDefault = false; @@ -2110,18 +2116,18 @@ public static Mapping[] loadCustomTagsMappings(ConfigImpl config, Struct root) { ctMapping = Caster.toStruct(it.next(), null); if (ctMapping == null) continue; - String virtual = createVirtual(ctMapping); - String physical = getAttr(ctMapping, "physical"); - String archive = getAttr(ctMapping, "archive"); - boolean readonly = toBoolean(getAttr(ctMapping, "readonly"), false); - boolean hidden = toBoolean(getAttr(ctMapping, "hidden"), false); + String virtual = createVirtual(config, ctMapping); + String physical = getAttr(config, ctMapping, "physical"); + String archive = getAttr(config, ctMapping, "archive"); + boolean readonly = toBoolean(getAttr(config, ctMapping, "readonly"), false); + boolean hidden = toBoolean(getAttr(config, ctMapping, "hidden"), false); if ("{lucee-web}/customtags/".equals(physical) || "{lucee-server}/customtags/".equals(physical)) continue; if ("{lucee-config}/customtags/".equals(physical)) hasDefault = true; - short inspTemp = inspectTemplate(ctMapping); - int insTempSlow = Caster.toIntValue(getAttr(ctMapping, "inspectTemplateIntervalSlow"), -1); - int insTempFast = Caster.toIntValue(getAttr(ctMapping, "inspectTemplateIntervalFast"), -1); + short inspTemp = inspectTemplate(config, ctMapping); + int insTempSlow = Caster.toIntValue(getAttr(config, ctMapping, "inspectTemplateIntervalSlow"), -1); + int insTempFast = Caster.toIntValue(getAttr(config, ctMapping, "inspectTemplateIntervalFast"), -1); - String primary = getAttr(ctMapping, "primary"); + String primary = getAttr(config, ctMapping, "primary"); boolean physicalFirst = StringUtil.isEmpty(archive, true) || !"archive".equalsIgnoreCase(primary); list.add(new MappingImpl(config, virtual, physical, archive, inspTemp, insTempSlow, insTempFast, physicalFirst, hidden, readonly, true, false, true, null, @@ -2182,11 +2188,11 @@ public static void loadTag(ConfigImpl config, Struct root, boolean doNew) { // get library directories if (fileSystem != null) { - if (StringUtil.isEmpty(strDefaultTLDDirectory)) strDefaultTLDDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "tldDirectory")); - if (StringUtil.isEmpty(strDefaultTagDirectory)) strDefaultTagDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "tagDirectory")); - if (StringUtil.isEmpty(strDefaultTLDDirectory)) strDefaultTLDDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "tldDefaultDirectory")); - if (StringUtil.isEmpty(strDefaultTagDirectory)) strDefaultTagDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "tagDefaultDirectory")); - if (StringUtil.isEmpty(strTagDirectory)) strTagDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "tagAddionalDirectory")); + if (StringUtil.isEmpty(strDefaultTLDDirectory)) strDefaultTLDDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "tldDirectory")); + if (StringUtil.isEmpty(strDefaultTagDirectory)) strDefaultTagDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "tagDirectory")); + if (StringUtil.isEmpty(strDefaultTLDDirectory)) strDefaultTLDDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "tldDefaultDirectory")); + if (StringUtil.isEmpty(strDefaultTagDirectory)) strDefaultTagDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "tagDefaultDirectory")); + if (StringUtil.isEmpty(strTagDirectory)) strTagDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "tagAddionalDirectory")); } // set default directories if necessary @@ -2258,11 +2264,11 @@ public static void loadFunctions(ConfigImpl config, Struct rootMayNull, boolean // get library directories if (fileSystem != null) { - if (StringUtil.isEmpty(strDefaultFLDDirectory)) strDefaultFLDDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "flddirectory")); - if (StringUtil.isEmpty(strDefaultFuncDirectory)) strDefaultFuncDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "functionDirectory")); - if (StringUtil.isEmpty(strDefaultFLDDirectory)) strDefaultFLDDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "fldDefaultDirectory")); - if (StringUtil.isEmpty(strDefaultFuncDirectory)) strDefaultFuncDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "functionDefaultDirectory")); - if (StringUtil.isEmpty(strFuncDirectory)) strFuncDirectory = ConfigUtil.translateOldPath(getAttr(fileSystem, "functionAddionalDirectory")); + if (StringUtil.isEmpty(strDefaultFLDDirectory)) strDefaultFLDDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "flddirectory")); + if (StringUtil.isEmpty(strDefaultFuncDirectory)) strDefaultFuncDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "functionDirectory")); + if (StringUtil.isEmpty(strDefaultFLDDirectory)) strDefaultFLDDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "fldDefaultDirectory")); + if (StringUtil.isEmpty(strDefaultFuncDirectory)) strDefaultFuncDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "functionDefaultDirectory")); + if (StringUtil.isEmpty(strFuncDirectory)) strFuncDirectory = ConfigUtil.translateOldPath(getAttr(config, fileSystem, "functionAddionalDirectory")); } // set default directories if necessary @@ -2440,8 +2446,8 @@ public static URL loadUpdate(ConfigImpl config, Struct root) { if (root != null) { ConfigServerImpl cs = (ConfigServerImpl) config; - String location = getAttr(root, "updateLocation"); - if (StringUtil.isEmpty(location, true)) location = getAttr(root, "updateSiteURL"); + String location = getAttr(config, root, "updateLocation"); + if (StringUtil.isEmpty(location, true)) location = getAttr(config, root, "updateSiteURL"); if (!StringUtil.isEmpty(location, true)) { location = location.trim(); if ("http://update.lucee.org".equals(location)) location = DEFAULT_LOCATION; @@ -2479,24 +2485,24 @@ public static RemoteClient[] loadRemoteClients(ConfigImpl config, Struct root) { if (client == null) continue; // type - String type = getAttr(client, "type"); + String type = getAttr(config, client, "type"); if (StringUtil.isEmpty(type)) type = "web"; // url - String url = getAttr(client, "url"); - String label = getAttr(client, "label"); + String url = getAttr(config, client, "url"); + String label = getAttr(config, client, "label"); if (StringUtil.isEmpty(label)) label = url; - String sUser = getAttr(client, "serverUsername"); - String sPass = ConfigUtil.decrypt(getAttr(client, "serverPassword")); - String aPass = ConfigUtil.decrypt(getAttr(client, "adminPassword")); - String aCode = ConfigUtil.decrypt(getAttr(client, "securityKey")); + String sUser = getAttr(config, client, "serverUsername"); + String sPass = ConfigUtil.decrypt(getAttr(config, client, "serverPassword")); + String aPass = ConfigUtil.decrypt(getAttr(config, client, "adminPassword")); + String aCode = ConfigUtil.decrypt(getAttr(config, client, "securityKey")); // if(aCode!=null && aCode.indexOf('-')!=-1)continue; - String usage = getAttr(client, "usage"); + String usage = getAttr(config, client, "usage"); if (usage == null) usage = ""; - String pUrl = getAttr(client, "proxyServer"); - int pPort = Caster.toIntValue(getAttr(client, "proxyPort"), -1); - String pUser = getAttr(client, "proxyUsername"); - String pPass = ConfigUtil.decrypt(getAttr(client, "proxyPassword")); + String pUrl = getAttr(config, client, "proxyServer"); + int pPort = Caster.toIntValue(getAttr(config, client, "proxyPort"), -1); + String pUser = getAttr(config, client, "proxyUsername"); + String pPass = ConfigUtil.decrypt(getAttr(config, client, "proxyPassword")); ProxyData pd = null; if (!StringUtil.isEmpty(pUrl, true)) { pd = new ProxyDataImpl(); @@ -2532,7 +2538,7 @@ public static PrintStream loadErr(ConfigImpl config, Struct root) { String err = null; err = SystemUtil.getSystemPropOrEnvVar("lucee.system.err", null); - if (StringUtil.isEmpty(err)) err = getAttr(root, "systemErr"); + if (StringUtil.isEmpty(err)) err = getAttr(config, root, "systemErr"); return toPrintStream(config, err, true); } catch (Throwable t) { @@ -2550,7 +2556,7 @@ public static PrintStream loadOut(ConfigImpl config, Struct root) { String out = null; // sys prop or env var out = SystemUtil.getSystemPropOrEnvVar("lucee.system.out", null); - if (StringUtil.isEmpty(out)) out = getAttr(root, "systemOut"); + if (StringUtil.isEmpty(out)) out = getAttr(config, root, "systemOut"); return toPrintStream(config, out, false); } @@ -2622,7 +2628,7 @@ public static TimeZone loadTimezone(ConfigImpl config, Struct root, TimeZone def // timeZone String strTimeZone = null; - strTimeZone = getAttr(root, new String[] { "timezone", "thisTimezone" }); + strTimeZone = getAttr(config, root, new String[] { "timezone", "thisTimezone" }); if (!StringUtil.isEmpty(strTimeZone)) return TimeZone.getTimeZone(strTimeZone); else { @@ -2644,7 +2650,7 @@ public static Locale loadLocale(ConfigImpl config, Struct root, Locale defaultVa if (ConfigUtil.hasAccess(config, SecurityManager.TYPE_SETTING)) { try { // locale - String strLocale = getAttr(root, new String[] { "locale", "thisLocale" }); + String strLocale = getAttr(config, root, new String[] { "locale", "thisLocale" }); if (!StringUtil.isEmpty(strLocale)) return Caster.toLocale(strLocale, defaultValue); } @@ -2659,7 +2665,7 @@ public static Locale loadLocale(ConfigImpl config, Struct root, Locale defaultVa public static ClassDefinition loadWS(ConfigImpl config, Struct root, ClassDefinition defaultValue) { try { Struct ws = ConfigUtil.getAsStruct("webservice", root); - ClassDefinition cd = ws != null ? getClassDefinition(ws, "", config.getIdentification()) : null; + ClassDefinition cd = ws != null ? getClassDefinition(config, ws, "", config.getIdentification()) : null; if (cd != null && !StringUtil.isEmpty(cd.getClassName())) { return cd; } @@ -2681,9 +2687,9 @@ public static ClassDefinition loadORMClass(ConfigImpl config, Struct root) { ClassDefinition cd = null; if (orm != null) { - cd = getClassDefinition(orm, "engine", config.getIdentification()); + cd = getClassDefinition(config, orm, "engine", config.getIdentification()); if (cd == null || cd.isClassNameEqualTo(DummyORMEngine.class.getName()) || cd.isClassNameEqualTo("lucee.runtime.orm.hibernate.HibernateORMEngine")) - cd = getClassDefinition(orm, "", config.getIdentification()); + cd = getClassDefinition(config, orm, "", config.getIdentification()); if (cd != null && (cd.isClassNameEqualTo(DummyORMEngine.class.getName()) || cd.isClassNameEqualTo("lucee.runtime.orm.hibernate.HibernateORMEngine"))) cd = null; } @@ -2720,7 +2726,7 @@ public static ORMConfiguration loadORMConfig(ConfigImpl config, Struct root, ORM public static short loadJava(ConfigImpl config, Struct root, short defaultValue) { try { - String strCompileType = getAttr(root, "compileType"); + String strCompileType = getAttr(config, root, "compileType"); if (!StringUtil.isEmpty(strCompileType)) { strCompileType = strCompileType.trim().toLowerCase(); if (strCompileType.equals("after-startup")) { @@ -2745,7 +2751,7 @@ public static JavaSettings loadJavaSettings(ConfigImpl config, Struct root, Java Resource lib = config.getLibraryDirectory(); Resource[] libs = lib.listResources(ExtensionResourceFilter.EXTENSION_JAR_NO_DIR); - Struct javasettings = ConfigUtil.getAsStruct(root, false, "javasettings"); + Struct javasettings = ConfigUtil.getAsStruct(config, root, false, "javasettings"); JavaSettings js = JavaSettingsImpl.getInstance(config, javasettings, libs); return js; @@ -2836,7 +2842,7 @@ public static Map loadStartupHook(ConfigImpl config, Struct roo if (child == null) continue; // class - ClassDefinition cd = getClassDefinition(child, "", config.getIdentification()); + ClassDefinition cd = getClassDefinition(config, child, "", config.getIdentification()); ConfigBase.Startup existing = startups.get(cd.getClassName()); if (existing != null) { @@ -2882,7 +2888,7 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va // Send partial try { - String strSendPartial = getAttr(root, "mailSendPartial"); + String strSendPartial = getAttr(config, root, "mailSendPartial"); if (!StringUtil.isEmpty(strSendPartial) && hasAccess) { config.setMailSendPartial(toBoolean(strSendPartial, false)); } @@ -2898,7 +2904,7 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va // User set try { - String strUserSet = getAttr(root, "mailUserSet"); + String strUserSet = getAttr(config, root, "mailUserSet"); if (!StringUtil.isEmpty(strUserSet) && hasAccess) { config.setUserSet(toBoolean(strUserSet, true)); } @@ -2914,7 +2920,7 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va // Spool Interval try { - String strSpoolInterval = getAttr(root, "mailSpoolInterval"); + String strSpoolInterval = getAttr(config, root, "mailSpoolInterval"); if (!StringUtil.isEmpty(strSpoolInterval) && hasAccess) { config.setMailSpoolInterval(Caster.toIntValue(strSpoolInterval, 30)); } @@ -2930,7 +2936,7 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va // Encoding try { - String strEncoding = getAttr(root, "mailDefaultEncoding"); + String strEncoding = getAttr(config, root, "mailDefaultEncoding"); if (!StringUtil.isEmpty(strEncoding, true) && hasAccess) { config.setMailDefaultEncoding(strEncoding); } @@ -2946,7 +2952,7 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va // Spool Enable try { - String strSpoolEnable = getAttr(root, "mailSpoolEnable"); + String strSpoolEnable = getAttr(config, root, "mailSpoolEnable"); if (!StringUtil.isEmpty(strSpoolEnable) && hasAccess) { config.setMailSpoolEnable(toBoolean(strSpoolEnable, true)); } @@ -2962,7 +2968,7 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va // Timeout try { - String strTimeout = getAttr(root, "mailConnectionTimeout"); + String strTimeout = getAttr(config, root, "mailConnectionTimeout"); if (!StringUtil.isEmpty(strTimeout) && hasAccess) { config.setMailTimeout(Caster.toIntValue(strTimeout, 30)); } @@ -2994,10 +3000,10 @@ public static void loadMail(ConfigImpl config, Struct root) { // does no init va if (el == null) continue; i++; servers.add(i, - new ServerImpl(Caster.toIntValue(getAttr(el, "id"), i + 1), getAttr(el, "smtp"), Caster.toIntValue(getAttr(el, "port"), 25), - getAttr(el, "username"), ConfigUtil.decrypt(getAttr(el, "password")), toLong(getAttr(el, "life"), 1000 * 60 * 5), - toLong(getAttr(el, "idle"), 1000 * 60 * 1), toBoolean(getAttr(el, "tls"), false), toBoolean(getAttr(el, "ssl"), false), - toBoolean(getAttr(el, "reuseConnection"), true), ServerImpl.TYPE_GLOBAL)); + new ServerImpl(Caster.toIntValue(getAttr(config, el, "id"), i + 1), getAttr(config, el, "smtp"), Caster.toIntValue(getAttr(config, el, "port"), 25), + getAttr(config, el, "username"), ConfigUtil.decrypt(getAttr(config, el, "password")), toLong(getAttr(config, el, "life"), 1000 * 60 * 5), + toLong(getAttr(config, el, "idle"), 1000 * 60 * 1), toBoolean(getAttr(config, el, "tls"), false), + toBoolean(getAttr(config, el, "ssl"), false), toBoolean(getAttr(config, el, "reuseConnection"), true), ServerImpl.TYPE_GLOBAL)); } catch (Throwable t) { @@ -3036,11 +3042,11 @@ public static void loadMonitors(ConfigImpl config, Struct root) { el = Caster.toStruct(it.next(), null); if (el == null) continue; - cd = getClassDefinition(el, "", config.getIdentification()); - strType = getAttr(el, "type"); - name = getAttr(el, "name"); - async = Caster.toBooleanValue(getAttr(el, "async"), false); - _log = Caster.toBooleanValue(getAttr(el, "log"), true); + cd = getClassDefinition(config, el, "", config.getIdentification()); + strType = getAttr(config, el, "type"); + name = getAttr(config, el, "name"); + async = Caster.toBooleanValue(getAttr(config, el, "async"), false); + _log = Caster.toBooleanValue(getAttr(config, el, "log"), true); if ("request".equalsIgnoreCase(strType)) type = IntervallMonitor.TYPE_REQUEST; else if ("action".equalsIgnoreCase(strType)) type = Monitor.TYPE_ACTION; @@ -3109,7 +3115,7 @@ public static ClassDefinition loadSearchClass(ConfigImpl config, S Struct search = ConfigUtil.getAsStruct("search", root); // class - ClassDefinition cd = search != null ? getClassDefinition(search, "engine", config.getIdentification()) : null; + ClassDefinition cd = search != null ? getClassDefinition(config, search, "engine", config.getIdentification()) : null; if (cd == null || !cd.hasClass() || "lucee.runtime.search.lucene.LuceneSearchEngine".equals(cd.getClassName())) { cd = new ClassDefinitionImpl(DummySearchEngine.class); } @@ -3128,7 +3134,7 @@ public static String loadSearchDir(ConfigImpl config, Struct root) { Struct search = ConfigUtil.getAsStruct("search", root); // directory - String dir = search != null ? getAttr(search, "directory") : null; + String dir = search != null ? getAttr(config, search, "directory") : null; if (StringUtil.isEmpty(dir)) { dir = "{lucee-web}/search/"; } @@ -3152,72 +3158,72 @@ public static int loadDebugOptions(ConfigImpl config, Struct root) { String[] debugOptions = StringUtil.isEmpty(strDebugOption) ? null : ListUtil.listToStringArray(strDebugOption, ','); String str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingDatabase", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowDatabase"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingDatabase"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowDatabase"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingDatabase"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_DATABASE; } else if (debugOptions != null && extractDebugOption("database", debugOptions)) options += ConfigPro.DEBUG_DATABASE; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingException", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowException"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingException"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowException"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingException"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_EXCEPTION; } else if (debugOptions != null && extractDebugOption("exception", debugOptions)) options += ConfigPro.DEBUG_EXCEPTION; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingTemplate", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowTemplate"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingTemplate"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowTemplate"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingTemplate"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_TEMPLATE; } else if (debugOptions != null && extractDebugOption("template", debugOptions)) options += ConfigPro.DEBUG_TEMPLATE; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingDump", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowDump"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingDump"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowDump"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingDump"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_DUMP; } else if (debugOptions != null && extractDebugOption("dump", debugOptions)) options += ConfigPro.DEBUG_DUMP; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingTracing", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowTracing"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowTrace"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingTracing"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowTracing"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowTrace"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingTracing"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_TRACING; } else if (debugOptions != null && extractDebugOption("tracing", debugOptions)) options += ConfigPro.DEBUG_TRACING; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingTimer", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowTimer"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingTimer"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowTimer"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingTimer"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_TIMER; } else if (debugOptions != null && extractDebugOption("timer", debugOptions)) options += ConfigPro.DEBUG_TIMER; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingImplicitAccess", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowImplicitAccess"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingImplicitAccess"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowImplicitAccess"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingImplicitAccess"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_IMPLICIT_ACCESS; } else if (debugOptions != null && extractDebugOption("implicit-access", debugOptions)) options += ConfigPro.DEBUG_IMPLICIT_ACCESS; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingQueryUsage", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingShowQueryUsage"); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingQueryUsage"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingShowQueryUsage"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingQueryUsage"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_QUERY_USAGE; } else if (debugOptions != null && extractDebugOption("queryUsage", debugOptions)) options += ConfigPro.DEBUG_QUERY_USAGE; str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.debuggingThread", null); - if (StringUtil.isEmpty(str)) str = getAttr(root, "debuggingThread"); + if (StringUtil.isEmpty(str)) str = getAttr(config, root, "debuggingThread"); if (hasAccess && !StringUtil.isEmpty(str)) { if (toBoolean(str, false)) options += ConfigPro.DEBUG_THREAD; } @@ -3261,12 +3267,12 @@ public static Map loadCFX(ConfigImpl config, Struct root) { cfxTag = Caster.toStruct(entry.getValue(), null); if (cfxTag == null) continue; - String type = getAttr(cfxTag, "type"); + String type = getAttr(config, cfxTag, "type"); if (type != null) { // Java CFX Tags if ("java".equalsIgnoreCase(type)) { String name = entry.getKey().getString(); - ClassDefinition cd = getClassDefinition(cfxTag, "", config.getIdentification()); + ClassDefinition cd = getClassDefinition(config, cfxTag, "", config.getIdentification()); if (!StringUtil.isEmpty(name) && cd.hasClass()) { map.put(name.toLowerCase(), new JavaCFXTagClass(name, cd)); } @@ -3316,9 +3322,8 @@ private static void _loadExtensionBundles(ConfigServerImpl config, Struct root) log(config, t); } - List extensions = new ArrayList(); - Set installedFiles = new HashSet<>(); - Set installedIds = new HashSet<>(); + Map extensionsConfig = new ConcurrentHashMap<>(); + { String strBundles; RHExtension rhe; @@ -3330,10 +3335,10 @@ private static void _loadExtensionBundles(ConfigServerImpl config, Struct root) while (it.hasNext()) { child = Caster.toStruct(it.next(), null); if (child == null) continue; - id = getAttr(child, KeyConstants._id); + id = getAttr(config, child, KeyConstants._id); BundleInfo[] bfsq; try { - String strRes = getAttr(child, KeyConstants._resource, KeyConstants._path, KeyConstants._url); + String strRes = getAttr(config, child, KeyConstants._resource, KeyConstants._path, KeyConstants._url); if (StringUtil.isEmpty(id) && StringUtil.isEmpty(strRes)) continue; Resource res = null; @@ -3357,9 +3362,9 @@ private static void _loadExtensionBundles(ConfigServerImpl config, Struct root) } } - rhe = RHExtension.installExtension(config, id, getAttr(child, KeyConstants._version), res, false); + rhe = RHExtension.installExtension(config, id, getAttr(config, child, KeyConstants._version), res, false); // startBundles(config, rhe, firstLoad); - extensions.add(rhe); + extensionsConfig.put(rhe.getExtensionInstalledName(), rhe); // installedFiles.add(rhe.getExtensionFile()); // installedIds.add(rhe.getId()); } @@ -3372,17 +3377,13 @@ private static void _loadExtensionBundles(ConfigServerImpl config, Struct root) } // start bundles in parallel but wait for them to finish - CountDownLatch latch = new CountDownLatch(extensions.size()); + CountDownLatch latch = new CountDownLatch(extensionsConfig.size()); ExecutorService executor = ThreadUtil.createExecutorService(); try { - for (RHExtension ext: extensions) { + for (RHExtension ext: extensionsConfig.values()) { executor.submit(() -> { try { - // Add extension info to thread-safe collections - installedFiles.add(ext.getExtensionFile()); - installedIds.add(ext.getId()); - // Call the startBundles method for each extension startBundles(config, ext, firstLoad); } @@ -3417,28 +3418,42 @@ private static void _loadExtensionBundles(ConfigServerImpl config, Struct root) // uninstall extensions no longer used Boolean cleanupExtension = Caster.toBooleanValue(SystemUtil.getSystemPropOrEnvVar("lucee.cleanup.extension", null), true); if (cleanupExtension) { - Resource[] installed = RHExtension.getExtensionInstalledDir(config).listResources(new ExtensionResourceFilter("lex")); + Map installed = RHExtension.loadExtensionInstalledFiles(config); if (installed != null) { ResetFilter filter = new ResetFilter(); try { - for (Resource r: installed) { - if (!installedFiles.contains(r)) { - // is maybe a diff version installed? + for (Resource r: installed.values()) { + + // is this extension file not in the config + if (!extensionsConfig.containsKey(r.getName())) { RHExtension ext = RHExtension.getInstance(config, r); - if (!installedIds.contains(ext.getId())) { + + RHExtension match = null; + for (RHExtension e: extensionsConfig.values()) { + if (e.getId().equals(ext.getId())) { + match = e; + break; + } + } + + // maybe it got updated and the extension file was not removed + if (match != null) { + if (deployLog != null) deployLog.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"); - ConfigAdmin._removeRHExtension(config, ext, null, filter, true); - if (deployLog != null) deployLog.info("extension", "removed extension [" + ext + "]"); + + "] in the installed folder that is in a different version in the configuraton [" + match + "], so we delete that extension file."); + RHExtension.removeExtensionInstalledFile(config, r.getName()); } + // the extension no longer configured, sowe remove it else { if (deployLog != null) deployLog.info("extension", "Found the extension [" + ext - + "] in the installed folder that is in a different version in the configuraton, so we delete that extension file."); - ConfigAdmin.deleteExtensionFile(ext, r); + + "] in the installed folder that is not present in the configuration in any version, so we will uninstall it"); + ConfigAdmin._removeRHExtension(config, ext, null, filter, true); + if (deployLog != null) deployLog.info("extension", "removed extension [" + ext + "]"); } } + } } finally { @@ -3447,7 +3462,7 @@ private static void _loadExtensionBundles(ConfigServerImpl config, Struct root) } } // set - config.setExtensions(extensions.toArray(new RHExtension[extensions.size()]), md5); + config.setExtensions(extensionsConfig.values().toArray(new RHExtension[extensionsConfig.size()]), md5); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -3500,7 +3515,7 @@ public static List loadExtensionDefinition(ConfigImpl config Map child = Caster.toStringMap(childSct, null); if (child == null) return null; - String id = getAttr(childSct, KeyConstants._id); + String id = getAttr(config, childSct, KeyConstants._id); try { return RHExtension.toExtensionDefinition(config, id, child); } @@ -3549,7 +3564,7 @@ public static List loadExtensionDefinitionSerial(ConfigImpl child = Caster.toStringMap(childSct, null); if (child == null) continue; - id = getAttr(childSct, KeyConstants._id); + id = getAttr(config, childSct, KeyConstants._id); try { extensions.add(RHExtension.toExtensionDefinition(config, id, child)); @@ -3617,7 +3632,7 @@ public static Mapping[] loadComponentMappings(ConfigImpl config, Struct root) { boolean hasSet = false; // Web Mapping - Array compMappings = ConfigUtil.getAsArray(root, true, KeyConstants._virtual, KeyConstants._physical, false, "componentMappings", "componentPaths"); + Array compMappings = ConfigUtil.getAsArray(config, root, true, KeyConstants._virtual, KeyConstants._physical, false, "componentMappings", "componentPaths"); hasSet = false; boolean hasDefault = false; if (compMappings.size() > 0) { @@ -3629,29 +3644,29 @@ public static Mapping[] loadComponentMappings(ConfigImpl config, Struct root) { cMapping = Caster.toStruct(it.next(), null); if (cMapping == null) continue; - String virtual = createVirtual(cMapping); - String physical = getAttr(cMapping, "physical"); - String archive = getAttr(cMapping, "archive"); - boolean readonly = toBoolean(getAttr(cMapping, "readonly"), false); - boolean hidden = toBoolean(getAttr(cMapping, "hidden"), false); + String virtual = createVirtual(config, cMapping); + String physical = getAttr(config, cMapping, "physical"); + String archive = getAttr(config, cMapping, "archive"); + boolean readonly = toBoolean(getAttr(config, cMapping, "readonly"), false); + boolean hidden = toBoolean(getAttr(config, cMapping, "hidden"), false); if ("{lucee-web}/components/".equals(physical) || "{lucee-server}/components/".equals(physical)) continue; if ("{lucee-config}/components/".equals(physical)) hasDefault = true; - String strListMode = getAttr(cMapping, "listenerMode"); - if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(cMapping, "listener-mode"); - if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(cMapping, "listenermode"); + String strListMode = getAttr(config, cMapping, "listenerMode"); + if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(config, cMapping, "listener-mode"); + if (StringUtil.isEmpty(strListMode)) strListMode = getAttr(config, cMapping, "listenermode"); int listMode = ConfigUtil.toListenerMode(strListMode, -1); - String strListType = getAttr(cMapping, "listenerType"); - if (StringUtil.isEmpty(strListType)) strListMode = getAttr(cMapping, "listener-type"); - if (StringUtil.isEmpty(strListType)) strListMode = getAttr(cMapping, "listenertype"); + String strListType = getAttr(config, cMapping, "listenerType"); + if (StringUtil.isEmpty(strListType)) strListMode = getAttr(config, cMapping, "listener-type"); + if (StringUtil.isEmpty(strListType)) strListMode = getAttr(config, cMapping, "listenertype"); int listType = ConfigUtil.toListenerType(strListType, -1); - short inspTemp = inspectTemplate(cMapping); - int insTempSlow = Caster.toIntValue(getAttr(cMapping, "inspectTemplateIntervalSlow"), -1); - int insTempFast = Caster.toIntValue(getAttr(cMapping, "inspectTemplateIntervalFast"), -1); + short inspTemp = inspectTemplate(config, cMapping); + int insTempSlow = Caster.toIntValue(getAttr(config, cMapping, "inspectTemplateIntervalSlow"), -1); + int insTempFast = Caster.toIntValue(getAttr(config, cMapping, "inspectTemplateIntervalFast"), -1); - String primary = getAttr(cMapping, "primary"); + String primary = getAttr(config, cMapping, "primary"); boolean physicalFirst = archive == null || !"archive".equalsIgnoreCase(primary); hasSet = true; @@ -3693,24 +3708,24 @@ public static void loadProxy(ConfigServerImpl config, Struct root) { String server = null, username = null, password = null; int port = -1; if (proxy != null && proxy.size() > 0) { - enabled = Caster.toBooleanValue(getAttr(proxy, "enabled"), true); - server = getAttr(proxy, "server"); - username = getAttr(proxy, "username"); - password = getAttr(proxy, "password"); - port = Caster.toIntValue(getAttr(proxy, "port"), -1); + enabled = Caster.toBooleanValue(getAttr(config, proxy, "enabled"), true); + server = getAttr(config, proxy, "server"); + username = getAttr(config, proxy, "username"); + password = getAttr(config, proxy, "password"); + port = Caster.toIntValue(getAttr(config, proxy, "port"), -1); } if (StringUtil.isEmpty(server, true)) { - server = getAttr(root, "updateProxyHost"); - username = getAttr(root, "updateProxyUsername"); - password = getAttr(root, "updateProxyPassword"); - port = Caster.toIntValue(getAttr(root, "updateProxyPort"), -1); + server = getAttr(config, root, "updateProxyHost"); + username = getAttr(config, root, "updateProxyUsername"); + password = getAttr(config, root, "updateProxyPassword"); + port = Caster.toIntValue(getAttr(config, root, "updateProxyPort"), -1); enabled = !StringUtil.isEmpty(server, true); } // includes/excludes - Set includes = proxy != null ? ProxyDataImpl.toStringSet(getAttr(proxy, "includes")) : null; - Set excludes = proxy != null ? ProxyDataImpl.toStringSet(getAttr(proxy, "excludes")) : null; + Set includes = proxy != null ? ProxyDataImpl.toStringSet(getAttr(config, proxy, "includes")) : null; + Set excludes = proxy != null ? ProxyDataImpl.toStringSet(getAttr(config, proxy, "excludes")) : null; if (enabled && hasAccess && !StringUtil.isEmpty(server)) { ProxyDataImpl pd = (ProxyDataImpl) ProxyDataImpl.getInstance(server, port, username, password); @@ -3732,7 +3747,7 @@ public static boolean loadError(ConfigImpl config, Struct root, boolean defaultV // status code Boolean bStausCode = Caster.toBoolean(SystemUtil.getSystemPropOrEnvVar("lucee.status.code", null), null); - if (bStausCode == null) bStausCode = Caster.toBoolean(getAttr(root, "errorStatusCode"), null); + if (bStausCode == null) bStausCode = Caster.toBoolean(getAttr(config, root, "errorStatusCode"), null); if (bStausCode != null && hasAccess) { return bStausCode.booleanValue(); @@ -3749,7 +3764,7 @@ public static Regex loadRegex(ConfigImpl config, Struct root, Regex defaultValue try { boolean hasAccess = ConfigUtil.hasAccess(config, SecurityManager.TYPE_SETTING); - String strType = getAttr(root, "regexType"); + String strType = getAttr(config, root, "regexType"); int type = StringUtil.isEmpty(strType) ? RegexFactory.TYPE_UNDEFINED : RegexFactory.toType(strType, RegexFactory.TYPE_UNDEFINED); if (hasAccess && type != RegexFactory.TYPE_UNDEFINED) { @@ -3792,37 +3807,37 @@ public static long toLong(String value, long defaultValue) { return longValue; } - public static String getAttr(Struct data, String name) { + public static String getAttr(Config config, Struct data, String name) { String v = ConfigUtil.getAsString(name, data, null); if (v == null) { return null; } if (StringUtil.isEmpty(v)) return ""; - return ConfigUtil.replaceConfigPlaceHolder(v); + return ConfigUtil.replaceConfigPlaceHolder(config, v); } - public static String getAttr(Struct data, String name, String alias) { + public static String getAttr(Config config, Struct data, String name, String alias) { String v = ConfigUtil.getAsString(name, data, null); if (v == null) v = ConfigUtil.getAsString(alias, data, null); if (v == null) return null; if (StringUtil.isEmpty(v)) return ""; - return ConfigUtil.replaceConfigPlaceHolder(v); + return ConfigUtil.replaceConfigPlaceHolder(config, v); } - public static String getAttr(Struct data, String[] names) { + public static String getAttr(Config config, Struct data, String[] names) { String v; for (String name: names) { v = ConfigUtil.getAsString(name, data, null); - if (!StringUtil.isEmpty(v)) return ConfigUtil.replaceConfigPlaceHolder(v); + if (!StringUtil.isEmpty(v)) return ConfigUtil.replaceConfigPlaceHolder(config, v); } return null; } - public static String getAttr(Struct data, lucee.runtime.type.Collection.Key... names) { + public static String getAttr(Config config, Struct data, lucee.runtime.type.Collection.Key... names) { String v; for (lucee.runtime.type.Collection.Key name: names) { v = ConfigUtil.getAsString(name, data, null); - if (!StringUtil.isEmpty(v)) return ConfigUtil.replaceConfigPlaceHolder(v); + if (!StringUtil.isEmpty(v)) return ConfigUtil.replaceConfigPlaceHolder(config, v); } return null; } diff --git a/core/src/main/java/lucee/runtime/config/ConfigImpl.java b/core/src/main/java/lucee/runtime/config/ConfigImpl.java index 2b0877f71b..ad63439457 100755 --- a/core/src/main/java/lucee/runtime/config/ConfigImpl.java +++ b/core/src/main/java/lucee/runtime/config/ConfigImpl.java @@ -67,6 +67,7 @@ import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.Md5; import lucee.commons.lang.PhysicalClassLoader; +import lucee.commons.lang.PhysicalClassLoaderFactory; import lucee.commons.lang.SerializableObject; import lucee.commons.lang.StringUtil; import lucee.commons.lang.types.RefBoolean; @@ -107,7 +108,6 @@ import lucee.runtime.exp.PageException; import lucee.runtime.exp.PageRuntimeException; 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; @@ -416,7 +416,7 @@ public abstract class ConfigImpl extends ConfigBase implements ConfigPro { private String mainLoggerName; private short compileType = -1; - private short inspectTemplate = INSPECT_AUTO; + private short inspectTemplate = -1; private int inspectTemplateAutoIntervalSlow = ConfigPro.INSPECT_INTERVAL_UNDEFINED; private int inspectTemplateAutoIntervalFast = ConfigPro.INSPECT_INTERVAL_UNDEFINED; @@ -464,7 +464,7 @@ public boolean isAllowURLRequestTimeout() { if (allowURLRequestTimeout == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isAllowURLRequestTimeout")) { if (allowURLRequestTimeout == null) { - String allowURLReqTimeout = ConfigFactoryImpl.getAttr(root, new String[] { "requestTimeoutInURL", "allowUrlRequesttimeout" }); + String allowURLReqTimeout = ConfigFactoryImpl.getAttr(this, root, new String[] { "requestTimeoutInURL", "allowUrlRequesttimeout" }); if (!StringUtil.isEmpty(allowURLReqTimeout)) { allowURLRequestTimeout = Caster.toBooleanValue(allowURLReqTimeout, false); } @@ -543,7 +543,7 @@ public short getScopeCascadingType() { synchronized (SystemUtil.createToken("ConfigImpl", "getScopeCascadingType")) { if (scopeType == null) { // Cascading - String strScopeCascadingType = ConfigFactoryImpl.getAttr(root, "scopeCascading"); + String strScopeCascadingType = ConfigFactoryImpl.getAttr(this, root, "scopeCascading"); if (!StringUtil.isEmpty(strScopeCascadingType)) { scopeType = ConfigUtil.toScopeCascading(strScopeCascadingType, Config.SCOPE_STANDARD); } @@ -625,7 +625,7 @@ public boolean allowImplicidQueryCall() { synchronized (SystemUtil.createToken("ConfigImpl", "allowImplicidQueryCall")) { if (allowImplicidQueryCall == null) { Boolean b = Caster.toBoolean(SystemUtil.getSystemPropOrEnvVar("lucee.cascade.to.resultset", null), null); - if (b == null) b = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "cascadeToResultset"), Boolean.TRUE); + if (b == null) b = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "cascadeToResultset"), Boolean.TRUE); allowImplicidQueryCall = b; } } @@ -656,7 +656,7 @@ public boolean limitEvaluation() { if (b == null) { Struct security = ConfigUtil.getAsStruct("security", root); if (security != null) { - b = Caster.toBoolean(ConfigFactoryImpl.getAttr(security, "limitEvaluation"), null); + b = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, security, "limitEvaluation"), null); } } if (b != null) limitEvaluation = b; @@ -684,7 +684,7 @@ public boolean mergeFormAndURL() { if (mergeFormAndURL == null) { synchronized (SystemUtil.createToken("ConfigImpl", "mergeFormAndURL")) { if (mergeFormAndURL == null) { - String strMergeFormAndURL = ConfigFactoryImpl.getAttr(root, "mergeUrlForm"); + String strMergeFormAndURL = ConfigFactoryImpl.getAttr(this, root, "mergeUrlForm"); if (!StringUtil.isEmpty(strMergeFormAndURL, true)) { mergeFormAndURL = Caster.toBoolean(strMergeFormAndURL, Boolean.FALSE); } @@ -711,7 +711,7 @@ public TimeSpan getApplicationTimeout() { if (applicationTimeout == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getApplicationTimeout")) { if (applicationTimeout == null) { - String str = ConfigFactoryImpl.getAttr(root, "applicationTimeout"); + String str = ConfigFactoryImpl.getAttr(this, root, "applicationTimeout"); if (!StringUtil.isEmpty(str, true)) { applicationTimeout = Caster.toTimespan(str.trim(), null); } @@ -738,7 +738,7 @@ public TimeSpan getSessionTimeout() { if (sessionTimeout == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getSessionTimeout")) { if (sessionTimeout == null) { - String str = ConfigFactoryImpl.getAttr(root, "sessionTimeout"); + String str = ConfigFactoryImpl.getAttr(this, root, "sessionTimeout"); if (!StringUtil.isEmpty(str, true)) { sessionTimeout = Caster.toTimespan(str.trim(), null); } @@ -765,7 +765,7 @@ public TimeSpan getClientTimeout() { if (clientTimeout == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getClientTimeout")) { if (clientTimeout == null) { - String str = ConfigFactoryImpl.getAttr(root, "clientTimeout"); + String str = ConfigFactoryImpl.getAttr(this, root, "clientTimeout"); if (!StringUtil.isEmpty(str, true)) { clientTimeout = Caster.toTimespan(str.trim(), null); } @@ -794,7 +794,7 @@ public TimeSpan getRequestTimeout() { if (requestTimeout == null) { TimeSpan ts = null; String reqTimeout = SystemUtil.getSystemPropOrEnvVar("lucee.requesttimeout", null); - if (StringUtil.isEmpty(reqTimeout)) reqTimeout = ConfigFactoryImpl.getAttr(root, "requesttimeout"); + if (StringUtil.isEmpty(reqTimeout)) reqTimeout = ConfigFactoryImpl.getAttr(this, root, "requesttimeout"); if (!StringUtil.isEmpty(reqTimeout)) ts = Caster.toTimespan(reqTimeout, null); if (ts != null && ts.getMillis() > 0) requestTimeout = ts; else requestTimeout = new TimeSpanImpl(0, 0, 0, 50); @@ -820,7 +820,7 @@ public boolean isClientCookies() { if (clientCookies == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isClientCookies")) { if (clientCookies == null) { - String strClientCookies = ConfigFactoryImpl.getAttr(root, "clientCookies"); + String strClientCookies = ConfigFactoryImpl.getAttr(this, root, "clientCookies"); if (!StringUtil.isEmpty(strClientCookies, true)) { clientCookies = Caster.toBoolean(strClientCookies, Boolean.TRUE); } @@ -847,7 +847,7 @@ public boolean isDevelopMode() { if (developMode == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isDevelopMode")) { if (developMode == null) { - String str = ConfigFactoryImpl.getAttr(root, "developMode"); + String str = ConfigFactoryImpl.getAttr(this, root, "developMode"); if (!StringUtil.isEmpty(str, true)) { developMode = Caster.toBoolean(str.trim(), ConfigPro.DEFAULT_DEVELOP_MODE); } @@ -874,7 +874,7 @@ public boolean isClientManagement() { if (clientManagement == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isClientManagement")) { if (clientManagement == null) { - String strClientManagement = ConfigFactoryImpl.getAttr(root, "clientManagement"); + String strClientManagement = ConfigFactoryImpl.getAttr(this, root, "clientManagement"); if (!StringUtil.isEmpty(strClientManagement)) { clientManagement = Caster.toBoolean(strClientManagement, Boolean.FALSE); } @@ -901,7 +901,7 @@ public boolean isDomainCookies() { if (domainCookies == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isDomainCookies")) { if (domainCookies == null) { - String strDomainCookies = ConfigFactoryImpl.getAttr(root, "domainCookies"); + String strDomainCookies = ConfigFactoryImpl.getAttr(this, root, "domainCookies"); if (!StringUtil.isEmpty(strDomainCookies, true)) { domainCookies = Caster.toBoolean(strDomainCookies.trim(), Boolean.FALSE); } @@ -928,7 +928,7 @@ public boolean isSessionManagement() { if (sessionManagement == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isSessionManagement")) { if (sessionManagement == null) { - String strSessionManagement = ConfigFactoryImpl.getAttr(root, "sessionManagement"); + String strSessionManagement = ConfigFactoryImpl.getAttr(this, root, "sessionManagement"); if (!StringUtil.isEmpty(strSessionManagement, true)) { sessionManagement = Caster.toBoolean(strSessionManagement, Boolean.TRUE); } @@ -1094,8 +1094,8 @@ public boolean getPSQL() { synchronized (SystemUtil.createToken("ConfigImpl", "getPSQL")) { if (psq == null) { // PSQ - String strPSQ = ConfigFactoryImpl.getAttr(root, "preserveSingleQuote"); - if (StringUtil.isEmpty(strPSQ)) strPSQ = ConfigFactoryImpl.getAttr(root, "datasourcePreserveSingleQuotes"); + String strPSQ = ConfigFactoryImpl.getAttr(this, root, "preserveSingleQuote"); + if (StringUtil.isEmpty(strPSQ)) strPSQ = ConfigFactoryImpl.getAttr(this, root, "datasourcePreserveSingleQuotes"); if (!StringUtil.isEmpty(strPSQ)) { psq = Caster.toBoolean(strPSQ, Boolean.FALSE); } @@ -1182,7 +1182,7 @@ public boolean getShowDebug() { if (showDebug == null) { // monitoring debug String str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.showDebug", null); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showDebug"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showDebug"); if (!StringUtil.isEmpty(str)) { showDebug = Caster.toBoolean(str, Boolean.FALSE); @@ -1211,11 +1211,11 @@ public boolean getShowDoc() { synchronized (SystemUtil.createToken("ConfigImpl", "getShowDoc")) { if (showDoc == null) { String str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.showDoc", null); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showReference"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "doc"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "documentation"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "reference"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showDoc"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showReference"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "doc"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "documentation"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "reference"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showDoc"); if (!StringUtil.isEmpty(str)) { showDoc = Caster.toBoolean(str, Boolean.FALSE); } @@ -1243,10 +1243,10 @@ public boolean getShowMetric() { synchronized (SystemUtil.createToken("ConfigImpl", "getShowMetric")) { if (showMetric == null) { String str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.showMetric", null); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showMetrics"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "metric"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "metrics"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showMetric"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showMetrics"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "metric"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "metrics"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showMetric"); if (!StringUtil.isEmpty(str)) { showMetric = Caster.toBoolean(str, Boolean.FALSE); } @@ -1274,9 +1274,9 @@ public boolean getShowTest() { synchronized (SystemUtil.createToken("ConfigImpl", "getShowTest")) { if (showTest == null) { String str = SystemUtil.getSystemPropOrEnvVar("lucee.monitoring.showTest", null); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showTests"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "test"); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "showTest"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showTests"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "test"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "showTest"); if (!StringUtil.isEmpty(str)) { showTest = Caster.toBoolean(str, false); } @@ -1303,7 +1303,7 @@ public boolean debugLogOutput() { if (debugLogOutput == null) { synchronized (SystemUtil.createToken("ConfigImpl", "debugLogOutput")) { if (debugLogOutput == null) { - String strDLO = ConfigFactoryImpl.getAttr(root, "debuggingLogOutput"); + String strDLO = ConfigFactoryImpl.getAttr(this, root, "debuggingLogOutput"); if (!StringUtil.isEmpty(strDLO)) { debugLogOutput = Caster.toBooleanValue(strDLO, false) ? ConfigPro.CLIENT_BOOLEAN_TRUE : ConfigPro.CLIENT_BOOLEAN_FALSE; } @@ -1438,7 +1438,7 @@ protected Password getPassword() { if (initPassword) { synchronized (SystemUtil.createToken("ConfigImpl", "getPassword")) { if (initPassword) { - Password pw = PasswordImpl.readFromStruct(root, getSalt(), false, true); + Password pw = PasswordImpl.readFromStruct(this, root, getSalt(), false, true); if (pw != null) { password = pw; } @@ -2035,7 +2035,7 @@ public Resource getTempDirectory() { if (tempDirectory == null) { try { Resource configDir = getConfigDir(); - String strTempDirectory = ConfigUtil.translateOldPath(ConfigFactoryImpl.getAttr(root, "tempDirectory")); + String strTempDirectory = ConfigUtil.translateOldPath(ConfigFactoryImpl.getAttr(this, root, "tempDirectory")); Resource cst = null; // Temp Dir @@ -2329,7 +2329,7 @@ public boolean getRestList() { synchronized (SystemUtil.createToken("ConfigImpl", "getRestList")) { if (restList == null) { Struct rest = ConfigUtil.getAsStruct("rest", root); - restList = rest != null ? Caster.toBoolean(ConfigFactoryImpl.getAttr(rest, "list"), Boolean.FALSE) : Boolean.FALSE; + restList = rest != null ? Caster.toBoolean(ConfigFactoryImpl.getAttr(this, rest, "list"), Boolean.FALSE) : Boolean.FALSE; } } } @@ -2370,7 +2370,7 @@ public short getClientType() { if (clientType == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getClientType")) { if (clientType == null) { - String str = ConfigFactoryImpl.getAttr(root, "clientType"); + String str = ConfigFactoryImpl.getAttr(this, root, "clientType"); if (!StringUtil.isEmpty(str, true)) { str = str.trim().toLowerCase(); if (str.equals("file")) clientType = Config.CLIENT_SCOPE_TYPE_FILE; @@ -2449,7 +2449,7 @@ public int getComponentDataMemberDefaultAccess() { synchronized (SystemUtil.createToken("ConfigImpl", "getComponentDataMemberDefaultAccess")) { if (componentDataMemberDefaultAccess == null) { - String strDmda = ConfigFactoryImpl.getAttr(root, "componentDataMemberAccess"); + String strDmda = ConfigFactoryImpl.getAttr(this, root, "componentDataMemberAccess"); if (!StringUtil.isEmpty(strDmda, true)) { strDmda = strDmda.toLowerCase().trim(); if (strDmda.equals("remote")) componentDataMemberDefaultAccess = Component.ACCESS_REMOTE; @@ -2490,7 +2490,7 @@ public String getComponentDumpTemplate() { if (componentDumpTemplate == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getComponentDumpTemplate")) { if (componentDumpTemplate == null) { - String strDumpRemplate = ConfigFactoryImpl.getAttr(root, "componentDumpTemplate"); + String strDumpRemplate = ConfigFactoryImpl.getAttr(this, root, "componentDumpTemplate"); if (StringUtil.isEmpty(strDumpRemplate, true)) { componentDumpTemplate = "/lucee/component-dump.cfm"; } @@ -2538,14 +2538,14 @@ public String getErrorTemplate(int statusCode) { boolean hasAccess = ConfigUtil.hasAccess(this, SecurityManager.TYPE_DEBUGGING); // 500 - String template500 = ConfigFactoryImpl.getAttr(root, "errorGeneralTemplate"); - if (StringUtil.isEmpty(template500)) template500 = ConfigFactoryImpl.getAttr(root, "generalErrorTemplate"); + String template500 = ConfigFactoryImpl.getAttr(this, root, "errorGeneralTemplate"); + if (StringUtil.isEmpty(template500)) template500 = ConfigFactoryImpl.getAttr(this, root, "generalErrorTemplate"); if (hasAccess && !StringUtil.isEmpty(template500)) tmp.put("500", template500); else tmp.put("500", "/lucee/templates/error/error." + (Constants.getCFMLTemplateExtensions()[0])); // 404 - String template404 = ConfigFactoryImpl.getAttr(root, "errorMissingTemplate"); - if (StringUtil.isEmpty(template404)) template404 = ConfigFactoryImpl.getAttr(root, "missingErrorTemplate"); + String template404 = ConfigFactoryImpl.getAttr(this, root, "errorMissingTemplate"); + if (StringUtil.isEmpty(template404)) template404 = ConfigFactoryImpl.getAttr(this, root, "missingErrorTemplate"); if (hasAccess && !StringUtil.isEmpty(template404)) tmp.put("404", template404); else tmp.put("404", "/lucee/templates/error/error." + (Constants.getCFMLTemplateExtensions()[0])); @@ -2573,7 +2573,7 @@ public short getSessionType() { synchronized (SystemUtil.createToken("ConfigImpl", "getSessionType")) { if (sessionType == null) { // Session-Type - String strSessionType = ConfigFactoryImpl.getAttr(root, "sessionType"); + String strSessionType = ConfigFactoryImpl.getAttr(this, root, "sessionType"); if (!StringUtil.isEmpty(strSessionType, true)) { sessionType = AppListenerUtil.toSessionType(strSessionType.trim(), Config.SESSION_TYPE_APPLICATION); } @@ -2630,7 +2630,7 @@ public Resource getClassDirectory() { String strDeployDirectory = null; Struct fileSystem = ConfigUtil.getAsStruct("fileSystem", root); if (fileSystem != null) { - strDeployDirectory = ConfigUtil.translateOldPath(ConfigFactoryImpl.getAttr(fileSystem, "deployDirectory")); + strDeployDirectory = ConfigUtil.translateOldPath(ConfigFactoryImpl.getAttr(this, fileSystem, "deployDirectory")); } deployDirectory = ConfigUtil.getFile(configDir, strDeployDirectory, "cfclasses", configDir, FileUtil.TYPE_DIR, ResourceUtil.LEVEL_GRAND_PARENT_FILE, this); } @@ -2677,7 +2677,7 @@ public boolean isSuppressContent() { if (suppressContent == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isSuppressContent")) { if (suppressContent == null) { - String str = ConfigFactoryImpl.getAttr(root, "suppressContent"); + String str = ConfigFactoryImpl.getAttr(this, root, "suppressContent"); if (!StringUtil.isEmpty(str, true)) { suppressContent = Caster.toBoolean(str, Boolean.FALSE); } @@ -2714,7 +2714,7 @@ public CharSet getTemplateCharSet() { synchronized (SystemUtil.createToken("ConfigImpl", "getTemplateCharSet")) { if (templateCharset == null) { String template = SystemUtil.getSystemPropOrEnvVar("lucee.template.charset", null); - if (StringUtil.isEmpty(template)) template = ConfigFactoryImpl.getAttr(root, "templateCharset"); + if (StringUtil.isEmpty(template)) template = ConfigFactoryImpl.getAttr(this, root, "templateCharset"); if (!StringUtil.isEmpty(template)) templateCharset = CharsetUtil.toCharSet(template, null); if (templateCharset == null) templateCharset = SystemUtil.getCharSet(); @@ -2747,7 +2747,7 @@ public CharSet getWebCharSet() { if (webCharset == null) { // web String web = SystemUtil.getSystemPropOrEnvVar("lucee.web.charset", null); - if (StringUtil.isEmpty(web)) web = ConfigFactoryImpl.getAttr(root, "webCharset"); + if (StringUtil.isEmpty(web)) web = ConfigFactoryImpl.getAttr(this, root, "webCharset"); if (!StringUtil.isEmpty(web)) webCharset = CharsetUtil.toCharSet(web, null); if (webCharset == null) webCharset = CharSet.UTF8; @@ -2780,7 +2780,7 @@ public CharSet getResourceCharSet() {// = SystemUtil.getCharSet() if (resourceCharset == null) { String resource = null; resource = SystemUtil.getSystemPropOrEnvVar("lucee.resource.charset", null); - if (StringUtil.isEmpty(resource)) resource = ConfigFactoryImpl.getAttr(root, "resourceCharset"); + if (StringUtil.isEmpty(resource)) resource = ConfigFactoryImpl.getAttr(this, root, "resourceCharset"); if (!StringUtil.isEmpty(resource)) resourceCharset = CharsetUtil.toCharSet(resource, null); if (resourceCharset == null) resourceCharset = SystemUtil.getCharSet(); } @@ -3057,14 +3057,14 @@ public ApplicationListener getApplicationListener() { ApplicationListener listener; String strLT = SystemUtil.getSystemPropOrEnvVar("lucee.listener.type", null); if (StringUtil.isEmpty(strLT)) strLT = SystemUtil.getSystemPropOrEnvVar("lucee.application.listener", null); - if (StringUtil.isEmpty(strLT)) strLT = ConfigFactoryImpl.getAttr(root, new String[] { "listenerType", "applicationListener" }); + if (StringUtil.isEmpty(strLT)) strLT = ConfigFactoryImpl.getAttr(this, root, new String[] { "listenerType", "applicationListener" }); listener = ConfigUtil.loadListener(strLT, null); if (listener == null) listener = new MixedAppListener(); // mode String strLM = SystemUtil.getSystemPropOrEnvVar("lucee.listener.mode", null); if (StringUtil.isEmpty(strLM)) strLM = SystemUtil.getSystemPropOrEnvVar("lucee.application.mode", null); - if (StringUtil.isEmpty(strLM)) strLM = ConfigFactoryImpl.getAttr(root, new String[] { "listenerMode", "applicationMode" }); + if (StringUtil.isEmpty(strLM)) strLM = ConfigFactoryImpl.getAttr(this, root, new String[] { "listenerMode", "applicationMode" }); int listenerMode = ConfigUtil.toListenerMode(strLM, -1); if (listenerMode == -1) listenerMode = ApplicationListener.MODE_CURRENT2ROOT; listener.setMode(listenerMode); @@ -3073,7 +3073,7 @@ public ApplicationListener getApplicationListener() { if (listener instanceof ModernAppListener) { String strSi = SystemUtil.getSystemPropOrEnvVar("lucee.listener.singleton", null); if (StringUtil.isEmpty(strSi)) strSi = SystemUtil.getSystemPropOrEnvVar("lucee.application.singleton", null); - if (StringUtil.isEmpty(strSi)) strSi = ConfigFactoryImpl.getAttr(root, new String[] { "listenerSingleton", "applicationSingleton" }); + if (StringUtil.isEmpty(strSi)) strSi = ConfigFactoryImpl.getAttr(this, root, new String[] { "listenerSingleton", "applicationSingleton" }); listener.setSingelton(Caster.toBooleanValue(strSi, false)); } applicationListener = listener; @@ -3104,7 +3104,7 @@ public int getScriptProtect() { synchronized (SystemUtil.createToken("ConfigImpl", "getScriptProtect")) { if (scriptProtect == null) { String strScriptProtect = SystemUtil.getSystemPropOrEnvVar("lucee.script.protect", null); - if (StringUtil.isEmpty(strScriptProtect)) strScriptProtect = ConfigFactoryImpl.getAttr(root, "scriptProtect"); + if (StringUtil.isEmpty(strScriptProtect)) strScriptProtect = ConfigFactoryImpl.getAttr(this, root, "scriptProtect"); if (!StringUtil.isEmpty(strScriptProtect)) { scriptProtect = AppListenerUtil.translateScriptProtect(strScriptProtect, 0); } @@ -3155,7 +3155,7 @@ public boolean getTriggerComponentDataMember() { if (triggerComponentDataMember == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getTriggerComponentDataMember")) { if (triggerComponentDataMember == null) { - triggerComponentDataMember = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "componentImplicitNotation"), Boolean.FALSE); + triggerComponentDataMember = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "componentImplicitNotation"), Boolean.FALSE); } } } @@ -3179,7 +3179,7 @@ public Resource getClientScopeDir() { synchronized (SystemUtil.createToken("ConfigImpl", "getClientScopeDir")) { if (clientScopeDir == null) { Resource configDir = getConfigDir(); - String strClientDirectory = ConfigFactoryImpl.getAttr(root, "clientDirectory"); + String strClientDirectory = ConfigFactoryImpl.getAttr(this, root, "clientDirectory"); if (!StringUtil.isEmpty(strClientDirectory, true)) { strClientDirectory = ConfigUtil.translateOldPath(strClientDirectory.trim()); clientScopeDir = ConfigUtil.getFile(configDir, strClientDirectory, "client-scope", configDir, FileUtil.TYPE_DIR, ResourceUtil.LEVEL_PARENT_FILE, this); @@ -3215,7 +3215,7 @@ public long getClientScopeDirSize() { if (clientScopeDirSize == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getClientScopeDirSize")) { if (clientScopeDirSize == null) { - String strMax = ConfigFactoryImpl.getAttr(root, "clientDirectoryMaxSize"); + String strMax = ConfigFactoryImpl.getAttr(this, root, "clientDirectoryMaxSize"); if (!StringUtil.isEmpty(strMax, true)) { clientScopeDirSize = ByteSizeParser.parseByteSizeDefinition(strMax.trim(), 1024L * 1024L * 100L); } @@ -3249,12 +3249,12 @@ protected void setSessionScopeDir(Resource sessionScopeDir) { @Override public ClassLoader getRPCClassLoader(boolean reload) throws IOException { - return PhysicalClassLoader.getRPCClassLoader(this, getJavaSettings(), reload); + return PhysicalClassLoaderFactory.getRPCClassLoader(this, getJavaSettings(), reload); } @Override public ClassLoader getRPCClassLoader(boolean reload, JavaSettings js) throws IOException { - return PhysicalClassLoader.getRPCClassLoader(this, js != null ? js : getJavaSettings(), reload); + return PhysicalClassLoaderFactory.getRPCClassLoader(this, js != null ? js : getJavaSettings(), reload); } private static final Object dclt = new SerializableObject(); @@ -3269,7 +3269,7 @@ public PhysicalClassLoader getDirectClassLoader(boolean reload) throws IOExcepti if (!dir.exists()) { ResourceUtil.createDirectoryEL(dir, true); } - directClassLoader = PhysicalClassLoader.getPhysicalClassLoader(this, dir, reload); + directClassLoader = PhysicalClassLoaderFactory.getPhysicalClassLoader(this, dir, reload); } } } @@ -3286,7 +3286,7 @@ public Resource getCacheDir() { synchronized (SystemUtil.createToken("ConfigImpl", "getCacheDir")) { if (cacheDir == null) { Resource configDir = getConfigDir(); - String strCacheDirectory = ConfigFactoryImpl.getAttr(root, "cacheDirectory"); + String strCacheDirectory = ConfigFactoryImpl.getAttr(this, root, "cacheDirectory"); if (!StringUtil.isEmpty(strCacheDirectory)) { strCacheDirectory = ConfigUtil.translateOldPath(strCacheDirectory); Resource res = ConfigUtil.getFile(configDir, strCacheDirectory, "cache", configDir, FileUtil.TYPE_DIR, ResourceUtil.LEVEL_GRAND_PARENT_FILE, this); @@ -3317,7 +3317,7 @@ public long getCacheDirSize() { if (cacheDirSize == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getCacheDirSize")) { if (cacheDirSize == null) { - String strMax = ConfigFactoryImpl.getAttr(root, "cacheDirectoryMaxSize"); + String strMax = ConfigFactoryImpl.getAttr(this, root, "cacheDirectoryMaxSize"); if (!StringUtil.isEmpty(strMax)) { cacheDirSize = ByteSizeParser.parseByteSizeDefinition(strMax, CACHE_DIR_SIZE_DEFAULT); } @@ -3405,7 +3405,7 @@ public boolean useComponentShadow() { if (useComponentShadow == null) { synchronized (SystemUtil.createToken("ConfigImpl", "useComponentShadow")) { if (useComponentShadow == null) { - useComponentShadow = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "componentUseVariablesScope"), Boolean.TRUE); + useComponentShadow = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "componentUseVariablesScope"), Boolean.TRUE); } } } @@ -3428,7 +3428,7 @@ public boolean useComponentPathCache() { if (useComponentPathCache == null) { synchronized (SystemUtil.createToken("ConfigImpl", "useComponentPathCache")) { if (useComponentPathCache == null) { - useComponentPathCache = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "componentUseCachePath"), Boolean.TRUE); + useComponentPathCache = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "componentUseCachePath"), Boolean.TRUE); } } } @@ -3452,8 +3452,8 @@ public boolean useCTPathCache() { synchronized (SystemUtil.createToken("ConfigImpl", "useCTPathCache")) { if (useCTPathCache == null) { if (ConfigUtil.hasAccess(this, SecurityManager.TYPE_CUSTOM_TAG)) { - String strDoPathcache = ConfigFactoryImpl.getAttr(root, "customTagUseCachePath"); - if (StringUtil.isEmpty(strDoPathcache, true)) strDoPathcache = ConfigFactoryImpl.getAttr(root, "customTagCachePaths"); + String strDoPathcache = ConfigFactoryImpl.getAttr(this, root, "customTagUseCachePath"); + if (StringUtil.isEmpty(strDoPathcache, true)) strDoPathcache = ConfigFactoryImpl.getAttr(this, root, "customTagCachePaths"); if (!StringUtil.isEmpty(strDoPathcache, true)) { useCTPathCache = Caster.toBooleanValue(strDoPathcache.trim(), true); } @@ -3630,8 +3630,8 @@ public boolean doLocalCustomTag() { } else { if (ConfigUtil.hasAccess(this, SecurityManager.TYPE_CUSTOM_TAG)) { - String strDoCTLocalSearch = ConfigFactoryImpl.getAttr(root, "customTagLocalSearch"); - if (StringUtil.isEmpty(strDoCTLocalSearch, true)) strDoCTLocalSearch = ConfigFactoryImpl.getAttr(root, "customTagSearchLocal"); + String strDoCTLocalSearch = ConfigFactoryImpl.getAttr(this, root, "customTagLocalSearch"); + if (StringUtil.isEmpty(strDoCTLocalSearch, true)) strDoCTLocalSearch = ConfigFactoryImpl.getAttr(this, root, "customTagSearchLocal"); if (!StringUtil.isEmpty(strDoCTLocalSearch)) { doLocalCustomTag = Caster.toBooleanValue(strDoCTLocalSearch.trim(), true); } @@ -3666,7 +3666,7 @@ public String[] getCustomTagExtensions() { } else { if (ConfigUtil.hasAccess(this, SecurityManager.TYPE_CUSTOM_TAG)) { - String strExtensions = ConfigFactoryImpl.getAttr(root, "customTagExtensions"); + String strExtensions = ConfigFactoryImpl.getAttr(this, root, "customTagExtensions"); if (!StringUtil.isEmpty(strExtensions)) { try { String[] arr = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(strExtensions, ",")); @@ -3702,7 +3702,7 @@ public boolean doComponentDeepSearch() { if (doComponentTagDeepSearch == null) { synchronized (SystemUtil.createToken("ConfigImpl", "doComponentDeepSearch")) { if (doComponentTagDeepSearch == null) { - String strDeepSearch = ConfigFactoryImpl.getAttr(root, "componentDeepSearch"); + String strDeepSearch = ConfigFactoryImpl.getAttr(this, root, "componentDeepSearch"); if (!StringUtil.isEmpty(strDeepSearch)) { doComponentTagDeepSearch = Caster.toBoolean(strDeepSearch.trim(), Boolean.FALSE); } @@ -3735,8 +3735,8 @@ public boolean doCustomTagDeepSearch() { } else { if (ConfigUtil.hasAccess(this, SecurityManager.TYPE_CUSTOM_TAG)) { - String strDoCTDeepSearch = ConfigFactoryImpl.getAttr(root, "customTagDeepSearch"); - if (StringUtil.isEmpty(strDoCTDeepSearch, true)) strDoCTDeepSearch = ConfigFactoryImpl.getAttr(root, "customTagSearchSubdirectories"); + String strDoCTDeepSearch = ConfigFactoryImpl.getAttr(this, root, "customTagDeepSearch"); + if (StringUtil.isEmpty(strDoCTDeepSearch, true)) strDoCTDeepSearch = ConfigFactoryImpl.getAttr(this, root, "customTagSearchSubdirectories"); if (!StringUtil.isEmpty(strDoCTDeepSearch)) { doCustomTagDeepSearch = Caster.toBooleanValue(strDoCTDeepSearch.trim(), false); } @@ -3769,7 +3769,7 @@ public double getVersion() { synchronized (SystemUtil.createToken("ConfigImpl", "getVersion")) { if (version == null) { try { - String strVersion = ConfigFactoryImpl.getAttr(root, "version"); + String strVersion = ConfigFactoryImpl.getAttr(this, root, "version"); version = Caster.toDoubleValue(strVersion, DEFAULT_VERSION); } catch (Throwable t) { @@ -3800,7 +3800,7 @@ public boolean closeConnection() { if (closeConnection == null) { synchronized (SystemUtil.createToken("ConfigImpl", "closeConnection")) { if (closeConnection == null) { - String str = ConfigFactoryImpl.getAttr(root, "closeConnection"); + String str = ConfigFactoryImpl.getAttr(this, root, "closeConnection"); if (!StringUtil.isEmpty(str)) { closeConnection = Caster.toBoolean(str, Boolean.FALSE); } @@ -3827,7 +3827,7 @@ public boolean contentLength() { if (contentLength == null) { synchronized (SystemUtil.createToken("ConfigImpl", "contentLength")) { if (contentLength == null) { - String str = ConfigFactoryImpl.getAttr(root, "contentLength"); + String str = ConfigFactoryImpl.getAttr(this, root, "contentLength"); if (!StringUtil.isEmpty(str)) { contentLength = Caster.toBoolean(str, Boolean.TRUE); } @@ -3856,7 +3856,7 @@ public boolean allowCompression() { if (allowCompression == null) { String str = SystemUtil.getSystemPropOrEnvVar("lucee.allow.compression", null); if (StringUtil.isEmpty(str)) { - str = ConfigFactoryImpl.getAttr(root, "allowCompression"); + str = ConfigFactoryImpl.getAttr(this, root, "allowCompression"); } if (!StringUtil.isEmpty(str)) { allowCompression = Caster.toBoolean(str, ConfigImpl.DEFAULT_ALLOW_COMPRESSION); @@ -3914,7 +3914,7 @@ public boolean isShowVersion() { if (showVersion == null) { synchronized (SystemUtil.createToken("ConfigImpl", "isShowVersion")) { if (showVersion == null) { - String str = ConfigFactoryImpl.getAttr(root, "showVersion"); + String str = ConfigFactoryImpl.getAttr(this, root, "showVersion"); if (!StringUtil.isEmpty(str)) { showVersion = Caster.toBoolean(str, Boolean.FALSE); } @@ -3976,7 +3976,7 @@ public int getRemoteClientMaxThreads() { synchronized (SystemUtil.createToken("ConfigImpl", "getRemoteClientMaxThreads")) { if (remoteClientMaxThreads == null) { Struct _clients = ConfigUtil.getAsStruct("remoteClients", root); - remoteClientMaxThreads = Caster.toInteger(ConfigFactoryImpl.getAttr(_clients, "maxThreads"), 20); + remoteClientMaxThreads = Caster.toInteger(ConfigFactoryImpl.getAttr(this, _clients, "maxThreads"), 20); } } } @@ -4003,7 +4003,7 @@ public Resource getRemoteClientDirectory() { String strDir = SystemUtil.getSystemPropOrEnvVar("lucee.task.directory", null); if (StringUtil.isEmpty(strDir)) { Struct _clients = ConfigUtil.getAsStruct("remoteClients", root); - strDir = _clients != null ? ConfigFactoryImpl.getAttr(_clients, "directory") : null; + strDir = _clients != null ? ConfigFactoryImpl.getAttr(this, _clients, "directory") : null; } remoteClientDirectory = ConfigUtil.getFile(getRootDirectory(), strDir, "client-task", getConfigDir(), FileUtil.TYPE_DIR, ResourceUtil.LEVEL_GRAND_PARENT_FILE, this); @@ -4054,8 +4054,8 @@ public int getLocalMode() { if (localMode == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getLocalMode")) { if (localMode == null) { - String strLocalMode = ConfigFactoryImpl.getAttr(root, "localMode"); - if (StringUtil.isEmpty(strLocalMode)) strLocalMode = ConfigFactoryImpl.getAttr(root, "localScopeMode"); + String strLocalMode = ConfigFactoryImpl.getAttr(this, root, "localMode"); + if (StringUtil.isEmpty(strLocalMode)) strLocalMode = ConfigFactoryImpl.getAttr(this, root, "localScopeMode"); if (!StringUtil.isEmpty(strLocalMode, true)) { localMode = AppListenerUtil.toLocalMode(strLocalMode, Undefined.MODE_LOCAL_OR_ARGUMENTS_ONLY_WHEN_EXISTS); } @@ -4174,7 +4174,7 @@ public boolean allowRealPath() { if (allowRealPath == null) { Struct fileSystem = ConfigUtil.getAsStruct("fileSystem", root); if (fileSystem != null) { - String strAllowRealPath = ConfigFactoryImpl.getAttr(fileSystem, "allowRealpath"); + String strAllowRealPath = ConfigFactoryImpl.getAttr(this, fileSystem, "allowRealpath"); if (!StringUtil.isEmpty(strAllowRealPath, true)) { allowRealPath = Caster.toBoolean(strAllowRealPath.trim(), Boolean.TRUE); } @@ -4221,7 +4221,7 @@ public Struct getRemoteClientUsage() { synchronized (SystemUtil.createToken("ConfigImpl", "getRemoteClientUsage")) { if (remoteClientUsage == null) { Struct _clients = ConfigUtil.getAsStruct("remoteClients", root); - Struct sct = ConfigUtil.getAsStruct(_clients, true, "usage");// config.setRemoteClientUsage(toStruct(strUsage)); + Struct sct = ConfigUtil.getAsStruct(null, _clients, true, "usage");// config.setRemoteClientUsage(toStruct(strUsage)); if (sct == null) remoteClientUsage = new StructImpl(); else remoteClientUsage = sct; @@ -4249,8 +4249,8 @@ public Class getAdminSyncClass() { synchronized (SystemUtil.createToken("ConfigImpl", "getAdminSyncClass")) { if (adminSyncClass == null) { try { - ClassDefinition asc = ConfigFactoryImpl.getClassDefinition(root, "adminSync", getIdentification()); - if (!asc.hasClass()) asc = ConfigFactoryImpl.getClassDefinition(root, "adminSynchronisation", getIdentification()); + ClassDefinition asc = ConfigFactoryImpl.getClassDefinition(this, root, "adminSync", getIdentification()); + if (!asc.hasClass()) asc = ConfigFactoryImpl.getClassDefinition(this, root, "adminSynchronisation", getIdentification()); if (asc.hasClass()) { @@ -4394,7 +4394,7 @@ public short getInspectTemplate() { if (inspectTemplate == -1) { synchronized (SystemUtil.createToken("ConfigImpl", "getInspectTemplate")) { if (inspectTemplate == -1) { - String strInspectTemplate = ConfigFactoryImpl.getAttr(root, "inspectTemplate"); + String strInspectTemplate = ConfigFactoryImpl.getAttr(this, root, "inspectTemplate"); if (!StringUtil.isEmpty(strInspectTemplate, true)) { inspectTemplate = ConfigUtil.inspectTemplate(strInspectTemplate, ConfigPro.INSPECT_AUTO); } @@ -4423,7 +4423,7 @@ public boolean getTypeChecking() { if (typeChecking == null) { Boolean b = Caster.toBoolean(SystemUtil.getSystemPropOrEnvVar("lucee.type.checking", null), null); if (b == null) b = Caster.toBoolean(SystemUtil.getSystemPropOrEnvVar("lucee.udf.type.checking", null), null); - if (b == null) b = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, new String[] { "typeChecking", "UDFTypeChecking" }), Boolean.TRUE); + if (b == null) b = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, new String[] { "typeChecking", "UDFTypeChecking" }), Boolean.TRUE); typeChecking = b; } } @@ -4447,9 +4447,9 @@ public int getInspectTemplateAutoInterval(boolean slow) { if (inspectTemplateAutoIntervalSlow == ConfigPro.INSPECT_INTERVAL_UNDEFINED) { synchronized (SystemUtil.createToken("ConfigImpl", "getInspectTemplateAutoInterval")) { if (inspectTemplateAutoIntervalSlow == ConfigPro.INSPECT_INTERVAL_UNDEFINED) { - inspectTemplateAutoIntervalFast = Caster.toIntValue(ConfigFactoryImpl.getAttr(root, "inspectTemplateIntervalFast"), ConfigPro.INSPECT_INTERVAL_FAST); + inspectTemplateAutoIntervalFast = Caster.toIntValue(ConfigFactoryImpl.getAttr(this, root, "inspectTemplateIntervalFast"), ConfigPro.INSPECT_INTERVAL_FAST); if (inspectTemplateAutoIntervalFast <= 0) inspectTemplateAutoIntervalFast = ConfigPro.INSPECT_INTERVAL_FAST; - inspectTemplateAutoIntervalSlow = Caster.toIntValue(ConfigFactoryImpl.getAttr(root, "inspectTemplateIntervalSlow"), ConfigPro.INSPECT_INTERVAL_SLOW); + inspectTemplateAutoIntervalSlow = Caster.toIntValue(ConfigFactoryImpl.getAttr(this, root, "inspectTemplateIntervalSlow"), ConfigPro.INSPECT_INTERVAL_SLOW); if (inspectTemplateAutoIntervalSlow <= 0) inspectTemplateAutoIntervalSlow = ConfigPro.INSPECT_INTERVAL_SLOW; } } @@ -4601,7 +4601,7 @@ public boolean getExecutionLogEnabled() { synchronized (SystemUtil.createToken("ConfigImpl", "getExecutionLogEnabled")) { if (executionLogEnabled == null) { Struct sct = ConfigUtil.getAsStruct("executionLog", root); - executionLogEnabled = Caster.toBoolean(ConfigFactoryImpl.getAttr(sct, "enabled"), Boolean.FALSE); + executionLogEnabled = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, sct, "enabled"), Boolean.FALSE); } } } @@ -4750,7 +4750,7 @@ public ConfigImpl resetORMConfig() { private Map> udfCache = new ConcurrentHashMap>(); @Override - public CIPage getCachedPage(PageContext pc, String pathWithCFC) throws TemplateException { + public CIPage getCachedPage(PageContext pc, String pathWithCFC) throws PageException { return componentPathCache.getPage(pc, pathWithCFC); } @@ -4792,7 +4792,7 @@ public long getApplicationPathCacheTimeout() { if (applicationPathCacheTimeout == null) { TimeSpan ts = null; String str = SystemUtil.getSystemPropOrEnvVar("lucee.application.path.cache.timeout", null); - if (StringUtil.isEmpty(str)) str = ConfigFactoryImpl.getAttr(root, "applicationPathTimeout"); + if (StringUtil.isEmpty(str)) str = ConfigFactoryImpl.getAttr(this, root, "applicationPathTimeout"); if (!StringUtil.isEmpty(str)) ts = Caster.toTimespan(str, null); if (ts != null && ts.getMillis() > 0) applicationPathCacheTimeout = ts.getMillis(); else applicationPathCacheTimeout = 20000L; @@ -4896,7 +4896,7 @@ public ImportDefintion getComponentDefaultImport() { if (componentDefaultImport == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getComponentDefaultImport")) { if (componentDefaultImport == null) { - String strCDI = ConfigFactoryImpl.getAttr(root, "componentAutoImport"); + String strCDI = ConfigFactoryImpl.getAttr(this, root, "componentAutoImport"); if (!StringUtil.isEmpty(strCDI, true)) { this.componentDefaultImport = ImportDefintionImpl.getInstance(strCDI.trim(), null); } @@ -4930,7 +4930,7 @@ public boolean getComponentLocalSearch() { if (componentLocalSearch == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getComponentLocalSearch")) { if (componentLocalSearch == null) { - componentLocalSearch = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "componentLocalSearch"), Boolean.TRUE); + componentLocalSearch = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "componentLocalSearch"), Boolean.TRUE); } } } @@ -4979,7 +4979,7 @@ public String getClientStorage() { if (clientStorage == null) { synchronized (SystemUtil.createToken("ConfigImpl", "")) { if (clientStorage == null) { - String str = ConfigFactoryImpl.getAttr(root, "clientStorage"); + String str = ConfigFactoryImpl.getAttr(this, root, "clientStorage"); if (!StringUtil.isEmpty(str, true)) { clientStorage = str.trim(); } @@ -5006,7 +5006,7 @@ public String getSessionStorage() { if (sessionStorage == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getSessionStorage")) { if (sessionStorage == null) { - String str = ConfigFactoryImpl.getAttr(root, "sessionStorage"); + String str = ConfigFactoryImpl.getAttr(this, root, "sessionStorage"); if (!StringUtil.isEmpty(str, true)) { sessionStorage = str.trim(); } @@ -5063,10 +5063,11 @@ public DebugEntry[] getDebugEntries() { try { e = Caster.toStruct(it.next(), null); if (e == null) continue; - id = ConfigFactoryImpl.getAttr(e, "id"); + id = ConfigFactoryImpl.getAttr(this, e, "id"); list.put(id, - new DebugEntry(id, ConfigFactoryImpl.getAttr(e, "type"), ConfigFactoryImpl.getAttr(e, "iprange"), ConfigFactoryImpl.getAttr(e, "label"), - ConfigFactoryImpl.getAttr(e, "path"), ConfigFactoryImpl.getAttr(e, "fullname"), ConfigUtil.getAsStruct(e, true, "custom"))); + new DebugEntry(id, ConfigFactoryImpl.getAttr(this, e, "type"), ConfigFactoryImpl.getAttr(this, e, "iprange"), + ConfigFactoryImpl.getAttr(this, e, "label"), ConfigFactoryImpl.getAttr(this, e, "path"), + ConfigFactoryImpl.getAttr(this, e, "fullname"), ConfigUtil.getAsStruct(this, e, true, "custom"))); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); @@ -5118,8 +5119,8 @@ public int getDebugMaxRecordsLogged() { if (debugMaxRecordsLogged == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getDebugMaxRecordsLogged")) { if (debugMaxRecordsLogged == null) { - String strMax = ConfigFactoryImpl.getAttr(root, "debuggingMaxRecordsLogged"); - if (StringUtil.isEmpty(strMax)) strMax = ConfigFactoryImpl.getAttr(root, "debuggingShowMaxRecordsLogged"); + String strMax = ConfigFactoryImpl.getAttr(this, root, "debuggingMaxRecordsLogged"); + if (StringUtil.isEmpty(strMax)) strMax = ConfigFactoryImpl.getAttr(this, root, "debuggingShowMaxRecordsLogged"); if (!StringUtil.isEmpty(strMax)) { debugMaxRecordsLogged = Caster.toIntValue(strMax, 10); } @@ -5150,9 +5151,9 @@ public boolean getDotNotationUpperCase() { if (dotNotationUpperCase == null) { Boolean tmp = Caster.toBoolean(SystemUtil.getSystemPropOrEnvVar("lucee.preserve.case", null), null); if (tmp != null) tmp = !tmp; // invert: lucee.preserve.case=true means dotNotationUpperCase=false - if (tmp == null) tmp = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "dotNotationUpperCase"), null); + if (tmp == null) tmp = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "dotNotationUpperCase"), null); if (tmp == null) { - tmp = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "preserveCase"), null); + tmp = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "preserveCase"), null); if (tmp != null) tmp = !tmp; } if (tmp == null) dotNotationUpperCase = Boolean.TRUE; @@ -5186,7 +5187,7 @@ public boolean getDefaultFunctionOutput() { if (defaultFunctionOutput == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getDefaultFunctionOutput")) { if (defaultFunctionOutput == null) { - String output = ConfigFactoryImpl.getAttr(root, "defaultFunctionOutput"); + String output = ConfigFactoryImpl.getAttr(this, root, "defaultFunctionOutput"); defaultFunctionOutput = Caster.toBooleanValue(output, true); } } @@ -5214,7 +5215,7 @@ public boolean getSuppressWSBeforeArg() { if (getSuppressWSBeforeArg == null) { String suppress = SystemUtil.getSystemPropOrEnvVar("lucee.suppress.ws.before.arg", null); if (StringUtil.isEmpty(suppress, true)) { - suppress = ConfigFactoryImpl.getAttr(root, new String[] { "suppressWhitespaceBeforeArgument", "suppressWhitespaceBeforecfargument" }); + suppress = ConfigFactoryImpl.getAttr(this, root, new String[] { "suppressWhitespaceBeforeArgument", "suppressWhitespaceBeforecfargument" }); } getSuppressWSBeforeArg = Caster.toBooleanValue(suppress, true); } @@ -5249,7 +5250,7 @@ public int getMode() { if (mode == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getMode")) { if (mode == null) { - String str = ConfigFactoryImpl.getAttr(root, "mode"); + String str = ConfigFactoryImpl.getAttr(this, root, "mode"); if (!StringUtil.isEmpty(str, true)) { str = str.trim(); if ("custom".equalsIgnoreCase(str)) mode = ConfigPro.MODE_CUSTOM; @@ -5282,12 +5283,12 @@ public int getCFMLWriterType() { if (writerType == null) { String str = SystemUtil.getSystemPropOrEnvVar("lucee.cfml.writer", null); if (StringUtil.isEmpty(str)) { - str = ConfigFactoryImpl.getAttr(root, "cfmlWriter"); + str = ConfigFactoryImpl.getAttr(this, root, "cfmlWriter"); } // CB compatibility if (StringUtil.isEmpty(str, true)) { - str = ConfigFactoryImpl.getAttr(root, "whitespaceManagement"); + str = ConfigFactoryImpl.getAttr(this, root, "whitespaceManagement"); } if (!StringUtil.isEmpty(str, true)) { @@ -5329,7 +5330,7 @@ public boolean getBufferOutput() { if (bufferOutput == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getBufferOutput")) { if (bufferOutput == null) { - String str = ConfigFactoryImpl.getAttr(root, "bufferTagBodyOutput"); + String str = ConfigFactoryImpl.getAttr(this, root, "bufferTagBodyOutput"); if (!StringUtil.isEmpty(str, true)) { bufferOutput = Caster.toBoolean(str.trim(), DEFAULT_BUFFER_TAG_BODY_OUTPUT); } @@ -5384,7 +5385,7 @@ public boolean checkForChangesInConfigFile() { if (checkForChangesInConfigFile == null) { synchronized (SystemUtil.createToken("ConfigImpl", "checkForChangesInConfigFile")) { if (checkForChangesInConfigFile == null) { - String cFc = ConfigFactoryImpl.getAttr(root, "checkForChanges"); + String cFc = ConfigFactoryImpl.getAttr(this, root, "checkForChanges"); if (!StringUtil.isEmpty(cFc, true)) { checkForChangesInConfigFile = Caster.toBoolean(cFc.trim(), Boolean.FALSE); } @@ -5411,7 +5412,7 @@ public int getExternalizeStringGTE() { if (externalizeStringGTE == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getExternalizeStringGTE")) { if (externalizeStringGTE == null) { - String str = ConfigFactoryImpl.getAttr(root, "externalizeStringGte"); + String str = ConfigFactoryImpl.getAttr(this, root, "externalizeStringGte"); if (Decision.isNumber(str)) { externalizeStringGTE = Caster.toIntValue(str, -1); } @@ -5568,7 +5569,7 @@ public Boolean getHandleUnQuotedAttrValueAsString() { synchronized (SystemUtil.createToken("ConfigImpl", "getHandleUnQuotedAttrValueAsString")) { if (handleUnQuotedAttrValueAsString == null) { // Handle Unquoted Attribute Values As String - String str = ConfigFactoryImpl.getAttr(root, "handleUnquotedAttributeValueAsString"); + String str = ConfigFactoryImpl.getAttr(this, root, "handleUnquotedAttributeValueAsString"); if (str != null && Decision.isBoolean(str)) { handleUnQuotedAttrValueAsString = Caster.toBooleanValue(str, true); } @@ -5598,7 +5599,7 @@ public Object getCachedWithin(int type) { HashMap map = new HashMap(); for (int i = 0; i < ConfigPro.CACHE_TYPES.length; i++) { try { - String cw = ConfigFactoryImpl.getAttr(root, "cachedWithin" + StringUtil.ucFirst(ConfigPro.STRING_CACHE_TYPES[i])); + String cw = ConfigFactoryImpl.getAttr(this, root, "cachedWithin" + StringUtil.ucFirst(ConfigPro.STRING_CACHE_TYPES[i])); if (!StringUtil.isEmpty(cw, true)) map.put(ConfigPro.CACHE_TYPES[i], cw.trim()); } catch (Throwable t) { @@ -5645,8 +5646,8 @@ public String getSalt() { if (salt == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getSalt")) { if (salt == null) { - String salt = ConfigFactoryImpl.getAttr(root, "salt"); - if (StringUtil.isEmpty(salt, true)) salt = ConfigFactoryImpl.getAttr(root, "adminSalt"); + String salt = ConfigFactoryImpl.getAttr(this, root, "salt"); + if (StringUtil.isEmpty(salt, true)) salt = ConfigFactoryImpl.getAttr(this, root, "adminSalt"); // salt (every context need to have a salt) if (StringUtil.isEmpty(salt, true)) throw new RuntimeException("context is invalid, there is no salt!"); this.salt = salt.trim(); @@ -5773,7 +5774,7 @@ public int getQueueMax() { synchronized (SystemUtil.createToken("ConfigImpl", "getQueueMax")) { if (queueMax == -1) { Integer max = Caster.toInteger(SystemUtil.getSystemPropOrEnvVar("lucee.queue.max", null), null); - if (max == null) max = Caster.toInteger(ConfigFactoryImpl.getAttr(root, "requestQueueMax"), null); + if (max == null) max = Caster.toInteger(ConfigFactoryImpl.getAttr(this, root, "requestQueueMax"), null); queueMax = Caster.toIntValue(max, 100); } } @@ -5798,7 +5799,7 @@ public long getQueueTimeout() { synchronized (SystemUtil.createToken("ConfigImpl", "getQueueTimeout")) { if (queueTimeout == -1) { Long timeout = Caster.toLong(SystemUtil.getSystemPropOrEnvVar("lucee.queue.timeout", null), null); - if (timeout == null) timeout = Caster.toLong(ConfigFactoryImpl.getAttr(root, "requestQueueTimeout"), null); + if (timeout == null) timeout = Caster.toLong(ConfigFactoryImpl.getAttr(this, root, "requestQueueTimeout"), null); queueTimeout = Caster.toLongValue(timeout, 0L); } } @@ -5823,7 +5824,7 @@ public boolean getQueueEnable() { synchronized (SystemUtil.createToken("ConfigImpl", "getQueueEnable")) { if (queueEnable == null) { Boolean enable = Caster.toBoolean(SystemUtil.getSystemPropOrEnvVar("lucee.queue.enable", null), null); - if (enable == null) enable = Caster.toBoolean(ConfigFactoryImpl.getAttr(root, "requestQueueEnable"), null); + if (enable == null) enable = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, root, "requestQueueEnable"), null); queueEnable = Caster.toBooleanValue(enable, false); } } @@ -5847,7 +5848,7 @@ public boolean getCGIScopeReadonly() { if (cgiScopeReadonly == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getCGIScopeReadonly")) { if (cgiScopeReadonly == null) { - String strCGIReadonly = ConfigFactoryImpl.getAttr(root, "cgiScopeReadOnly"); + String strCGIReadonly = ConfigFactoryImpl.getAttr(this, root, "cgiScopeReadOnly"); if (!StringUtil.isEmpty(strCGIReadonly, true)) { cgiScopeReadonly = Caster.toBooleanValue(strCGIReadonly.trim(), true); } @@ -6000,7 +6001,7 @@ public final boolean getFullNullSupport() { if (fullNullSupport == null) { boolean fns = false; String str = SystemUtil.getSystemPropOrEnvVar("lucee.full.null.support", null); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, new String[] { "nullSupport", "fullNullSupport" }); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, new String[] { "nullSupport", "fullNullSupport" }); if (!StringUtil.isEmpty(str, true)) { fns = Caster.toBooleanValue(str, false); @@ -6044,7 +6045,7 @@ public TimeSpan getCachedAfterTimeRange() { synchronized (SystemUtil.createToken("ConfigImpl", "getCachedAfterTimeRange")) { if (initCachedAfterTimeRange) { TimeSpan ts = null; - String ca = ConfigFactoryImpl.getAttr(root, "cachedAfter"); + String ca = ConfigFactoryImpl.getAttr(this, root, "cachedAfter"); if (!StringUtil.isEmpty(ca)) ts = Caster.toTimespan(ca, null); if (ts != null && ts.getMillis() > 0) cachedAfterTimeRange = ts; initCachedAfterTimeRange = false; @@ -6116,7 +6117,7 @@ public boolean getPreciseMath() { if (preciseMath == null) { boolean pm = false; String str = SystemUtil.getSystemPropOrEnvVar("lucee.precise.math", null); - if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(root, "preciseMath"); + if (StringUtil.isEmpty(str, true)) str = ConfigFactoryImpl.getAttr(this, root, "preciseMath"); if (!StringUtil.isEmpty(str, true)) { pm = Caster.toBooleanValue(str, false); @@ -6156,7 +6157,7 @@ public String getMainLogger() { mainLoggerName = mainLogger.trim(); } else { - mainLogger = ConfigFactoryImpl.getAttr(root, "mainLogger"); + mainLogger = ConfigFactoryImpl.getAttr(this, root, "mainLogger"); if (!StringUtil.isEmpty(mainLogger, true)) { mainLoggerName = mainLogger.trim(); } @@ -6190,7 +6191,7 @@ public boolean getFormUrlAsStruct() { if (formUrlAsStruct == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getFormUrlAsStruct")) { if (formUrlAsStruct == null) { - String str = ConfigFactoryImpl.getAttr(root, "formUrlAsStruct"); + String str = ConfigFactoryImpl.getAttr(this, root, "formUrlAsStruct"); if (!StringUtil.isEmpty(str, true)) { formUrlAsStruct = Caster.toBoolean(str.trim(), Boolean.TRUE); } @@ -6218,7 +6219,7 @@ public int getReturnFormat() { if (returnFormat == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getReturnFormat")) { if (returnFormat == null) { - String strRF = ConfigFactoryImpl.getAttr(root, "returnFormat"); + String strRF = ConfigFactoryImpl.getAttr(this, root, "returnFormat"); if (!StringUtil.isEmpty(strRF, true)) returnFormat = UDFUtil.toReturnFormat(strRF, UDF.RETURN_FORMAT_WDDX); else returnFormat = UDF.RETURN_FORMAT_WDDX; } diff --git a/core/src/main/java/lucee/runtime/config/ConfigPro.java b/core/src/main/java/lucee/runtime/config/ConfigPro.java index 33e8c8dce5..8811c5d54d 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigPro.java +++ b/core/src/main/java/lucee/runtime/config/ConfigPro.java @@ -33,7 +33,6 @@ import lucee.runtime.engine.ExecutionLogFactory; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; -import lucee.runtime.exp.TemplateException; import lucee.runtime.extension.ExtensionDefintion; import lucee.runtime.extension.RHExtension; import lucee.runtime.extension.RHExtensionProvider; @@ -329,7 +328,7 @@ public interface ConfigPro extends Config { public void putCTInitFile(String key, InitFile initFile); - public CIPage getCachedPage(PageContext pc, String pathWithCFC) throws TemplateException; + public CIPage getCachedPage(PageContext pc, String pathWithCFC) throws PageException; public void putCachedPageSource(String pathWithCFC, PageSource ps); diff --git a/core/src/main/java/lucee/runtime/config/ConfigServerImpl.java b/core/src/main/java/lucee/runtime/config/ConfigServerImpl.java index c4967c4c12..490aa6ab71 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigServerImpl.java +++ b/core/src/main/java/lucee/runtime/config/ConfigServerImpl.java @@ -343,7 +343,7 @@ public SecurityManager getDefaultSecurityManager() { if (defaultSecurityManager == null) { Struct security = ConfigUtil.getAsStruct("security", root); if (security != null) { - defaultSecurityManager = ConfigFactoryImpl._toSecurityManagerSingle(security); + defaultSecurityManager = ConfigFactoryImpl._toSecurityManagerSingle(this, security); } else defaultSecurityManager = SecurityManagerImpl.getOpenSecurityManager(); } @@ -373,7 +373,7 @@ protected Password getDefaultPassword() { if (defaultPassword == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getDefaultPassword")) { if (defaultPassword == null) { - Password pw = PasswordImpl.readFromStruct(root, getSalt(), true, true); + Password pw = PasswordImpl.readFromStruct(this, root, getSalt(), true, true); if (pw != null) defaultPassword = pw; else defaultPassword = getPassword(); } @@ -424,7 +424,7 @@ public String getUpdateType() { if (updateType == null) { synchronized (SystemUtil.createToken("ConfigServerImpl", "getUpdateType")) { if (updateType == null) { - String ut = ConfigFactoryImpl.getAttr(root, "updateType"); + String ut = ConfigFactoryImpl.getAttr(this, root, "updateType"); if (StringUtil.isEmpty(ut, true)) updateType = "manual"; else updateType = ut.trim(); } @@ -500,10 +500,12 @@ public Map getLabels() { synchronized (SystemUtil.createToken("ConfigServerImpl", "getLabels")) { if (labels == null) { labels = ConfigFactoryImpl.loadLabel(null, root); + // Only create empty map if loadLabel returns null + if (labels == null) { + labels = new HashMap(); + } } } - - labels = new HashMap(); } return labels; } @@ -622,7 +624,7 @@ public boolean isMonitoringEnabled() { synchronized (SystemUtil.createToken("ConfigServerImpl", "isMonitoringEnabled")) { if (monitoringEnabled == null) { Struct parent = ConfigUtil.getAsStruct("monitoring", root); - monitoringEnabled = Caster.toBoolean(ConfigFactoryImpl.getAttr(parent, "enabled"), Boolean.FALSE); + monitoringEnabled = Caster.toBoolean(ConfigFactoryImpl.getAttr(this, parent, "enabled"), Boolean.FALSE); } } } @@ -645,7 +647,7 @@ public int getLoginDelay() { if (delay == -1) { synchronized (SystemUtil.createToken("ConfigServerImpl", "getLoginDelay")) { if (delay == -1) { - delay = Caster.toIntValue(ConfigFactoryImpl.getAttr(root, "loginDelay"), 1); + delay = Caster.toIntValue(ConfigFactoryImpl.getAttr(this, root, "loginDelay"), 1); } } } @@ -668,7 +670,7 @@ public boolean getLoginCaptcha() { if (captcha == null) { synchronized (SystemUtil.createToken("ConfigServerImpl", "getLoginCaptcha")) { if (captcha == null) { - captcha = Caster.toBooleanValue(ConfigFactoryImpl.getAttr(root, "loginCaptcha"), false); + captcha = Caster.toBooleanValue(ConfigFactoryImpl.getAttr(this, root, "loginCaptcha"), false); } } } @@ -691,7 +693,7 @@ public boolean getRememberMe() { if (rememberMe == null) { synchronized (SystemUtil.createToken("ConfigServerImpl", "getRememberMe")) { if (rememberMe == null) { - rememberMe = Caster.toBooleanValue(ConfigFactoryImpl.getAttr(root, "loginRememberme"), true); + rememberMe = Caster.toBooleanValue(ConfigFactoryImpl.getAttr(this, root, "loginRememberme"), true); } } } @@ -714,7 +716,7 @@ public boolean getDateCasterClassicStyle() { if (classicStyle == null) { synchronized (SystemUtil.createToken("ConfigServerImpl", "getDateCasterClassicStyle")) { if (classicStyle == null) { - String strClassicDateParsing = ConfigFactoryImpl.getAttr(root, "classicDateParsing"); + String strClassicDateParsing = ConfigFactoryImpl.getAttr(this, root, "classicDateParsing"); classicStyle = Caster.toBoolean(strClassicDateParsing, Boolean.FALSE); } } @@ -898,7 +900,7 @@ public String[] getAuthenticationKeys() { if (authKeys == null) { synchronized (SystemUtil.createToken("ConfigImpl", "getAuthenticationKeys")) { if (authKeys == null) { - String keyList = ConfigFactoryImpl.getAttr(root, "authKeys"); + String keyList = ConfigFactoryImpl.getAttr(this, root, "authKeys"); if (!StringUtil.isEmpty(keyList)) { String[] keys = ListUtil.trimItems(ListUtil.toStringArray(ListUtil.toListRemoveEmpty(keyList, ','))); for (int i = 0; i < keys.length; i++) { diff --git a/core/src/main/java/lucee/runtime/config/ConfigUtil.java b/core/src/main/java/lucee/runtime/config/ConfigUtil.java index c8089c5ac1..aaf3fee6aa 100755 --- a/core/src/main/java/lucee/runtime/config/ConfigUtil.java +++ b/core/src/main/java/lucee/runtime/config/ConfigUtil.java @@ -56,6 +56,7 @@ import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; +import lucee.runtime.exp.PageRuntimeException; import lucee.runtime.exp.SecurityException; import lucee.runtime.listener.ApplicationContext; import lucee.runtime.listener.ApplicationListener; @@ -69,6 +70,7 @@ import lucee.runtime.net.http.ServletContextDummy; import lucee.runtime.op.Caster; import lucee.runtime.op.Decision; +import lucee.runtime.security.SecretProviderFactory; import lucee.runtime.security.SecurityManager; import lucee.runtime.type.Array; import lucee.runtime.type.ArrayImpl; @@ -94,6 +96,11 @@ public final class ConfigUtil { * @param str * @return */ + public static CharSequence decrypt(CharSequence str) { + if (str instanceof String) return str; + return decrypt(str); + } + public static String decrypt(String str) { if (StringUtil.isEmpty(str) || !StringUtil.startsWithIgnoreCase(str, "encrypted:")) return str; str = str.substring(10); @@ -767,24 +774,24 @@ protected static FunctionLib[] duplicate(FunctionLib[] flds, boolean deepCopy) { return rst; } - public static Object replaceConfigPlaceHolders(Object obj) { + public static Object replaceConfigPlaceHolders(Config config, Object obj) { if (obj == null) return obj; // handle simple value if (Decision.isSimpleValue(obj)) { - if (obj instanceof CharSequence) return replaceConfigPlaceHolder(obj.toString()); + if (obj instanceof CharSequence) return replaceConfigPlaceHolder(config, obj.toString()); return obj; } // handle collection if (obj instanceof lucee.runtime.type.Collection) { - return replaceConfigPlaceHolders((lucee.runtime.type.Collection) obj); + return replaceConfigPlaceHolders(config, (lucee.runtime.type.Collection) obj); } return obj; } - public static lucee.runtime.type.Collection replaceConfigPlaceHolders(lucee.runtime.type.Collection data) { + public static lucee.runtime.type.Collection replaceConfigPlaceHolders(Config config, lucee.runtime.type.Collection data) { if (data == null) return data; lucee.runtime.type.Collection repl; @@ -795,21 +802,27 @@ public static lucee.runtime.type.Collection replaceConfigPlaceHolders(lucee.runt Entry e; while (it.hasNext()) { e = it.next(); - repl.setEL(e.getKey(), replaceConfigPlaceHolders(e.getValue())); + repl.setEL(e.getKey(), replaceConfigPlaceHolders(config, e.getValue())); } return repl; } - public static String replaceConfigPlaceHolder(String v) { + public static String replaceConfigPlaceHolderX(String v) { + return replaceConfigPlaceHolder(null, v); + } + + public final static String replaceConfigPlaceHolder(Config config, String v) { if (StringUtil.isEmpty(v) || v.indexOf('{') == -1) return v; - int s = -1, e = -1, d = -1; + int s = -1, e = -1, d = -1, sec = -1; int prefixLen, start = -1, end; - String _name, _prop; + String _name; + CharSequence _prop; while ((s = v.indexOf("{system:", start)) != -1 | /* don't change */ (e = v.indexOf("{env:", start)) != -1 | /* don't change */ - (d = v.indexOf("${", start)) != -1) { - boolean isSystem = false, isDollar = false; + (d = v.indexOf("${", start)) != -1 | /* don't change */ + (sec = v.indexOf("{secret:", start)) != -1) { + boolean isSystem = false, isDollar = false, isSecret = false; // system if (s > -1 && (e == -1 || e > s)) { start = s; @@ -821,6 +834,12 @@ else if (e > -1) { start = e; prefixLen = 5; } + // secret + else if (sec > -1) { + start = sec; + prefixLen = 8; + isSecret = true; + } // dollar else { start = d; @@ -832,7 +851,23 @@ else if (e > -1) { if (end > prefixLen) { _name = v.substring(start + prefixLen, end); // print.edate(_name); - if (isDollar) { + if (isSecret) { + + String[] _parts = _name.split(":"); + try { + if (config == null) throw new ApplicationException("cannot use secret in this context"); + if (_parts.length == 1) { + _prop = SecretProviderFactory.getSecret(ThreadLocalPageContext.getConfig(config), null, _parts[0], true); + } + else { + _prop = SecretProviderFactory.getSecret(ThreadLocalPageContext.getConfig(config), _parts[0], _parts[1], true); + } + } + catch (PageException pe) { + throw new PageRuntimeException(pe); + } + } + else if (isDollar) { String[] _parts = _name.split(":"); _prop = SystemUtil.getSystemPropOrEnvVar(_parts[0], (_parts.length > 1) ? _parts[1] : null); } @@ -858,7 +893,7 @@ public static Array getAsArray(String parent, String child, Struct sct) { return getAsArray(child, getAsStruct(parent, sct)); } - public static Array getAsArray(Struct input, boolean replacePlaceholders, String... names) { + public static Array getAsArray(Config config, Struct input, boolean replacePlaceholders, String... names) { Array arr = null; if (input == null) return arr; @@ -875,10 +910,10 @@ public static Array getAsArray(Struct input, boolean replacePlaceholders, String input.put(names[0], arr); return arr; } - return replacePlaceholders ? (Array) replaceConfigPlaceHolders(arr) : arr; + return replacePlaceholders ? (Array) replaceConfigPlaceHolders(config, arr) : arr; } - public static Struct getAsStruct(Struct input, boolean allowCSSString, String... names) { + public static Struct getAsStruct(Config config, Struct input, boolean allowCSSString, String... names) { Struct sct = null; if (input == null) return sct; @@ -894,7 +929,7 @@ public static Struct getAsStruct(Struct input, boolean allowCSSString, String... for (String name: names) { obj = input.get(name, null); if (obj instanceof CharSequence && !StringUtil.isEmpty(obj.toString(), true)) { - sct = toStruct(obj.toString().trim()); + sct = toStruct(config, obj.toString().trim()); if (!sct.isEmpty()) break; } } @@ -906,10 +941,10 @@ public static Struct getAsStruct(Struct input, boolean allowCSSString, String... input.put(names[0], sct); return sct; } - return (Struct) replaceConfigPlaceHolders(sct); + return (Struct) replaceConfigPlaceHolders(config, sct); } - public static Struct toStruct(String str) { + public static Struct toStruct(Config config, String str) { Struct sct = new StructImpl(StructImpl.TYPE_LINKED); try { @@ -917,7 +952,7 @@ public static Struct toStruct(String 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()), replaceConfigPlaceHolder(URLDecoder.decode(item[1], true))); + if (item.length == 2) sct.setEL(KeyImpl.init(URLDecoder.decode(item[0], true).trim()), replaceConfigPlaceHolder(config, URLDecoder.decode(item[1], true))); else if (item.length == 1) sct.setEL(KeyImpl.init(URLDecoder.decode(item[0], true).trim()), ""); } } @@ -976,7 +1011,7 @@ public static Array getAsArray(String name, Struct sct) { * @return * @throws PageException */ - public static Array getAsArray(Struct input, boolean convertStructToArray, Key convertKey, Key stringKey, boolean replacePlaceHolder, String... names) { + public static Array getAsArray(Config config, Struct input, boolean convertStructToArray, Key convertKey, Key stringKey, boolean replacePlaceHolder, String... names) { Array arr = null; if (input == null) return arr; @@ -1034,7 +1069,7 @@ else if (arr == null) { return arr; } - if (replacePlaceHolder) return (Array) replaceConfigPlaceHolders(arr); + if (replacePlaceHolder) return (Array) replaceConfigPlaceHolders(config, arr); return arr; } diff --git a/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java b/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java index 0ad7e245bd..ce3d297420 100644 --- a/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java +++ b/core/src/main/java/lucee/runtime/config/ConfigWebImpl.java @@ -75,7 +75,6 @@ import lucee.runtime.exp.PageException; import lucee.runtime.exp.PageRuntimeException; 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; @@ -104,6 +103,7 @@ import lucee.runtime.search.SearchEngine; import lucee.runtime.security.SecretProvider; import lucee.runtime.security.SecurityManager; +import lucee.runtime.security.SecurityManagerImpl; import lucee.runtime.spooler.SpoolerEngine; import lucee.runtime.tag.TagHandlerPool; import lucee.runtime.type.Collection.Key; @@ -601,7 +601,12 @@ public CharSet getResourceCharSet() { @Override public SecurityManager getSecurityManager() { - return cs.getSecurityManager(); + SecurityManager sm = cs.getSecurityManager(); + // Set the root directory for local file access checks + if (sm instanceof SecurityManagerImpl) { + ((SecurityManagerImpl) sm).setRootDirectory(getRootDirectory()); + } + return sm; } @Override @@ -1024,7 +1029,7 @@ public ORMConfiguration getORMConfig() { } @Override - public CIPage getCachedPage(PageContext pc, String pathWithCFC) throws TemplateException { + public CIPage getCachedPage(PageContext pc, String pathWithCFC) throws PageException { return componentPathCache.getPage(pc, pathWithCFC); } diff --git a/core/src/main/java/lucee/runtime/config/PasswordImpl.java b/core/src/main/java/lucee/runtime/config/PasswordImpl.java index 09243d123a..1587fbf2b0 100644 --- a/core/src/main/java/lucee/runtime/config/PasswordImpl.java +++ b/core/src/main/java/lucee/runtime/config/PasswordImpl.java @@ -124,14 +124,14 @@ private static String hash(String str, String salt) { } } - public static Password readFromStruct(Struct data, String salt, boolean isDefault, boolean getPasswordFromEnv) { + public static Password readFromStruct(Config config, Struct data, String salt, boolean isDefault, boolean getPasswordFromEnv) { String prefix = isDefault ? "adminDefault" : "admin"; String prefixOlder = isDefault ? "default" : ""; // first we look for the hashed and salted password // preferred adminDefaultHSPW adminHSPW - String pw = ConfigFactoryImpl.getAttr(data, prefix + "hspw"); - if (StringUtil.isEmpty(pw, true)) pw = ConfigFactoryImpl.getAttr(data, prefixOlder + "hspw"); + String pw = ConfigFactoryImpl.getAttr(config, data, prefix + "hspw"); + if (StringUtil.isEmpty(pw, true)) pw = ConfigFactoryImpl.getAttr(config, data, prefixOlder + "hspw"); if (!StringUtil.isEmpty(pw, true)) { // password is only of use when there is a salt as well if (salt == null) return null; @@ -140,17 +140,17 @@ public static Password readFromStruct(Struct data, String salt, boolean isDefaul // fall back to password that is hashed but not salted // preferred adminDefaultPW adminPW - pw = ConfigFactoryImpl.getAttr(data, prefix + "pw"); - if (StringUtil.isEmpty(pw, true)) pw = ConfigFactoryImpl.getAttr(data, prefixOlder + "pw"); + pw = ConfigFactoryImpl.getAttr(config, data, prefix + "pw"); + if (StringUtil.isEmpty(pw, true)) pw = ConfigFactoryImpl.getAttr(config, data, prefixOlder + "pw"); if (!StringUtil.isEmpty(pw, true)) { return new PasswordImpl(ORIGIN_HASHED, pw, null, HASHED); } // fall back to encrypted password // preferred adminDefaultPassword adminPassword - String pwEnc = ConfigFactoryImpl.getAttr(data, prefix + "Password"); - if (StringUtil.isEmpty(pwEnc, true)) pwEnc = ConfigFactoryImpl.getAttr(data, prefixOlder + "Password"); - if (isDefault && StringUtil.isEmpty(pwEnc, true)) pwEnc = ConfigFactoryImpl.getAttr(data, "adminPasswordDefault"); + String pwEnc = ConfigFactoryImpl.getAttr(config, data, prefix + "Password"); + if (StringUtil.isEmpty(pwEnc, true)) pwEnc = ConfigFactoryImpl.getAttr(config, data, prefixOlder + "Password"); + if (isDefault && StringUtil.isEmpty(pwEnc, true)) pwEnc = ConfigFactoryImpl.getAttr(config, data, "adminPasswordDefault"); if (!StringUtil.isEmpty(pwEnc, true)) { String rawPassword = new BlowfishEasy("tpwisgh").decryptString(pwEnc); return new PasswordImpl(ORIGIN_ENCRYPTED, rawPassword, salt); diff --git a/core/src/main/java/lucee/runtime/config/s3/BundleProvider.java b/core/src/main/java/lucee/runtime/config/s3/BundleProvider.java index 6fcfaa4204..17edff1259 100644 --- a/core/src/main/java/lucee/runtime/config/s3/BundleProvider.java +++ b/core/src/main/java/lucee/runtime/config/s3/BundleProvider.java @@ -74,6 +74,7 @@ public final class BundleProvider extends DefaultHandler { public static final int CONNECTION_TIMEOUT = 1000; private static final long MAX_AGE = 24 * 60 * 60 * 1000; private static final int MAX_REDIRECTS = 10; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); private static final int DOWNLOAD_CONNECT_TIMEOUT = 10000; // 10 seconds private static final int DOWNLOAD_READ_TIMEOUT = 60000; // 60 seconds @@ -395,7 +396,7 @@ private File deployBundledBundle(File bundleDirectory, String symbolicName, Stri } // this also not works with windows - if (SystemUtil.isWindows()) return null; + if (IS_WINDOWS) return null; ZipEntry entry; File temp; ZipInputStream zis = null; diff --git a/core/src/main/java/lucee/runtime/converter/ScriptConverter.java b/core/src/main/java/lucee/runtime/converter/ScriptConverter.java index 43a72eac54..59bed9081d 100644 --- a/core/src/main/java/lucee/runtime/converter/ScriptConverter.java +++ b/core/src/main/java/lucee/runtime/converter/ScriptConverter.java @@ -185,7 +185,7 @@ private void _serializeList(List list, StringBuilder sb, Set done) throw */ public void _serializeStruct(Struct struct, StringBuilder sb, Set done) throws ConverterException { sb.append(goIn()); - boolean ordered = struct instanceof StructImpl && ((StructImpl) struct).getType() == Struct.TYPE_LINKED; + boolean ordered = struct instanceof StructImpl && (((StructImpl) struct).getType() == Struct.TYPE_LINKED || ((StructImpl) struct).getType() == StructImpl.TYPE_LINKED_NOT_SYNC); if (ordered) sb.append('['); else sb.append('{'); diff --git a/core/src/main/java/lucee/runtime/db/DataSourceSupport.java b/core/src/main/java/lucee/runtime/db/DataSourceSupport.java index 6a4084ca10..746f50c275 100644 --- a/core/src/main/java/lucee/runtime/db/DataSourceSupport.java +++ b/core/src/main/java/lucee/runtime/db/DataSourceSupport.java @@ -74,6 +74,7 @@ public abstract class DataSourceSupport implements DataSourcePro, Cloneable, Ser private final int maxIdle; private final int maxTotal; private Boolean mssql; + private String id; public DataSourceSupport(Config config, String name, ClassDefinition cd, String username, String password, TagListener listener, boolean blob, boolean clob, int connectionLimit, int idleTimeout, int liveTimeout, int minIdle, int maxIdle, int maxTotal, long metaCacheTimeout, TimeZone timezone, int allow, boolean storage, @@ -400,12 +401,14 @@ public int hashCode() { @Override public String id() { - - return new StringBuilder().append(getConnectionStringTranslated()).append(':').append(getConnectionLimit()).append(':').append(getConnectionTimeout()).append(':') - .append(getLiveTimeout()).append(':').append(getMetaCacheTimeout()).append(':').append((getName() + "").toLowerCase()).append(':').append(getUsername()).append(':') - .append(getPassword()).append(':').append(validate()).append(':').append(cd == null ? "null" : cd.toString()).append(':') - .append((getTimeZone() == null ? "null" : getTimeZone().getID())).append(':').append(isBlob()).append(':').append(isClob()).append(':').append(isReadOnly()) - .append(':').append(isStorage()).append(':').append(isRequestExclusive()).append(':').append(isAlwaysResetConnections()).toString(); + if (id == null) { + id = new StringBuilder().append(getConnectionStringTranslated()).append(':').append(getConnectionLimit()).append(':').append(getConnectionTimeout()).append(':') + .append(getLiveTimeout()).append(':').append(getMetaCacheTimeout()).append(':').append((getName() + "").toLowerCase()).append(':').append(getUsername()) + .append(':').append(getPassword()).append(':').append(validate()).append(':').append(cd == null ? "null" : cd.toString()).append(':') + .append((getTimeZone() == null ? "null" : getTimeZone().getID())).append(':').append(isBlob()).append(':').append(isClob()).append(':').append(isReadOnly()) + .append(':').append(isStorage()).append(':').append(isRequestExclusive()).append(':').append(isAlwaysResetConnections()).toString(); + } + return id; } @Override diff --git a/core/src/main/java/lucee/runtime/db/QoQ.java b/core/src/main/java/lucee/runtime/db/QoQ.java index 7937778467..43f46157a4 100644 --- a/core/src/main/java/lucee/runtime/db/QoQ.java +++ b/core/src/main/java/lucee/runtime/db/QoQ.java @@ -915,6 +915,25 @@ else if (operators.length == 2) { // if(op.equals("=") || op.equals("in")) return executeEQ(pc,sql,qr,expression,row); + // Handle convert() specially - the second operand is a type name, not a column to evaluate + if (op.equals("convert")) { + Object left = executeExp(pc, sql, source, operators[0], row); + String typeName; + // If the user does convert( col1, 'string' ) it will be a ValueExpression and we can use it + // directly; + // If the user does convert( col1, string ) it will be a ColumnExpression and we just want to use the + // column name ("string" in this case). + // convert() is the binary version of the unary operator cast() + // i.e. convert( col1, string ) is the same as cast( col1 as string ) + if (operators[1] instanceof ColumnExpression) { + typeName = ((ColumnExpression) operators[1]).getColumnName(); + } + else { + typeName = Caster.toString(executeExp(pc, sql, source, operators[1], row)); + } + return executeCast(pc, left, typeName); + } + Object left = executeExp(pc, sql, source, operators[0], row); Object right = executeExp(pc, sql, source, operators[1], row); @@ -931,18 +950,6 @@ else if (operators.length == 2) { if (op.equals("concat")) return Caster.toString(left).concat(Caster.toString(right)); if (op.equals("count")) return executeCount(pc, sql, source, operators); if (op.equals("coalesce")) return executeCoalesce(pc, sql, source, operators, row); - if (op.equals("convert")) { - // If the user does convert( col1, 'string' ) it will be a ValueExpression and we can use it - // directly; - // If the user does convert( col1, string ) it will be a ColumnExpressin and we just want to use the - // column name ("string" in this case). - // convert() is the binary version of the unary operator cast() - // i.e. convert( col1, string ) is the same as cast( col1 as string ) - if (operators[1] instanceof ColumnExpression) { - right = ((ColumnExpression) operators[1]).getColumnName(); - } - return executeCast(pc, left, Caster.toString(right)); - } break; case 'i': if (op.equals("isnull")) return executeCoalesce(pc, sql, source, operators, row); @@ -1468,7 +1475,18 @@ private boolean executeLike(PageContext pc, SQL sql, QueryImpl source, Operation * @throws PageException */ private Object executeColumn(PageContext pc, SQL sql, QueryImpl source, Column column, int row) throws PageException { - return executeColumn(pc, sql, source, column, row, null); + if (column.isParam()) { + return executeColumn(pc, sql, source, column, row, null); + } + try { + return column.getValue(pc, source, row); + } + catch (DatabaseException e) { + // Wrap as IllegalQoQException to prevent fallback to HSQLDB + IllegalQoQException iqe = new IllegalQoQException(e.getMessage(), e.getDetail(), sql, null); + ExceptionUtil.initCauseEL(iqe, e); + throw iqe; + } } private Object executeColumn(PageContext pc, SQL sql, QueryImpl source, Column column, int row, Object defaultValue) throws PageException { @@ -1496,7 +1514,15 @@ private Object executeColumn(PageContext pc, SQL sql, QueryImpl source, Column c } } } - return column.getValue(pc, source, row, defaultValue); + try { + return column.getValue(pc, source, row, defaultValue); + } + catch (DatabaseException e) { + // Wrap as IllegalQoQException to prevent fallback to HSQLDB + IllegalQoQException iqe = new IllegalQoQException(e.getMessage(), e.getDetail(), sql, null); + ExceptionUtil.initCauseEL(iqe, e); + throw iqe; + } } // Helpers for exceptions in Lambdas diff --git a/core/src/main/java/lucee/runtime/db/SQLCaster.java b/core/src/main/java/lucee/runtime/db/SQLCaster.java index 7acc5415ad..e3e4240730 100755 --- a/core/src/main/java/lucee/runtime/db/SQLCaster.java +++ b/core/src/main/java/lucee/runtime/db/SQLCaster.java @@ -164,8 +164,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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."); + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -177,8 +180,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -190,8 +196,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -204,8 +213,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -224,8 +236,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -241,8 +256,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -256,8 +274,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -269,8 +290,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -282,8 +306,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -295,8 +322,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -308,8 +338,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -349,8 +382,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -362,8 +398,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; @@ -375,8 +414,11 @@ public static void setValue(PageContext pc, TimeZone tz, PreparedStatement stat, } 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, "") + Log log = ThreadLocalPageContext.getLog(pc, "datasource"); + if (LogUtil.doesWarn(log)) { + 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; diff --git a/core/src/main/java/lucee/runtime/db/SQLImpl.java b/core/src/main/java/lucee/runtime/db/SQLImpl.java index ca33cd940e..e710d5ae16 100644 --- a/core/src/main/java/lucee/runtime/db/SQLImpl.java +++ b/core/src/main/java/lucee/runtime/db/SQLImpl.java @@ -26,6 +26,8 @@ */ public final class SQLImpl implements SQL, Serializable { + private static final SQLItem[] EMPTY_ITEMS = new SQLItem[0]; + private String strSQL; private SQLItem[] items; private int position = 0; @@ -40,7 +42,7 @@ public final class SQLImpl implements SQL, Serializable { */ public SQLImpl(String strSQL) { this.strSQL = strSQL; - this.items = new SQLItem[0]; + this.items = EMPTY_ITEMS; } /** @@ -51,7 +53,7 @@ public SQLImpl(String strSQL) { */ public SQLImpl(String strSQL, SQLItem[] items) { this.strSQL = strSQL; - this.items = items == null ? new SQLItem[0] : items; + this.items = items == null ? EMPTY_ITEMS : items; } public void addItems(SQLItem item) { diff --git a/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java b/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java index 02c6df2420..0febbd9688 100644 --- a/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java +++ b/core/src/main/java/lucee/runtime/engine/CFMLEngineImpl.java @@ -187,6 +187,8 @@ */ public final class CFMLEngineImpl implements CFMLEngine { + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + static { System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.NoOpLog"); System.setProperty("javax.xml.bind.context.factory", "com.sun.xml.bind.v2.ContextFactory"); @@ -260,11 +262,12 @@ private CFMLEngineImpl(CFMLEngineFactory factory, BundleCollection bc) { // Kick some stuff to get it started in parallel because it takes forever to load ThreadUtil.getThread(() -> { - try { - Class.forName("lucee.runtime.type.util.KeyConstants"); - } - catch (ClassNotFoundException e) { - } + // try { + // Class.forName("lucee.runtime.type.util.KeyConstants"); + KeyConstants.sync(); + // } + // catch (ClassNotFoundException e) { + // } }, true).start(); ThreadUtil.getThread(() -> { @@ -789,7 +792,7 @@ public static synchronized CFMLEngine getInstance(CFMLEngineFactory factory, Bun if (engine == null) { if (SystemUtil.getLoaderVersion() < 6.0D) { // windows needs 6.0 because restart is not working with older versions - if (SystemUtil.isWindows()) + if (IS_WINDOWS) throw new RuntimeException("You need to update a newer lucee.jar to run this version, you can download the latest jar from https://download.lucee.org."); else if (SystemUtil.getLoaderVersion() < 5.8D) throw new RuntimeException("You need to update your lucee.jar to run this version, you can download the latest jar from https://download.lucee.org."); diff --git a/core/src/main/java/lucee/runtime/extension/RHExtension.java b/core/src/main/java/lucee/runtime/extension/RHExtension.java index f49e556c54..d5c5bbdc90 100644 --- a/core/src/main/java/lucee/runtime/extension/RHExtension.java +++ b/core/src/main/java/lucee/runtime/extension/RHExtension.java @@ -129,6 +129,8 @@ public final class RHExtension implements Serializable { private static Set metadataFilesChecked = new HashSet<>(); private static Map instances = new ConcurrentHashMap<>(); + private static Map installedFiles = null; + private static Map availableFiles = null; private ExtensionMetadata metadata; private Resource extensionFile; @@ -148,10 +150,11 @@ public static RHExtension getInstance(Config config, Resource ext, RHExtension d } public static RHExtension getInstance(Config config, String id, String version) throws PageException { - return getInstance(config, new ExtensionDefintion(id, version).setSource(config, getExtensionInstalledFile(config, id, version, false))); + return getInstance(config, new ExtensionDefintion(id, version).setSource(config, getExtensionFile(config, id, version, null))); } public static RHExtension getInstance(Config config, Resource ext) { + RHExtension instance = instances.get(ext.getAbsolutePath()); if (instance == null) { synchronized (SystemUtil.createToken("RHExtension.getInstance", ext.getAbsolutePath())) { @@ -276,36 +279,46 @@ private static void init(Config config, ExtensionMetadata metadata, Resource ext public static RHExtension installExtension(ConfigPro config, String id, String version, Resource resource, boolean force) throws PageException, IOException { // get installed res - Resource res = StringUtil.isEmpty(version) ? null : getExtensionInstalledFile(config, id, version, false); - boolean installed = (res != null && res.isFile()); + Resource res = StringUtil.isEmpty(version) ? null : getExtensionInstalledFile(config, id, version, null); + boolean installed = (res != null); ResetFilter filter = new ResetFilter(); if (!installed) { - try { - if (resource != null) { - return DeployHandler.deployExtension(config, new ExtensionDefintion(id, version).setSource(config, resource), filter, null, false, true, true, - new RefBooleanImpl()); - } - else if (!StringUtil.isEmpty(id)) { - return DeployHandler.deployExtension(config, new ExtensionDefintion(id, version), filter, null, false, true, true, new RefBooleanImpl()); // MUSTT + synchronized (SystemUtil.createToken("RHExtension", "installedFiles")) { + try { + if (resource != null) { + return DeployHandler.deployExtension(config, new ExtensionDefintion(id, version).setSource(config, resource), filter, null, false, true, true, + new RefBooleanImpl()); + } + else if (!StringUtil.isEmpty(id)) { + return DeployHandler.deployExtension(config, new ExtensionDefintion(id, version), filter, null, false, true, true, new RefBooleanImpl()); // MUSTT + } + else { + throw new IOException("cannot install extension based on the given data [id:" + id + ";version:" + version + ";resource:" + resource + "]"); + } } - else { - throw new IOException("cannot install extension based on the given data [id:" + id + ";version:" + version + ";resource:" + resource + "]"); + finally { + filter.reset(config); + resetExtensionInstalledFile(config, id, version); } } - finally { - filter.reset(config); - } } // if forced we also install if it already is else if (force) { - return DeployHandler.deployExtension(config, res, false, true, RHExtension.ACTION_NONE); + synchronized (SystemUtil.createToken("RHExtension", "installedFiles")) { + try { + return DeployHandler.deployExtension(config, res, false, true, RHExtension.ACTION_NONE); + } + finally { + resetExtensionInstalledFile(config, id, version); + } + } } return getInstance(config, new ExtensionDefintion(id, version).setSource(config, res)); } - public static boolean isInstalled(Config config, String id, String version) throws PageException { - Resource res = getExtensionInstalledFile(config, id, version, false); - return res != null && res.isFile(); + public static boolean isInstalled(Config config, String id, String version) { + Resource res = getExtensionInstalledFile(config, id, version, null); + return res != null; } /** @@ -385,23 +398,26 @@ public static void write(Config config, ExtensionMetadata metadata) throws IOExc private Resource act(Config config, Resource ext, short action) throws PageException { Resource trg; Resource trgDir; - try { - trg = getExtensionInstalledFile(config, getId(), getVersion(), false); - trgDir = trg.getParentResource(); - trgDir.mkdirs(); - if (!ext.getParentResource().equals(trgDir)) { - if (trg.exists()) trg.delete(); - if (action == ACTION_COPY) { - ext.copyTo(trg, false); - } - else if (action == ACTION_MOVE) { - ResourceUtil.moveTo(ext, trg, true); + synchronized (SystemUtil.createToken("RHExtension", "installedFiles")) { + try { + trg = getExtensionInstalledFile(config, getId(), getVersion(), false); + trgDir = trg.getParentResource(); + trgDir.mkdirs(); + if (!ext.getParentResource().equals(trgDir)) { + if (trg.exists()) trg.delete(); + if (action == ACTION_COPY) { + ext.copyTo(trg, false); + } + else if (action == ACTION_MOVE) { + ResourceUtil.moveTo(ext, trg, true); + } + this.extensionFile = trg; } - this.extensionFile = trg; } - } - catch (Exception e) { - throw Caster.toPageException(e); + catch (Exception e) { + throw Caster.toPageException(e); + } + resetExtensionInstalledFile(config, getId(), getVersion()); } return trg; } @@ -413,39 +429,18 @@ public void addToAvailable(Config config) { private void addToAvailable(Config config, Resource ext) { if (ext == null || ext.length() == 0 || getId() == null) return; Log logger = ThreadLocalPageContext.getLog(config, "deploy"); - Resource res; - if (config instanceof ConfigWeb) { - res = ((ConfigWeb) config).getConfigServerDir().getRealResource("extensions/"); - } - else { - res = config.getConfigDir().getRealResource("extensions/"); - } - - // parent exist? - if (!res.isDirectory()) { - logger.warn("extension", "directory [" + res + "] does not exist"); - return; - } - res = res.getRealResource("available/"); - - // exist? - if (!res.isDirectory()) { + synchronized (SystemUtil.createToken("RHExtension", "availableFiles")) { + Resource res = ((ConfigPro) config).getExtensionAvailableDir(); + res = res.getRealResource(getId() + "-" + getVersion() + ".lex"); + if (res.length() == ext.length()) return; try { - res.createDirectory(true); + ResourceUtil.copy(ext, res); + logger.info("extension", "copy [" + getId() + ":" + getVersion() + "] to [" + res + "]"); } catch (IOException e) { logger.error("extension", e); - return; } - } - res = res.getRealResource(getId() + "-" + getVersion() + ".lex"); - if (res.length() == ext.length()) return; - try { - ResourceUtil.copy(ext, res); - logger.info("extension", "copy [" + getId() + ":" + getVersion() + "] to [" + res + "]"); - } - catch (IOException e) { - logger.error("extension", e); + resetExtensionAvailableFile(config, getId(), getVersion()); } } @@ -696,36 +691,36 @@ private static void readManifestConfigOld(Config config, ExtensionMetadata metad Log logger = ThreadLocalPageContext.getLog(config, "deploy"); Info info = ConfigUtil.getEngine(config).getInfo(); - metadata.setSymbolicName(ConfigFactoryImpl.getAttr(data, "symbolicName", "symbolic-name")); - metadata.setName(ConfigFactoryImpl.getAttr(data, "name"), label); + metadata.setSymbolicName(ConfigFactoryImpl.getAttr(config, data, "symbolicName", "symbolic-name")); + metadata.setName(ConfigFactoryImpl.getAttr(config, data, "name"), label); label = metadata.getName(); - metadata.setVersion(ConfigFactoryImpl.getAttr(data, "version"), label); + metadata.setVersion(ConfigFactoryImpl.getAttr(config, data, "version"), label); label += " : " + metadata._getVersion(); - metadata.setId(StringUtil.isEmpty(id) ? ConfigFactoryImpl.getAttr(data, "id") : id, label); - metadata.setDescription(ConfigFactoryImpl.getAttr(data, "description")); - metadata.setTrial(Caster.toBooleanValue(ConfigFactoryImpl.getAttr(data, "trial"), false)); - if (_img == null) _img = ConfigFactoryImpl.getAttr(data, "image"); + metadata.setId(StringUtil.isEmpty(id) ? ConfigFactoryImpl.getAttr(config, data, "id") : id, label); + metadata.setDescription(ConfigFactoryImpl.getAttr(config, data, "description")); + metadata.setTrial(Caster.toBooleanValue(ConfigFactoryImpl.getAttr(config, data, "trial"), false)); + if (_img == null) _img = ConfigFactoryImpl.getAttr(config, data, "image"); metadata.setImage(_img); - String cat = ConfigFactoryImpl.getAttr(data, "category"); - if (StringUtil.isEmpty(cat, true)) cat = ConfigFactoryImpl.getAttr(data, "categories"); + String cat = ConfigFactoryImpl.getAttr(config, data, "category"); + if (StringUtil.isEmpty(cat, true)) cat = ConfigFactoryImpl.getAttr(config, data, "categories"); metadata.setCategories(cat); - metadata.setMinCoreVersion(ConfigFactoryImpl.getAttr(data, "luceeCoreVersion", "lucee-core-version"), info); - metadata.setMinLoaderVersion(ConfigFactoryImpl.getAttr(data, "luceeCoreVersion", "lucee-loader-version"), info); - metadata.setStartBundles(Caster.toBooleanValue(ConfigFactoryImpl.getAttr(data, "startBundles", "start-bundles"), true)); - - metadata.setAMF(ConfigFactoryImpl.getAttr(data, "amf"), logger); - metadata.setResource(ConfigFactoryImpl.getAttr(data, "resource"), logger); - metadata.setSearch(ConfigFactoryImpl.getAttr(data, "search"), logger); - metadata.setORM(ConfigFactoryImpl.getAttr(data, "orm"), logger); - metadata.setWebservice(ConfigFactoryImpl.getAttr(data, "webservice"), logger); - metadata.setMonitor(ConfigFactoryImpl.getAttr(data, "monitor"), logger); - metadata.setCaches(ConfigFactoryImpl.getAttr(data, "cache"), logger); - metadata.setCacheHandler(ConfigFactoryImpl.getAttr(data, "cacheHandler", "cache-handler"), logger); - metadata.setJDBC(ConfigFactoryImpl.getAttr(data, "jdbc"), logger); - metadata.setStartupHook(ConfigFactoryImpl.getAttr(data, "startup-hook"), logger); - metadata.setMaven(ConfigFactoryImpl.getAttr(data, "maven"), logger); - metadata.setMapping(ConfigFactoryImpl.getAttr(data, "mapping"), logger); - metadata.setEventGatewayInstances(ConfigFactoryImpl.getAttr(data, "eventGatewayInstance", "event-gateway-instance"), logger); + metadata.setMinCoreVersion(ConfigFactoryImpl.getAttr(config, data, "luceeCoreVersion", "lucee-core-version"), info); + metadata.setMinLoaderVersion(ConfigFactoryImpl.getAttr(config, data, "luceeCoreVersion", "lucee-loader-version"), info); + metadata.setStartBundles(Caster.toBooleanValue(ConfigFactoryImpl.getAttr(config, data, "startBundles", "start-bundles"), true)); + + metadata.setAMF(ConfigFactoryImpl.getAttr(config, data, "amf"), logger); + metadata.setResource(ConfigFactoryImpl.getAttr(config, data, "resource"), logger); + metadata.setSearch(ConfigFactoryImpl.getAttr(config, data, "search"), logger); + metadata.setORM(ConfigFactoryImpl.getAttr(config, data, "orm"), logger); + metadata.setWebservice(ConfigFactoryImpl.getAttr(config, data, "webservice"), logger); + metadata.setMonitor(ConfigFactoryImpl.getAttr(config, data, "monitor"), logger); + metadata.setCaches(ConfigFactoryImpl.getAttr(config, data, "cache"), logger); + metadata.setCacheHandler(ConfigFactoryImpl.getAttr(config, data, "cacheHandler", "cache-handler"), logger); + metadata.setJDBC(ConfigFactoryImpl.getAttr(config, data, "jdbc"), logger); + metadata.setStartupHook(ConfigFactoryImpl.getAttr(config, data, "startup-hook"), logger); + metadata.setMaven(ConfigFactoryImpl.getAttr(config, data, "maven"), logger); + metadata.setMapping(ConfigFactoryImpl.getAttr(config, data, "mapping"), logger); + metadata.setEventGatewayInstances(ConfigFactoryImpl.getAttr(config, data, "eventGatewayInstance", "event-gateway-instance"), logger); } public void validate(Config config) throws ApplicationException { @@ -792,18 +787,133 @@ else if (load) { } } + public static Map loadExtensionInstalledFiles(Config config) { + if (installedFiles == null) { + synchronized (SystemUtil.createToken("RHExtension", "installedFiles")) { + if (installedFiles == null) { + Resource dir = ((ConfigPro) config).getExtensionInstalledDir(); + installedFiles = new ConcurrentHashMap<>(); + for (Resource res: dir.listResources(new ExtensionResourceFilter("lex"))) { + installedFiles.put(res.getName(), res); + } + } + } + } + return installedFiles; + } + + public static Map loadExtensionAvailableFiles(Config config) { + if (availableFiles == null) { + synchronized (SystemUtil.createToken("RHExtension", "availableFiles")) { + if (availableFiles == null) { + Resource dir = ((ConfigPro) config).getExtensionAvailableDir(); + availableFiles = new ConcurrentHashMap<>(); + for (Resource res: dir.listResources(new ExtensionResourceFilter("lex"))) { + availableFiles.put(res.getName(), res); + } + } + } + } + return availableFiles; + } + + public static void resetExtensionInstalledFile(Config config, String id, String version) { + String fileName = toHash(id, version, "lex"); + resetExtensionFile(config, installedFiles, ((ConfigPro) config).getExtensionInstalledDir(), "installedFiles", fileName); + } + + public static void resetExtensionAvailableFile(Config config, String id, String version) { + String fileName = id + "-" + version + ".lex"; + resetExtensionFile(config, availableFiles, ((ConfigPro) config).getExtensionAvailableDir(), "availableFiles", fileName); + } + + private static void resetExtensionFile(Config config, Map files, Resource dir, String lockName, String name) { + if (files != null) { + synchronized (SystemUtil.createToken("RHExtension", lockName)) { + if (files != null) { + Resource res = files.get(name); + // file exist in cache + if (res != null) { + // file no longer exist physically + if (!res.isFile()) { + files.remove(name); + } + } + // file not exist in cache + else { + res = dir.getRealResource(name); + // file exist physically + if (res.isFile()) { + files.put(name, res); + } + } + } + } + } + } + + public static void removeExtensionInstalledFile(Config config, String id, String version) throws IOException { + removeExtensionInstalledFile(config, toHash(id, version, "lex")); + } + + public static void removeExtensionInstalledFile(Config config, String fileName) throws IOException { + synchronized (SystemUtil.createToken("RHExtension", "installedFiles")) { + Resource res = ((ConfigPro) config).getExtensionInstalledDir().getRealResource(fileName); + if (res.isFile()) res.remove(true); + if (installedFiles != null) { + installedFiles.remove(fileName); + } + } + } + + public static void removeExtensionAvailableFile(Config config, String id, String version) throws IOException { + String fileName = id + "-" + version + ".lex"; + synchronized (SystemUtil.createToken("RHExtension", "availableFiles")) { + Resource res = ((ConfigPro) config).getExtensionAvailableDir().getRealResource(fileName); + if (res.isFile()) res.remove(true); + if (availableFiles != null) { + availableFiles.remove(fileName); + } + } + } + public static Resource getExtensionInstalledFile(Config config, String id, String version, boolean validate) throws ApplicationException { String fileName = toHash(id, version, "lex"); - Resource res = getExtensionInstalledDir(config).getRealResource(fileName); - if (validate && !res.exists()) throw new ApplicationException("Extension [" + fileName + "] was not found at [" + res + "]"); - return res; + // get existing file + Resource res = loadExtensionInstalledFiles(config).get(fileName); + if (res != null) return res; + + // no existing file and we need to throw an exception if not exist + if (validate) throw new ApplicationException("Extension [" + fileName + "] was not found at [" + res + "]"); + + // return none existing resource + return getExtensionInstalledDir(config).getRealResource(fileName); + } + + public static Resource getExtensionFile(Config config, String id, String version, Resource defaultValue) { + Resource res = getExtensionInstalledFile(config, id, version, null); + if (res != null) return res; + res = getExtensionAvailableFile(config, id, version, null); + if (res != null) return res; + return defaultValue; + } + + public String getExtensionInstalledName() { + return toHash(getId(), getVersion(), "lex"); } public static Resource getExtensionInstalledFile(Config config, String id, String version, Resource defaultValue) { String fileName = toHash(id, version, "lex"); - Resource res = getExtensionInstalledDir(config).getRealResource(fileName); - if (!res.exists()) return defaultValue; - return res; + Resource res = loadExtensionInstalledFiles(config).get(fileName); + if (res != null) return res; + return defaultValue; + } + + public static Resource getExtensionAvailableFile(Config config, String id, String version, Resource defaultValue) { + String fileName = id + "-" + version + ".lex"; + Resource res = loadExtensionAvailableFiles(config).get(fileName); + if (res != null) return res; + return defaultValue; } public static Resource getMetaDataFile(Config config, String id, String version) { diff --git a/core/src/main/java/lucee/runtime/functions/file/DirectoryDelete.java b/core/src/main/java/lucee/runtime/functions/file/DirectoryDelete.java index 65a6bbee0d..563230b7e9 100755 --- a/core/src/main/java/lucee/runtime/functions/file/DirectoryDelete.java +++ b/core/src/main/java/lucee/runtime/functions/file/DirectoryDelete.java @@ -20,7 +20,9 @@ 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.exp.FunctionException; import lucee.runtime.exp.PageException; import lucee.runtime.tag.Directory; @@ -30,8 +32,9 @@ public static String call(PageContext pc, String path) throws PageException { } public static String call(PageContext pc, String path, boolean recurse) throws PageException { + if (StringUtil.isEmpty(path, true)) throw new FunctionException(pc, "DirectoryDelete", 1, "path", "The argument [path] is required and cannot be empty"); Resource dir = ResourceUtil.toResourceNotExisting(pc, path); Directory.actionDelete(pc, dir, recurse, null); return null; } -} \ No newline at end of file +} diff --git a/core/src/main/java/lucee/runtime/functions/file/DirectoryEvery.java b/core/src/main/java/lucee/runtime/functions/file/DirectoryEvery.java index 110661126e..7a616a6329 100644 --- a/core/src/main/java/lucee/runtime/functions/file/DirectoryEvery.java +++ b/core/src/main/java/lucee/runtime/functions/file/DirectoryEvery.java @@ -190,7 +190,7 @@ private static Object info(Resource res, String dir, int level, boolean modeSupp sct.set(KeyConstants._dateLastModified, new DateTimeImpl(res.lastModified())); if (modeSupported) sct.set(KeyConstants._mode, new ModeObjectWrap(res)); if (res instanceof ResourceMetaData) sct.set(KeyConstants._meta, ((ResourceMetaData) res).getMetaData()); - sct.set(KeyConstants._attributes, Directory.getFileAttribute(res, true)); + sct.set(KeyConstants._attributes, Directory.getFileAttribute(res)); return sct; } @@ -205,7 +205,7 @@ private static Object info(Path path, String dir, int level, boolean modeSupport sct.set(KeyConstants._type, isDir ? "Dir" : "File"); sct.set(KeyConstants._dateLastModified, new DateTimeImpl(Files.getLastModifiedTime(path).toMillis())); if (modeSupported) sct.set(KeyConstants._mode, new ModeObjectWrap(path)); - sct.set(KeyConstants._attributes, Directory.getFileAttribute(path, true)); + sct.set(KeyConstants._attributes, Directory.getFileAttribute(path)); return sct; } diff --git a/core/src/main/java/lucee/runtime/functions/file/DirectoryRename.java b/core/src/main/java/lucee/runtime/functions/file/DirectoryRename.java index d222cc8ef3..9ad9e5b016 100755 --- a/core/src/main/java/lucee/runtime/functions/file/DirectoryRename.java +++ b/core/src/main/java/lucee/runtime/functions/file/DirectoryRename.java @@ -20,7 +20,9 @@ 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.exp.FunctionException; import lucee.runtime.exp.PageException; import lucee.runtime.tag.Directory; @@ -31,7 +33,8 @@ public static String call(PageContext pc, String oldPath, String newPath) throws } public static String call(PageContext pc, String oldPath, String newPath, boolean createPath) throws PageException { + if (StringUtil.isEmpty(oldPath, true)) throw new FunctionException(pc, "DirectoryRename", 1, "oldPath", "The argument [oldPath] is required and cannot be empty"); Resource dir = ResourceUtil.toResourceNotExisting(pc, oldPath); return Directory.actionRename(pc, dir, newPath, null, createPath, "public-read", null); } -} \ No newline at end of file +} diff --git a/core/src/main/java/lucee/runtime/functions/list/ListGetDuplicates.java b/core/src/main/java/lucee/runtime/functions/list/ListGetDuplicates.java index 5cfbbb76a6..3928e34739 100644 --- a/core/src/main/java/lucee/runtime/functions/list/ListGetDuplicates.java +++ b/core/src/main/java/lucee/runtime/functions/list/ListGetDuplicates.java @@ -63,7 +63,7 @@ public Object invoke(PageContext pc, Object[] args) throws PageException { if (args.length == 1) return call(pc, Caster.toString(args[0])); if (args.length == 2) return call(pc, Caster.toString(args[0]), Caster.toString(args[1])); if (args.length == 3) return call(pc, Caster.toString(args[0]), Caster.toString(args[1]), Caster.toBooleanValue(args[2])); - if (args.length == 3) return call(pc, Caster.toString(args[0]), Caster.toString(args[1]), Caster.toBooleanValue(args[2]), Caster.toBooleanValue(args[3])); + if (args.length == 4) return call(pc, Caster.toString(args[0]), Caster.toString(args[1]), Caster.toBooleanValue(args[2]), Caster.toBooleanValue(args[3])); throw new FunctionException(pc, "ListGetDuplicates", 1, 4, args.length); } diff --git a/core/src/main/java/lucee/runtime/functions/other/GetTagData.java b/core/src/main/java/lucee/runtime/functions/other/GetTagData.java index 8604106024..2489c3e8c8 100644 --- a/core/src/main/java/lucee/runtime/functions/other/GetTagData.java +++ b/core/src/main/java/lucee/runtime/functions/other/GetTagData.java @@ -50,6 +50,7 @@ import lucee.transformer.library.tag.TagLibFactory; import lucee.transformer.library.tag.TagLibTag; import lucee.transformer.library.tag.TagLibTagAttr; +import lucee.transformer.library.tag.TagLibTagAttrGroup; import lucee.transformer.library.tag.TagLibTagScript; public final class GetTagData implements Function { @@ -215,6 +216,19 @@ private static Struct javaBasedTag(TagLib tld, TagLibTag tag) throws PageExcepti _args.setEL(attr.getName(), _arg); } + + // LDEV-5901: Add attribute groups + lucee.runtime.type.Array groups = new lucee.runtime.type.ArrayImpl(); + for (TagLibTagAttrGroup group : tag.getAttributeGroups()) { + Struct grp = new StructImpl(StructImpl.TYPE_LINKED); + grp.set(KeyConstants._name, group.getName()); + grp.set("label", group.getLabel()); + grp.set(KeyConstants._description, group.getDescription()); + grp.set(KeyConstants._attributes, group.getAttributes()); + groups.appendEL(grp); + } + sct.set("attributeGroups", groups); + return sct; } diff --git a/core/src/main/java/lucee/runtime/functions/other/_GetStaticScope.java b/core/src/main/java/lucee/runtime/functions/other/_GetStaticScope.java index d575e0cc8b..18d5d458c3 100644 --- a/core/src/main/java/lucee/runtime/functions/other/_GetStaticScope.java +++ b/core/src/main/java/lucee/runtime/functions/other/_GetStaticScope.java @@ -17,7 +17,6 @@ */ package lucee.runtime.functions.other; -import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; import lucee.runtime.StaticScope; import lucee.runtime.component.ComponentLoader; @@ -30,25 +29,25 @@ public class _GetStaticScope implements Function { private static final long serialVersionUID = -2676531632543576056L; public static Object call(PageContext pc, String componentPath) throws PageException { - return call(pc, componentPath, null); + return call(pc, componentPath, _CreateComponent.TYPE_BOTH); } public static Object call(PageContext pc, String componentPath, String type) throws PageException { + if ("java".equalsIgnoreCase(type)) return call(pc, componentPath, _CreateComponent.TYPE_JAVA); + if ("cfml".equalsIgnoreCase(type)) return call(pc, componentPath, _CreateComponent.TYPE_CFML); + return call(pc, componentPath, _CreateComponent.TYPE_BOTH); + } - int iType = _CreateComponent.TYPE_BOTH; - if (StringUtil.isEmpty(type, true)) iType = _CreateComponent.TYPE_BOTH; - if ("java".equalsIgnoreCase(type)) iType = _CreateComponent.TYPE_JAVA; - else if ("cfml".equals(type)) iType = _CreateComponent.TYPE_CFML; + private static Object call(PageContext pc, String componentPath, int type) throws PageException { - if (iType != _CreateComponent.TYPE_JAVA) { - StaticScope ss = ComponentLoader.getStaticScope(pc, null, componentPath, null, null, iType == _CreateComponent.TYPE_CFML); + if (type != _CreateComponent.TYPE_JAVA) { + StaticScope ss = ComponentLoader.getStaticScope(pc, null, componentPath, null, null, type == _CreateComponent.TYPE_CFML); if (ss != null) return ss; } // no if needed, if type=="cfml", getStaticScope return a result or throw an exception - Class cls = _CreateComponent.loadClass(pc, componentPath, iType); + Class cls = _CreateComponent.loadClass(pc, componentPath, type); return new JavaObject((pc).getVariableUtil(), cls, false); } - } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/query/QueryToStruct.java b/core/src/main/java/lucee/runtime/functions/query/QueryToStruct.java index afac7b7492..a0f9555f24 100644 --- a/core/src/main/java/lucee/runtime/functions/query/QueryToStruct.java +++ b/core/src/main/java/lucee/runtime/functions/query/QueryToStruct.java @@ -1,7 +1,6 @@ package lucee.runtime.functions.query; import lucee.commons.lang.StringUtil; -import lucee.commons.math.MathUtil; import lucee.runtime.PageContext; import lucee.runtime.exp.FunctionException; import lucee.runtime.exp.PageException; @@ -30,7 +29,7 @@ public static Struct call(PageContext pc, Query qry, String columnKey, String st int rows = qry.getRecordcount(); if (rows == 0) return sct; Key[] columns = qry.getColumnNames(); - int colCount = MathUtil.nextPowerOfTwo(columns.length, 0); + int colCount = StructImpl.optimalCapacity(columns.length, 4); Key colKey = Caster.toKey(columnKey); Struct tmp; for (int r = 1; r <= rows; r++) { diff --git a/core/src/main/java/lucee/runtime/functions/security/GetSecret.java b/core/src/main/java/lucee/runtime/functions/security/GetSecret.java index b843efe506..0c3eb091ae 100644 --- a/core/src/main/java/lucee/runtime/functions/security/GetSecret.java +++ b/core/src/main/java/lucee/runtime/functions/security/GetSecret.java @@ -1,13 +1,10 @@ package lucee.runtime.functions.security; import lucee.runtime.PageContext; -import lucee.runtime.config.ConfigPro; -import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.FunctionException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.function.BIF; import lucee.runtime.op.Caster; -import lucee.runtime.security.SecretProvider; import lucee.runtime.security.SecretProviderFactory; public final class GetSecret extends BIF { @@ -20,17 +17,8 @@ public Object invoke(PageContext pc, Object[] args) throws PageException { String key = Caster.toString(args[0]); String name = args.length > 1 ? Caster.toString(args[1]) : null; - - // check all of them - if (name == null) { - for (SecretProvider sp: ((ConfigPro) pc.getConfig()).getSecretProviders().values()) { - if (sp.getSecret(key, null) != null) return new SecretProviderFactory.Ref(sp, key); - } - throw new ApplicationException("no secret provider found that provides the key [" + key + "]"); - } - - SecretProvider sp = ((ConfigPro) pc.getConfig()).getSecretProvider(name); - return new SecretProviderFactory.Ref(sp, key).touch(); + boolean resolve = args.length > 2 ? Caster.toBooleanValue(args[2]) : false; + return SecretProviderFactory.getSecret(pc.getConfig(), name, key, resolve); } } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/struct/StructInfo.java b/core/src/main/java/lucee/runtime/functions/struct/StructInfo.java new file mode 100644 index 0000000000..9134769d06 --- /dev/null +++ b/core/src/main/java/lucee/runtime/functions/struct/StructInfo.java @@ -0,0 +1,29 @@ + +package lucee.runtime.functions.struct; + +import lucee.runtime.PageContext; +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; +import lucee.runtime.type.StructImpl; +import lucee.runtime.type.util.KeyConstants; + +public final class StructInfo extends BIF { + + private static final long serialVersionUID = 6837257606513875592L; + + public static Struct call(PageContext pc, Struct struct) { + Struct sct = new StructImpl(); + sct.setEL(KeyConstants._hash, struct.hashCode()); + sct.setEL(KeyConstants._class, struct.getClass().getName()); + return sct; + } + + @Override + public Object invoke(PageContext pc, Object[] args) throws PageException { + if (args.length == 1) return call(pc, Caster.toStruct(args[0])); + throw new FunctionException(pc, "StructInfo", 1, 1, args.length); + } +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/system/Compress.java b/core/src/main/java/lucee/runtime/functions/system/Compress.java new file mode 100644 index 0000000000..3cb02c9563 --- /dev/null +++ b/core/src/main/java/lucee/runtime/functions/system/Compress.java @@ -0,0 +1,76 @@ +/** + * + * 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.runtime.functions.system; + +import java.io.IOException; + +import lucee.commons.io.compress.CompressUtil; +import lucee.commons.io.res.Resource; +import lucee.commons.io.res.util.ResourceUtil; +import lucee.runtime.PageContext; +import lucee.runtime.exp.FunctionException; +import lucee.runtime.exp.PageException; +import lucee.runtime.ext.function.Function; +import lucee.runtime.op.Caster; +import lucee.runtime.type.util.ListUtil; + +/** + * Implements the CFML Function compress (basic zip/gzip support) + */ +public final class Compress implements Function { + + public static boolean call(PageContext pc, String strFormat, String strSource, String srcTarget) throws PageException { + return call(pc, strFormat, strSource, srcTarget, true, null); + } + + public static boolean call(PageContext pc, String strFormat, String strSource, String srcTarget, boolean includeBaseFolder) throws PageException { + return call(pc, strFormat, strSource, srcTarget, includeBaseFolder, null); + } + + public static boolean call(PageContext pc, String strFormat, String strSource, String srcTarget, boolean includeBaseFolder, String strMode) throws PageException { + // strMode is accepted for signature compatibility but only used by compress extension for tar formats + strFormat = strFormat.trim().toLowerCase(); + int format; + if (strFormat.equals("zip")) format = CompressUtil.FORMAT_ZIP; + else if (strFormat.equals("gzip")) format = CompressUtil.FORMAT_GZIP; + else throw new FunctionException(pc, "compress", 1, "format", + "invalid format definition [" + strFormat + "], valid formats are [zip, gzip]. For additional formats (tar, tgz, bzip, etc.) install the compress extension."); + + String[] arrSources = ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(strSource, ",")); + + Resource[] sources = new Resource[arrSources.length]; + for (int i = 0; i < sources.length; i++) { + sources[i] = ResourceUtil.toResourceExisting(pc, arrSources[i]); + (pc.getConfig()).getSecurityManager().checkFileLocation(sources[i]); + } + + Resource target = ResourceUtil.toResourceExistingParent(pc, srcTarget); + (pc.getConfig()).getSecurityManager().checkFileLocation(target); + + try { + if (sources.length == 1) CompressUtil.compress(format, sources[0], target, includeBaseFolder, -1); + else CompressUtil.compress(format, sources, target, -1); + } + catch (IOException e) { + throw Caster.toPageException(e); + } + return true; + } + +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/functions/system/ExpandPath.java b/core/src/main/java/lucee/runtime/functions/system/ExpandPath.java index 28e9f8fbd3..f700bbbc7c 100644 --- a/core/src/main/java/lucee/runtime/functions/system/ExpandPath.java +++ b/core/src/main/java/lucee/runtime/functions/system/ExpandPath.java @@ -44,6 +44,7 @@ public final class ExpandPath implements Function { private static final long serialVersionUID = 6192659914120397912L; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); public static String call(PageContext pc, String relPath) throws PageException { ConfigWeb config = pc.getConfig(); @@ -74,7 +75,7 @@ public static String call(PageContext pc, String relPath) throws PageException { } // no expand needed - if (!SystemUtil.isWindows() && !sources[0].exists()) { + if (!IS_WINDOWS && !sources[0].exists()) { res = pc.getConfig().getResource(relPath); if (res.exists()) { return toReturnValue(relPath, res); @@ -89,7 +90,7 @@ public static String call(PageContext pc, String relPath) throws PageException { } // no expand needed - else if (!SystemUtil.isWindows()) { + else if (!IS_WINDOWS) { res = pc.getConfig().getResource(relPath); if (res.exists()) { return toReturnValue(relPath, res); @@ -143,7 +144,7 @@ private static String toReturnValue(String realPath, Resource res) { boolean pathEndsWithSep = StringUtil.endsWith(path, pathChar); boolean realEndsWithSep = StringUtil.endsWith(realPath, '/'); - if (!realEndsWithSep && SystemUtil.isWindows()) realEndsWithSep = StringUtil.endsWith(realPath, '\\'); + if (!realEndsWithSep && IS_WINDOWS) realEndsWithSep = StringUtil.endsWith(realPath, '\\'); if (realEndsWithSep) { if (!pathEndsWithSep) path = path + pathChar; @@ -159,7 +160,7 @@ private static String prettifyPath(PageContext pc, String path) { if (path == null) return null; // UNC Path - if (path.startsWith("\\\\") && SystemUtil.isWindows()) { + if (path.startsWith("\\\\") && IS_WINDOWS) { path = path.substring(2); path = path.replace('\\', '/'); return "//" + StringUtil.replace(path, "//", "/", false); diff --git a/core/src/main/java/lucee/runtime/functions/system/Extract.java b/core/src/main/java/lucee/runtime/functions/system/Extract.java new file mode 100644 index 0000000000..bdc1cf782f --- /dev/null +++ b/core/src/main/java/lucee/runtime/functions/system/Extract.java @@ -0,0 +1,73 @@ +/** + * + * 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.runtime.functions.system; + +import java.io.IOException; + +import lucee.commons.io.compress.CompressUtil; +import lucee.commons.io.res.Resource; +import lucee.commons.io.res.util.ResourceUtil; +import lucee.runtime.PageContext; +import lucee.runtime.exp.FunctionException; +import lucee.runtime.exp.PageException; +import lucee.runtime.ext.function.Function; +import lucee.runtime.op.Caster; +import lucee.runtime.type.util.ListUtil; + +/** + * Implements the CFML Function extract (basic zip/gzip support) + */ +public final class Extract implements Function { + + public static boolean call(PageContext pc, String strFormat, String strSource, String srcTarget) throws PageException { + + boolean singleFileFormat = false; + strFormat = strFormat.trim().toLowerCase(); + int format; + if (strFormat.equals("zip")) { + format = CompressUtil.FORMAT_ZIP; + } + else if (strFormat.equals("gzip")) { + format = CompressUtil.FORMAT_GZIP; + singleFileFormat = true; + } + else throw new FunctionException(pc, "extract", 1, "format", + "invalid format definition [" + strFormat + "], valid formats are [zip, gzip]. For additional formats (tar, tgz, bzip, etc.) install the compress extension."); + + String[] arrSources = ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(strSource, ",")); + + Resource[] sources = new Resource[arrSources.length]; + for (int i = 0; i < sources.length; i++) { + sources[i] = ResourceUtil.toResourceExisting(pc, arrSources[i]); + pc.getConfig().getSecurityManager().checkFileLocation(sources[i]); + } + + Resource target = singleFileFormat ? ResourceUtil.toResourceNotExisting(pc, srcTarget) : ResourceUtil.toResourceExisting(pc, srcTarget); + pc.getConfig().getSecurityManager().checkFileLocation(target); + + try { + CompressUtil.extract(format, sources, target); + } + catch (IOException e) { + throw Caster.toPageException(e); + } + return true; + } + +} \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/instrumentation/InstrumentationFactory.java b/core/src/main/java/lucee/runtime/instrumentation/InstrumentationFactory.java index d24c873c19..417e674358 100644 --- a/core/src/main/java/lucee/runtime/instrumentation/InstrumentationFactory.java +++ b/core/src/main/java/lucee/runtime/instrumentation/InstrumentationFactory.java @@ -60,6 +60,10 @@ public final class InstrumentationFactory { private static final String SEP = File.separator; private static final String TOOLS_VERSION = "7u25"; private static final String AGENT_CLASS_NAME = "lucee.runtime.instrumentation.ExternalAgent"; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private static final boolean IS_LINUX = SystemUtil.isLinux(); + private static final boolean IS_SOLARIS = SystemUtil.isSolaris(); + private static final boolean IS_MACOSX = SystemUtil.isMacOSX(); private static Instrumentation _instr; @@ -199,13 +203,13 @@ private static Resource createToolsJar(Config config) throws IOException { Resource dir = getDeployDirectory(config); String os = "bsd"; // used for Mac OS X - if (SystemUtil.isWindows()) { + if (IS_WINDOWS) { os = "windows"; } - else if (SystemUtil.isLinux()) { // not MacOSX + else if (IS_LINUX) { // not MacOSX os = "linux"; } - else if (SystemUtil.isSolaris()) { + else if (IS_SOLARIS) { os = "solaris"; } String name = "tools-" + os + "-" + TOOLS_VERSION + ".jar"; @@ -346,22 +350,22 @@ private static void addAttachIfNecessary(Config config, Log log) { String archBits = (SystemUtil.getJREArch() == SystemUtil.ARCH_64) ? "64" : "32"; // Windows - if (SystemUtil.isWindows()) { + if (IS_WINDOWS) { trgName = "attach.dll"; srcName = "windows" + archBits + "/" + trgName; } // Linux - else if (SystemUtil.isLinux()) { + else if (IS_LINUX) { trgName = "libattach.so"; srcName = "linux" + archBits + "/" + trgName; } // Solaris - else if (SystemUtil.isSolaris()) { + else if (IS_SOLARIS) { trgName = "libattach.so"; srcName = "solaris" + archBits + "/" + trgName; } // Mac OSX - else if (SystemUtil.isMacOSX()) { + else if (IS_MACOSX) { trgName = "libattach.dylib"; srcName = "macosx" + archBits + "/" + trgName; } diff --git a/core/src/main/java/lucee/runtime/op/Caster.java b/core/src/main/java/lucee/runtime/op/Caster.java index 3ee0370d7a..22cbdef4de 100755 --- a/core/src/main/java/lucee/runtime/op/Caster.java +++ b/core/src/main/java/lucee/runtime/op/Caster.java @@ -4651,15 +4651,27 @@ public static Struct toFunctionValues(Object[] args) throws ExpressionException } public static Struct toFunctionValues(Object[] args, int offset, int len) throws ExpressionException { - // TODO nicht sehr optimal - Struct sct = new StructImpl(StructImpl.TYPE_LINKED); + // LDEV-5907 use unsynchronized struct with optimal capacity to avoid resize + Struct sct = new StructImpl(StructImpl.TYPE_LINKED_NOT_SYNC, StructImpl.optimalCapacity(len, 4)); for (int i = offset; i < offset + len; i++) { if (args[i] instanceof FunctionValueImpl) { FunctionValueImpl value = (FunctionValueImpl) args[i]; sct.setEL(value.getNameAsKey(), value.getValue()); } - else throw new ExpressionException( - "Missing argument name, when using named parameters to a function, every parameter must have a name [" + i + ":" + args[i].getClass().getName() + "]."); + else { + String valueStr; + try { + valueStr = args[i] != null ? args[i].toString() : "null"; + } + catch (Exception e) { + valueStr = args[i].getClass().getName(); + } + if (valueStr.length() > 50) valueStr = valueStr.substring(0, 50) + "..."; + throw new ExpressionException( + "Missing argument name, when using named parameters to a function, all parameters must be named. " + + "Argument at position " + (i - offset + 1) + " [" + valueStr + "] is missing a name. " + + "Either name all arguments (e.g., argumentName=value) or use positional arguments only."); + } } return sct; } diff --git a/core/src/main/java/lucee/runtime/op/Decision.java b/core/src/main/java/lucee/runtime/op/Decision.java index dea59e19b4..28cf1e7ab8 100755 --- a/core/src/main/java/lucee/runtime/op/Decision.java +++ b/core/src/main/java/lucee/runtime/op/Decision.java @@ -91,14 +91,18 @@ public final class Decision { * @param value value to test * @return is value a simple value */ - public static boolean isSimpleValue(Object value) { - return (value instanceof Number) || (value instanceof Locale) || (value instanceof TimeZone) || (value instanceof String) || (value instanceof Character) - || (value instanceof Boolean) || (value instanceof Date) || ((value instanceof Castable) && !(value instanceof Objects) && !(value instanceof Collection)); + public final static boolean isSimpleValue(Object value) { + return (value instanceof CharSequence) || (value instanceof Number) || (value instanceof Boolean) || (value instanceof Date) + + || ((value instanceof Castable) && !(value instanceof Objects) && !(value instanceof Collection)) + + || (value instanceof Locale) || (value instanceof TimeZone) || (value instanceof Character); } - public static boolean isSimpleValueLimited(Object value) { - return (value instanceof Number) || (value instanceof Locale) || (value instanceof TimeZone) || (value instanceof String) || (value instanceof Boolean) - || (value instanceof Date); + public final static boolean isSimpleValueLimited(Object value) { + return (value instanceof CharSequence) || (value instanceof Number) || (value instanceof Boolean) || (value instanceof Date) + + || (value instanceof Locale) || (value instanceof TimeZone); } public static boolean isCastableToNumeric(Object o) { diff --git a/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java b/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java index 5c6b882a85..ce99018958 100644 --- a/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java +++ b/core/src/main/java/lucee/runtime/osgi/OSGiUtil.java @@ -893,8 +893,8 @@ private static Resource downloadBundle(CFMLEngineFactory factory, final String s Resource temp = SystemUtil.getTempFile("jar", false); InputStream is = null; try { - is = HTTPDownloader.get( updateUrl, DOWNLOAD_CONNECT_TIMEOUT, DOWNLOAD_READ_TIMEOUT ); - IOUtil.copy( is, temp, true ); + is = HTTPDownloader.get(updateUrl, DOWNLOAD_CONNECT_TIMEOUT, DOWNLOAD_READ_TIMEOUT); + IOUtil.copy(is, temp, true); // extract version and create file with correct name BundleFile bf = BundleFile.getInstance(temp); @@ -913,13 +913,7 @@ private static Resource downloadBundle(CFMLEngineFactory factory, final String s Resource jar = jarDir.getRealResource(symbolicName + "-" + symbolicVersion + ".jar"); try { - HTTPDownloader.downloadToFile( - updateUrl, - ResourceUtil.toFile(jar), - DOWNLOAD_CONNECT_TIMEOUT, - DOWNLOAD_READ_TIMEOUT, - DOWNLOAD_USER_AGENT - ); + HTTPDownloader.downloadToFile(updateUrl, ResourceUtil.toFile(jar), DOWNLOAD_CONNECT_TIMEOUT, DOWNLOAD_READ_TIMEOUT, DOWNLOAD_USER_AGENT); } catch (GeneralSecurityException e) { final String msg = "Download bundle failed for [" + symbolicName + "] in version [" + symbolicVersion + "] from [" + updateUrl @@ -2281,7 +2275,6 @@ public static String[] getBootdelegation() { } if (!StringUtil.isEmpty(bd)) { - bd += ",java.lang,java.lang.*"; bootDelegation = ListUtil.trimItems(ListUtil.listToStringArray(StringUtil.unwrap(bd), ',')); } } diff --git a/core/src/main/java/lucee/runtime/schedule/ExecutionThread.java b/core/src/main/java/lucee/runtime/schedule/ExecutionThread.java index 173e047b92..28a37f3cbf 100755 --- a/core/src/main/java/lucee/runtime/schedule/ExecutionThread.java +++ b/core/src/main/java/lucee/runtime/schedule/ExecutionThread.java @@ -118,13 +118,15 @@ public static void execute(ParentThreasRefThread ptrt, Config config, ScheduleTa HTTPResponse rsp = null; // execute - log.info(logName, "calling URL ->[" + url + "]"); + if (LogUtil.doesInfo(log)) log.info(logName, "calling URL ->[" + url + "]"); try { rsp = HTTPEngine4Impl.get(new URL(url), user, pass, task.getTimeout(), true, charset, null, proxy, headers.toArray(new Header[headers.size()])); if (rsp != null) { int sc = rsp.getStatusCode(); - if (sc >= 200 && sc < 300) log.info(logName, "successfully called URL [" + url + "], response code " + sc); + if (sc >= 200 && sc < 300) { + if (LogUtil.doesInfo(log)) log.info(logName, "successfully called URL [" + url + "], response code " + sc); + } else log.warn(logName, "called URL [" + url + "] returned response code " + sc); } else log.error(logName, "called URL [" + url + "] with no response!"); diff --git a/core/src/main/java/lucee/runtime/security/SecretProviderFactory.java b/core/src/main/java/lucee/runtime/security/SecretProviderFactory.java index 427348d93f..6a43b6f9c5 100644 --- a/core/src/main/java/lucee/runtime/security/SecretProviderFactory.java +++ b/core/src/main/java/lucee/runtime/security/SecretProviderFactory.java @@ -12,6 +12,7 @@ import lucee.runtime.PageContext; import lucee.runtime.config.Config; import lucee.runtime.config.ConfigFactoryImpl; +import lucee.runtime.config.ConfigPro; import lucee.runtime.db.ClassDefinition; import lucee.runtime.dump.DumpData; import lucee.runtime.dump.DumpProperties; @@ -22,6 +23,7 @@ import lucee.runtime.engine.ThreadLocalPageContext; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; +import lucee.runtime.exp.PageRuntimeException; import lucee.runtime.op.Castable; import lucee.runtime.op.Caster; import lucee.runtime.op.OpUtil; @@ -35,7 +37,7 @@ public class SecretProviderFactory { public static SecretProvider getInstance(Config config, String name, Struct data) throws PageException, ClassException, BundleException { ClassDefinition cd; - cd = ConfigFactoryImpl.getClassDefinition(data, "", config.getIdentification()); + cd = ConfigFactoryImpl.getClassDefinition(config, data, "", config.getIdentification()); if (cd.hasClass()) { Struct custom = Caster.toStruct(data.get(KeyConstants._custom, null), null); @@ -57,7 +59,7 @@ public static SecretProvider getInstance(Config config, ClassDefinition 0) { + if (errorVariable != null) { + // Store errors in variable and return without creating archive + pageContext.setVariable(errorVariable, errors); + return; + } + else { + // Throw all errors - build message from error structs + StringBuilder sb = new StringBuilder(); + Iterator it = errors.keySet().iterator(); + Object key; + while (it.hasNext()) { + key = it.next(); + if (sb.length() > 0) sb.append("\n\n"); + Object errorValue = errors.get(key); + if (errorValue instanceof Struct) { + Struct errorStruct = (Struct) errorValue; + sb.append(errorStruct.get("message", "")); + sb.append(", Error Occurred in File ["); + sb.append(key); + Object line = errorStruct.get("line", null); + if (line != null) { + sb.append(":"); + sb.append(line); + } + sb.append("]"); + } + else { + sb.append(errorValue); + } + } + throw new ApplicationException(sb.toString()); + } + } + Resource classRoot = mapping.getClassRootDirectory(); Resource temp = SystemUtil.getTempDirectory().getRealResource("mani-" + IDGenerator.stringId()); Resource mani = temp.getRealResource("META-INF/MANIFEST.MF"); @@ -1028,7 +1071,35 @@ else if (mappingType == MAPPING_CT) { } private void doCompileMapping() throws PageException { - doCompileMapping(MAPPING_REGULAR, getString("admin", action, "virtual").toLowerCase(), getBoolV("stoponerror", true), getBool("ignoreScopes", null)); + String errorVariable = getString("errorVariable", null); + boolean stopOnError = getBoolV("stoponerror", true); + + // errorVariable implies stopOnError=false + if (errorVariable != null) stopOnError = false; + + Struct errors = null; + if (!stopOnError) errors = new StructImpl(StructImpl.TYPE_LINKED); + + doCompileMapping(MAPPING_REGULAR, getString("admin", action, "virtual").toLowerCase(), stopOnError, getBool("ignoreScopes", null), errors); + + // Set errorVariable or throw if errors occurred + if (errors != null && errors.size() > 0) { + if (errorVariable != null) { + pageContext.setVariable(errorVariable, errors); + } + else { + // Build error message from all collected errors + StringBuilder sb = new StringBuilder(); + sb.append("Compilation failed with ").append(errors.size()).append(" error(s):\n"); + for (Key key: errors.keys()) { + Struct errorInfo = (Struct) errors.get(key, null); + sb.append("\n").append(key.getString()).append(": "); + sb.append(errorInfo.get("message", "Unknown error")); + } + throw new ApplicationException(sb.toString()); + } + } + adminSync.broadcast(attributes, config); } @@ -1043,6 +1114,10 @@ private void doCompileCTMapping() throws PageException { } private Mapping doCompileMapping(short mappingType, String virtual, boolean stoponerror, Boolean ignoreScopes) throws PageException { + return doCompileMapping(mappingType, virtual, stoponerror, ignoreScopes, null); + } + + private Mapping doCompileMapping(short mappingType, String virtual, boolean stoponerror, Boolean ignoreScopes, Struct errors) throws PageException { if (StringUtil.isEmpty(virtual)) return null; @@ -1057,9 +1132,10 @@ private Mapping doCompileMapping(short mappingType, String virtual, boolean stop for (int i = 0; i < mappings.length; i++) { Mapping mapping = mappings[i]; if (mapping.getVirtualLowerCaseWithSlash().equals(virtual)) { - Map errors = stoponerror ? null : MapFactory.getConcurrentMap(); + boolean errorsPassedIn = errors != null; + if (errors == null) errors = stoponerror ? null : new StructImpl(StructImpl.TYPE_LINKED); doCompileFile(mapping, mapping.getPhysical(), "", errors, ignoreScopes); - if (errors != null && errors.size() > 0) { + if (errors != null && errors.size() > 0 && !errorsPassedIn) { StringBuilder sb = new StringBuilder(); Iterator it = errors.keySet().iterator(); Object key; @@ -1077,7 +1153,7 @@ private Mapping doCompileMapping(short mappingType, String virtual, boolean stop return null; } - private void doCompileFile(Mapping mapping, Resource file, String path, Map errors, Boolean explicitIgnoreScope) throws PageException { + private void doCompileFile(Mapping mapping, Resource file, String path, Struct errors, Boolean explicitIgnoreScope) throws PageException { if (ResourceUtil.exists(file)) { if (file.isDirectory()) { Resource[] files = file.listResources(FILTER_CFML_TEMPLATES); @@ -1101,26 +1177,51 @@ else if (file.isFile()) { catch (PageException pe) { LogUtil.log((pageContext), Admin.class.getName(), pe); String template = ps.getDisplayPath(); - StringBuilder msg = new StringBuilder(pe.getMessage()); - msg.append(", Error Occurred in File ["); - msg.append(template); - if (pe instanceof PageExceptionImpl) { - try { - PageExceptionImpl pei = (PageExceptionImpl) pe; - Array context = pei.getTagContext(config); - if (context.size() > 0) { - msg.append(":"); - msg.append(Caster.toString(((Struct) context.getE(1)).get("line"))); + + if (errors != null) { + // Store structured error information + Struct errorInfo = new StructImpl(); + errorInfo.set("message", pe.getMessage()); + errorInfo.set("detail", pe.getDetail()); + + if (pe instanceof PageExceptionImpl) { + try { + PageExceptionImpl pei = (PageExceptionImpl) pe; + Array context = pei.getTagContext(config); + if (context.size() > 0) { + Struct firstContext = (Struct) context.getE(1); + errorInfo.set("line", firstContext.get("line", null)); + errorInfo.set("column", firstContext.get("column", null)); + } + } + catch (Throwable t) { + ExceptionUtil.rethrowIfNecessary(t); } - } - catch (Throwable t) { - ExceptionUtil.rethrowIfNecessary(t); } + errors.put(template, errorInfo); + } + else { + // Original behavior for throwing + StringBuilder msg = new StringBuilder(pe.getMessage()); + msg.append(", Error Occurred in File ["); + msg.append(template); + if (pe instanceof PageExceptionImpl) { + try { + PageExceptionImpl pei = (PageExceptionImpl) pe; + Array context = pei.getTagContext(config); + if (context.size() > 0) { + msg.append(":"); + msg.append(Caster.toString(((Struct) context.getE(1)).get("line"))); + } + } + catch (Throwable t) { + ExceptionUtil.rethrowIfNecessary(t); + } + } + msg.append("]"); + throw new ApplicationException(msg.toString()); } - msg.append("]"); - if (errors != null) errors.put(template, msg.toString()); - else throw new ApplicationException(msg.toString()); } finally { diff --git a/core/src/main/java/lucee/runtime/tag/Directory.java b/core/src/main/java/lucee/runtime/tag/Directory.java index 62165ccfb5..61fdfbda03 100755 --- a/core/src/main/java/lucee/runtime/tag/Directory.java +++ b/core/src/main/java/lucee/runtime/tag/Directory.java @@ -26,6 +26,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.DosFileAttributes; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -86,6 +87,8 @@ public final class Directory extends TagImpl { private static final Key DATE_LAST_MODIFIED = KeyConstants._dateLastModified; private static final Key ATTRIBUTES = KeyConstants._attributes; private static final Key DIRECTORY = KeyConstants._directory; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private static final boolean IS_UNIX = SystemUtil.isUnix(); public static final int LIST_INFO_QUERY_ALL = 1; public static final int LIST_INFO_QUERY_NAME = 2; @@ -107,6 +110,7 @@ public final class Directory extends TagImpl { /** The name of the directory to perform the action against. */ private Resource directory; + private String strDirectory; /** Defines the action to be taken with directory(ies) specified in directory. */ private String action = "list"; @@ -165,6 +169,7 @@ public void release() { filter = null; destination = null; directory = null; + strDirectory = null; action = "list"; sort = null; mode = -1; @@ -285,9 +290,8 @@ public void setListinfo(String strListinfo) { * @param directory value to set **/ public void setDirectory(String directory) { - + this.strDirectory = directory; this.directory = ResourceUtil.toResourceNotExisting(pageContext, directory); - // print.ln(this.directory); } /** @@ -383,9 +387,16 @@ public int doStartTag() throws PageException { if (!StringUtil.isEmpty(name) && res != null) pageContext.setVariable(name, res); } else if (action.equals("create")) actionCreate(pageContext, directory, serverPassword, createPath, mode, acl, storage, nameconflict); - else if (action.equals("delete")) actionDelete(pageContext, directory, recurse, serverPassword); - else if (action.equals("forcedelete")) actionDelete(pageContext, directory, true, serverPassword); + else if (action.equals("delete")) { + if (StringUtil.isEmpty(strDirectory, true)) throw new ApplicationException("The attribute [directory] is required for action [delete]"); + actionDelete(pageContext, directory, recurse, serverPassword); + } + else if (action.equals("forcedelete")) { + if (StringUtil.isEmpty(strDirectory, true)) throw new ApplicationException("The attribute [directory] is required for action [forcedelete]"); + actionDelete(pageContext, directory, true, serverPassword); + } else if (action.equals("rename")) { + if (StringUtil.isEmpty(strDirectory, true)) throw new ApplicationException("The attribute [directory] is required for action [rename]"); String res = actionRename(pageContext, directory, strNewdirectory, serverPassword, createPath, acl, storage); if (!StringUtil.isEmpty(name) && res != null) pageContext.setVariable(name, res); } @@ -535,8 +546,8 @@ public static Struct getInfo(PageContext pc, Resource directory, String serverPa sct.setEL(KeyConstants._size, Long.valueOf(directory.length())); sct.setEL("isReadable", directory.isReadable()); sct.setEL(KeyConstants._path, directory.getAbsolutePath()); - - if (SystemUtil.isUnix()) sct.setEL(KeyConstants._mode, new ModeObjectWrap(directory)); + + if (IS_UNIX) sct.setEL(KeyConstants._mode, new ModeObjectWrap(directory)); File file = new File(Caster.toString(directory)); BasicFileAttributes attr; try { @@ -571,9 +582,7 @@ private static int _fillQueryAll(Query query, Resource directory, ResourceFilter query.setAt(MODE, count, new ModeObjectWrap(list[i])); } query.setAt(DATE_LAST_MODIFIED, count, new Date(list[i].lastModified())); - // TODO File Attributes are Windows only... - // this is slow as it fetches each the attributes one at a time - query.setAt(ATTRIBUTES, count, getFileAttribute(list[i], true)); + query.setAt(ATTRIBUTES, count, getFileAttribute(list[i])); if (hasMeta) { query.setAt(META, count, ((ResourceMetaData) list[i]).getMetaData()); @@ -604,9 +613,7 @@ private static int _fillQueryAll(Query query, Resource directory, ResourceFilter query.setAt(MODE, count, new ModeObjectWrap(list[i])); } query.setAt(DATE_LAST_MODIFIED, count, new Date(list[i].lastModified())); - // TODO File Attributes are Windows only... - // this is slow as it fetches each the attributes one at a time - query.setAt(ATTRIBUTES, count, getFileAttribute(list[i], true)); + query.setAt(ATTRIBUTES, count, getFileAttribute(list[i])); if (hasMeta) { query.setAt(META, count, ((ResourceMetaData) list[i]).getMetaData()); @@ -939,13 +946,33 @@ private static Resource toDestination(PageContext pageContext, String path, Reso return ResourceUtil.toResourceNotExisting(pageContext, path); } - public static String getFileAttribute(Resource file, boolean exists) { - // TODO this is slow as it fetches attributes one at a time - // also Windows only! - return exists && !file.isWriteable() ? "R".concat(file.isHidden() ? "H" : "") : file.isHidden() ? "H" : ""; + public static String getFileAttribute(Resource file) { + // Windows-only attributes (R=ReadOnly, H=Hidden). On Unix, return empty string to avoid unnecessary syscalls. + if (!IS_WINDOWS) return ""; + + // Optimized: fetch both attributes in a single syscall for FileResource on Windows + if (file instanceof FileResource) { + try { + DosFileAttributes attrs = Files.readAttributes(((FileResource) file).toPath(), DosFileAttributes.class); + boolean isReadOnly = attrs.isReadOnly(); + boolean isHidden = attrs.isHidden(); + return isReadOnly ? "R".concat(isHidden ? "H" : "") : isHidden ? "H" : ""; + } + catch (IOException e) { + // Fall through to legacy implementation if DosFileAttributes not supported + } + } + // Fallback for non-FileResource or if DosFileAttributes fails + // Cache isHidden() result to avoid calling it twice + boolean hidden = file.isHidden(); + boolean writable = file.isWriteable(); + return !writable ? "R".concat(hidden ? "H" : "") : hidden ? "H" : ""; } - public static String getFileAttribute(Path path, boolean exists) { + public static String getFileAttribute(Path path) { + // Windows-only attributes (R=ReadOnly, H=Hidden). On Unix, return empty string to avoid unnecessary syscalls. + if (!IS_WINDOWS) return ""; + String hidden; try { hidden = Files.isHidden(path) ? "H" : ""; @@ -953,7 +980,7 @@ public static String getFileAttribute(Path path, boolean exists) { catch (IOException e) { hidden = ""; } - return exists && !Files.isWritable(path) ? "R".concat(hidden) : hidden; + return !Files.isWritable(path) ? "R".concat(hidden) : hidden; } /** diff --git a/core/src/main/java/lucee/runtime/tag/FileTag.java b/core/src/main/java/lucee/runtime/tag/FileTag.java index 40e5bb9499..3b979bb3c7 100755 --- a/core/src/main/java/lucee/runtime/tag/FileTag.java +++ b/core/src/main/java/lucee/runtime/tag/FileTag.java @@ -98,6 +98,8 @@ public final class FileTag extends BodyTagImpl { private static final int ACTION_UPLOAD = 5; private static final int ACTION_UPLOAD_ALL = 6; private static final int ACTION_COPY = 7; + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private static final boolean IS_UNIX = SystemUtil.isUnix(); private static final int ACTION_INFO = 8; private static final int ACTION_TOUCH = 9; private static final int ACTION_DELETE = 10; @@ -858,8 +860,8 @@ public static Struct getInfo(PageContext pc, Resource file, String serverPasswor catch (Exception e) { } sct.setEL(KeyConstants._dateLastModified, new DateTimeImpl(file.lastModified())); - sct.setEL(KeyConstants._attributes, getFileAttribute(file)); - if (SystemUtil.isUnix()) sct.setEL(KeyConstants._mode, new ModeObjectWrap(file)); + sct.setEL(KeyConstants._attributes, Directory.getFileAttribute(file)); + if (IS_UNIX) sct.setEL(KeyConstants._mode, new ModeObjectWrap(file)); try { sct.setEL(KeyConstants._checksum, Hash.md5(file)); @@ -877,10 +879,6 @@ public static Struct getInfo(PageContext pc, Resource file, String serverPasswor return sct; } - private static String getFileAttribute(Resource file) { - return file.exists() && !file.isWriteable() ? "R".concat(file.isHidden() ? "H" : "") : file.isHidden() ? "H" : ""; - } - /** * read source file * @@ -1252,7 +1250,7 @@ private static boolean checkFile(PageContext pc, SecurityManager sm, Resource fi * @throws PageException */ private static void setAttributes(Resource file, String attributes) throws PageException { - if (!SystemUtil.isWindows() || StringUtil.isEmpty(attributes)) return; + if (!IS_WINDOWS || StringUtil.isEmpty(attributes)) return; try { ResourceUtil.setAttribute(file, attributes); } @@ -1268,7 +1266,7 @@ private static void setAttributes(Resource file, String attributes) throws PageE * @throws ApplicationException */ private static void setMode(Resource file, int mode) throws ApplicationException { - if (mode == -1 || SystemUtil.isWindows()) return; + if (mode == -1 || IS_WINDOWS) return; try { file.setMode(mode); // FileUtil.setMode(file,mode); diff --git a/core/src/main/java/lucee/runtime/tag/Http.java b/core/src/main/java/lucee/runtime/tag/Http.java index c5048e7381..a519988941 100644 --- a/core/src/main/java/lucee/runtime/tag/Http.java +++ b/core/src/main/java/lucee/runtime/tag/Http.java @@ -43,7 +43,9 @@ import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; import org.apache.http.client.methods.HttpOptions; import org.apache.http.client.methods.HttpPatch; @@ -68,6 +70,7 @@ 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.io.res.Resource; import lucee.commons.io.res.util.ResourceUtil; import lucee.commons.lang.ExceptionUtil; @@ -840,17 +843,43 @@ else if (cacheHandler != null) { // TODO this else block can be removed when all HttpEntityEnclosingRequest eeReqPost = null; HttpEntityEnclosingRequest eeReq = null; + // Note: We need to determine if GET/DELETE will have a body by checking params + // If no body is needed, use standard HttpGet/HttpDelete for better server compatibility + // Some servers (like Cloudflare) refuse compression for non-standard GET requests with body support + boolean needsBodyForGet = false; + boolean needsBodyForDelete = false; + + // Check if any params require a body for GET/DELETE + for (int i = 0; i < len; i++) { + HttpParamBean param = this.params.get(i); + int type = param.getType(); + if (type == HttpParamBean.TYPE_XML || type == HttpParamBean.TYPE_BODY) { + if (this.method == METHOD_GET) needsBodyForGet = true; + if (this.method == METHOD_DELETE) needsBodyForDelete = true; + } + } + if (this.method == METHOD_GET) { - req = new HttpGetWithBody(url); - eeReq = (HttpEntityEnclosingRequest) req; + if (needsBodyForGet) { + req = new HttpGetWithBody(url); + eeReq = (HttpEntityEnclosingRequest) req; + } + else { + req = new HttpGet(url); + } } else if (this.method == METHOD_HEAD) { req = new HttpHead(url); } else if (this.method == METHOD_DELETE) { isBinary = true; - req = new HttpDeleteWithBody(url); - eeReq = (HttpEntityEnclosingRequest) req; + if (needsBodyForDelete) { + req = new HttpDeleteWithBody(url); + eeReq = (HttpEntityEnclosingRequest) req; + } + else { + req = new HttpDelete(url); + } } else if (this.method == METHOD_PUT) { isBinary = true; @@ -1649,12 +1678,13 @@ private static void logHttpRequest(PageContext pc, Struct data, String url, Stri Log log = ThreadLocalPageContext.getLog(pc, "http"); if (log == null) log = ThreadLocalPageContext.getLog(pc, "application"); if (log != null) { - String msg = "httpRequest [" + method + "] to [" + url + "], returned [" + data.get(STATUSCODE) + "] in " + (executionTimeNS / 1000000) + "ms, " - + (cached ? "(cached response)" : "") + " at " + CallStackGet.call(pc, "text"); - - if (t != null) log.error("cfhttp", msg, t); - else log.info("cfhttp", msg); + if (t != null || LogUtil.doesInfo(log)) { + String msg = "httpRequest [" + method + "] to [" + url + "], returned [" + data.get(STATUSCODE) + "] in " + (executionTimeNS / 1000000) + "ms, " + + (cached ? "(cached response)" : "") + " at " + CallStackGet.call(pc, "text"); + if (t != null) log.error("cfhttp", msg, t); + else log.info("cfhttp", msg); + } } } diff --git a/core/src/main/java/lucee/runtime/tag/Insert.java b/core/src/main/java/lucee/runtime/tag/Insert.java index 150a268500..979bbef48c 100755 --- a/core/src/main/java/lucee/runtime/tag/Insert.java +++ b/core/src/main/java/lucee/runtime/tag/Insert.java @@ -25,6 +25,7 @@ import lucee.commons.db.DBUtil; import lucee.commons.io.log.Log; +import lucee.commons.io.log.LogUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContextImpl; import lucee.runtime.config.ConfigPro; @@ -225,7 +226,7 @@ public int doEndTag() throws PageException { // log Log log = ThreadLocalPageContext.getLog(pageContext, "datasource"); - if (log.getLogLevel() >= Log.LEVEL_INFO) { + if (LogUtil.doesInfo(log)) { log.info("insert tag", "executed [" + sql.toString().trim() + "] in " + DecimalFormat.call(pageContext, query.getExecutionTime() / 1000000D) + " ms"); } } diff --git a/core/src/main/java/lucee/runtime/tag/NTAuthenticate.java b/core/src/main/java/lucee/runtime/tag/NTAuthenticate.java index 81a29c4fe9..e023f40331 100755 --- a/core/src/main/java/lucee/runtime/tag/NTAuthenticate.java +++ b/core/src/main/java/lucee/runtime/tag/NTAuthenticate.java @@ -27,6 +27,8 @@ public final class NTAuthenticate extends TagImpl { + private static final boolean IS_WINDOWS = SystemUtil.isWindows(); + private String username; private String password; private String domain; @@ -103,7 +105,7 @@ public int doStartTag() throws PageException { Struct resultSt = new StructImpl(); pageContext.setVariable(result, resultSt); - if (SystemUtil.isWindows()) { + if (IS_WINDOWS) { /* * * NTAuthentication ntauth = new NTAuthentication(domain); if(username != null) diff --git a/core/src/main/java/lucee/runtime/tag/Property.java b/core/src/main/java/lucee/runtime/tag/Property.java index 349ec306ef..58d0c2a160 100755 --- a/core/src/main/java/lucee/runtime/tag/Property.java +++ b/core/src/main/java/lucee/runtime/tag/Property.java @@ -147,7 +147,10 @@ public int doStartTag() throws PageException { if (pageContext.variablesScope() instanceof ComponentScope) { Component comp = ((ComponentScope) pageContext.variablesScope()).getComponent(); comp.setProperty(property); - property.setOwnerName(comp.getAbsName()); + // LDEV-3335: Only set owner if not already set (e.g., from parent component) + if (property.getOwnerPageSource() == null) { + property.setOwnerName(comp.getAbsName(), comp.getPageSource()); + } } return SKIP_BODY; diff --git a/core/src/main/java/lucee/runtime/tag/Query.java b/core/src/main/java/lucee/runtime/tag/Query.java index 42d475cf44..950aae03ec 100755 --- a/core/src/main/java/lucee/runtime/tag/Query.java +++ b/core/src/main/java/lucee/runtime/tag/Query.java @@ -698,7 +698,7 @@ else if (!StringUtil.isEmpty(data.name)) { } if (data.result != null) { long time = System.nanoTime() - start; - Struct sct = new StructImpl(); + Struct sct = new StructImpl(Struct.TYPE_REGULAR, 8); sct.setEL(KeyConstants._cached, Boolean.FALSE); sct.setEL(KeyConstants._executionTime, Caster.toDouble(time / 1000000)); sct.setEL(KeyConstants._executionTimeNano, Caster.toDouble(time)); @@ -764,7 +764,7 @@ else if ((queryResult.getColumncount() + queryResult.getRecordcount()) > 0 && !S // log Log log = ThreadLocalPageContext.getLog(pageContext, "datasource"); - if (log.getLogLevel() >= Log.LEVEL_INFO) { + if (LogUtil.doesInfo(log)) { log.info("query tag", "executed [" + sqlQuery.toString().trim() + "] in " + DecimalFormat.call(pageContext, exe / 1000000D) + " ms"); } } @@ -801,7 +801,7 @@ private static void validate(SQL sql) throws PageException { private static Struct createMetaData(PageContext pageContext, QueryBean data, QueryResult queryResult, SQL sqlQuery, boolean setVars, long exe) throws PageException { Struct meta; if (data.result != null && queryResult != null) { - meta = new StructImpl(); + meta = new StructImpl(Struct.TYPE_REGULAR, 16); boolean fns = ((PageContextImpl) pageContext).getFullNullSupport(); meta.setEL(KeyConstants._cached, Caster.toBoolean(queryResult.isCached())); @@ -848,7 +848,7 @@ private static Struct createMetaData(PageContext pageContext, QueryBean data, Qu if (sqlQuery != null) { SQLItem[] params = sqlQuery.getItems(); if (params != null && params.length > 0) { - Array arr = new ArrayImpl(); + Array arr = new ArrayImpl(params.length); meta.setEL(KeyConstants._sqlparameters, arr); for (int i = 0; i < params.length; i++) { if (fns) arr.append(params[i].isNulls() ? null : params[i].getValue()); @@ -875,8 +875,8 @@ private static void callAfter(PageContext pc, QueryBean data, String strSQL, Tem } private static Struct createArgStruct(PageContext pc, QueryBean data, String strSQL, TemplateLine tl) throws PageException { - Struct rtn = new StructImpl(Struct.TYPE_LINKED); - Struct args = new StructImpl(Struct.TYPE_LINKED); + Struct rtn = new StructImpl(Struct.TYPE_LINKED, 4); + Struct args = new StructImpl(Struct.TYPE_LINKED, 32); boolean fns = ((PageContextImpl) pc).getFullNullSupport(); @@ -905,7 +905,7 @@ private static Struct createArgStruct(PageContext pc, QueryBean data, String str set(args, "params", data.params); } else if (data.items != null) { - Array params = new ArrayImpl(); + Array params = new ArrayImpl(data.items.size()); Iterator it = data.items.iterator(); SQLItem item; while (it.hasNext()) { @@ -1051,7 +1051,7 @@ private PageSource getPageSource() { } private static Struct setExecutionTime(PageContext pc, long exe) { - Struct sct = new StructImpl(); + Struct sct = new StructImpl(Struct.TYPE_REGULAR, 2); sct.setEL(KeyConstants._executionTime, Double.valueOf(exe)); if (USE_LOCAL_SCOPE && pc.undefinedScope().getCheckArguments()) pc.localScope().setEL(KeyConstants._cfquery, sct); else pc.undefinedScope().setEL(KeyConstants._cfquery, sct); @@ -1060,14 +1060,14 @@ private static Struct setExecutionTime(PageContext pc, long exe) { private static Object executeORM(PageContext pageContext, QueryBean data, SQL sql, int returnType, Struct ormoptions) throws PageException { ORMSession session = ORMUtil.getSession(pageContext); - if (ormoptions == null) ormoptions = new StructImpl(); + if (ormoptions == null) ormoptions = new StructImpl(Struct.TYPE_REGULAR, 4); String dsn = null; if (ormoptions != null) dsn = Caster.toString(ormoptions.get(KeyConstants._datasource, null), null); if (StringUtil.isEmpty(dsn, true)) dsn = ORMUtil.getDefaultDataSource(pageContext).getName(); // params SQLItem[] _items = sql.getItems(); - Array params = new ArrayImpl(); + Array params = new ArrayImpl(_items.length); for (int i = 0; i < _items.length; i++) { params.appendEL(_items[i]); } diff --git a/core/src/main/java/lucee/runtime/tag/StoredProc.java b/core/src/main/java/lucee/runtime/tag/StoredProc.java index 50b0135a99..8a74395f95 100755 --- a/core/src/main/java/lucee/runtime/tag/StoredProc.java +++ b/core/src/main/java/lucee/runtime/tag/StoredProc.java @@ -36,6 +36,7 @@ import lucee.commons.io.IOUtil; 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; @@ -254,9 +255,9 @@ public void setDbtype(String dbtype) { } public void addProcParam(ProcParamBean param) { - - if (getLog().getLogLevel() >= Log.LEVEL_DEBUG) { // log entry added to troubleshoot LDEV-1147 - getLog().debug("StoredProc", String.format(" param [%s] %s = %s", SQLCaster.toStringType(param.getType(), "?"), param.getVariable(), param.getValue())); + Log log = getLog(); + if (LogUtil.doesDebug(log)) { // log entry added to troubleshoot LDEV-1147 + log.debug("StoredProc", String.format(" param [%s] %s = %s", SQLCaster.toStringType(param.getType(), "?"), param.getVariable(), param.getValue())); } params.add(param); } @@ -370,14 +371,11 @@ else if (parts.length == 3) { ResultSet procColumns = conn.getMetaData().getProcedureColumns(_objName, _owner, _procName, null); procParams = getProcMetaCollection(procColumns); if (procParams != null) procParamsCache.put(cacheId, new SoftReference(procParams)); - - if (getLog().getLogLevel() >= Log.LEVEL_DEBUG) { // log entry added to troubleshoot LDEV-1147 - getLog().debug("StoredProc", "PROC OBJECT_ID: " + resultSet.getInt("OBJECT_ID")); - } } else { - if (getLog().getLogLevel() >= Log.LEVEL_INFO) - getLog().info(StoredProc.class.getSimpleName(), "procedure " + procedure + " not found in view ALL_PROCEDURES"); + Log log = getLog(); + if (LogUtil.doesInfo(log)) + log.info(StoredProc.class.getSimpleName(), "procedure " + procedure + " not found in view ALL_PROCEDURES"); } } @@ -492,7 +490,8 @@ private ProcMetaCollection getProcMetaCollection(ResultSet rsProcColumns) throws IOUtil.close(rsProcColumns); } - if (getLog().getLogLevel() >= Log.LEVEL_DEBUG) { + Log log = getLog(); + if (LogUtil.doesDebug(log)) { StringBuilder sb = new StringBuilder(64); Iterator>> it = allProcs.entrySet().iterator(); while (it.hasNext()) { @@ -504,7 +503,7 @@ private ProcMetaCollection getProcMetaCollection(ResultSet rsProcColumns) throws sb.append(ProcMetaCollection.getParamTypeList(e.getValue())); sb.append(")}"); } - getLog().debug("StoredProc", sb.toString()); + log.debug("StoredProc", sb.toString()); } ProcMetaCollection result = null; @@ -567,9 +566,9 @@ public int doEndTag() throws PageException { SQLImpl _sql = new SQLImpl(sql); CallableStatement callStat = null; try { - - if (getLog().getLogLevel() >= Log.LEVEL_DEBUG) // log entry added to troubleshoot LDEV-1147 - getLog().debug("StoredProc", sql + " [" + params.size() + " params]"); + Log log = getLog(); + if (LogUtil.doesDebug(log)) // log entry added to troubleshoot LDEV-1147 + log.debug("StoredProc", sql + " [" + params.size() + " params]"); callStat = dc.getConnection().prepareCall(sql); if (blockfactor > 0) callStat.setFetchSize(blockfactor); @@ -705,8 +704,8 @@ else if (cacheValue instanceof Struct) { if (logdb) pageContext.getDebugger().addQuery(null, dsn, procedure, _sql, count, pageContext.getCurrentPageSource(), (int) exe); } - if (getLog().getLogLevel() >= Log.LEVEL_INFO) { - getLog().info(StoredProc.class.getSimpleName(), "executed [" + sql.trim() + "] in " + DecimalFormat.call(pageContext, exe / 1000000D) + " ms"); + if (LogUtil.doesInfo(log)) { + log.info(StoredProc.class.getSimpleName(), "executed [" + sql.trim() + "] in " + DecimalFormat.call(pageContext, exe / 1000000D) + " ms"); } } catch (SQLException e) { diff --git a/core/src/main/java/lucee/runtime/tag/TagUtil.java b/core/src/main/java/lucee/runtime/tag/TagUtil.java index 7c0f746be0..d244ce0ffd 100755 --- a/core/src/main/java/lucee/runtime/tag/TagUtil.java +++ b/core/src/main/java/lucee/runtime/tag/TagUtil.java @@ -391,7 +391,8 @@ public static TagLibTag getTagLibTag(ConfigPro config, String nameSpace, String public static void handleListener(PageContext pc, Object result, PageException pe, Object listener) throws PageException { if (listener instanceof UDF) { - Struct args = new StructImpl(StructImpl.TYPE_LINKED); + // LDEV-5907 use unsynchronized struct for temporary args passed to callWithNamedValues + Struct args = new StructImpl(StructImpl.TYPE_LINKED_NOT_SYNC, 4); if (result != null) args.set(KeyConstants._result, result); if (pe != null) args.set(KeyConstants._error, pe.getCatchBlock(pc.getConfig())); ((UDF) listener).callWithNamedValues(pc, args, true); @@ -402,7 +403,8 @@ else if (listener instanceof Component) { // result if (result != null) { if (cfc.contains(pc, KeyConstants._onSuccess)) { - Struct args = new StructImpl(StructImpl.TYPE_LINKED); + // LDEV-5907 use unsynchronized struct for temporary args passed to callWithNamedValues + Struct args = new StructImpl(StructImpl.TYPE_LINKED_NOT_SYNC, 4); args.set(KeyConstants._result, result); cfc.callWithNamedValues(pc, KeyConstants._onSuccess, args); } @@ -411,7 +413,8 @@ else if (listener instanceof Component) { if (pe != null) { boolean second = false; if (cfc.contains(pc, KeyConstants._onFail) || (second = cfc.contains(pc, KeyConstants._onError))) { - Struct args = new StructImpl(StructImpl.TYPE_LINKED); + // LDEV-5907 use unsynchronized struct for temporary args passed to callWithNamedValues + Struct args = new StructImpl(StructImpl.TYPE_LINKED_NOT_SYNC, 4); args.set(KeyConstants._error, pe.getCatchBlock(pc.getConfig())); cfc.callWithNamedValues(pc, second ? KeyConstants._onError : KeyConstants._onFail, args); } @@ -425,7 +428,8 @@ else if (listener instanceof Struct) { if (result != null) { UDF onSuccess = Caster.toFunction(coll.get(KeyConstants._onSuccess, null), null); if (onSuccess != null) { - Struct args = new StructImpl(StructImpl.TYPE_LINKED); + // LDEV-5907 use unsynchronized struct for temporary args passed to callWithNamedValues + Struct args = new StructImpl(StructImpl.TYPE_LINKED_NOT_SYNC, 4); args.set(KeyConstants._result, result); onSuccess.callWithNamedValues(pc, args, true); } @@ -435,7 +439,8 @@ else if (listener instanceof Struct) { UDF onError = Caster.toFunction(coll.get(KeyConstants._onFail, null), null); if (onError == null) onError = Caster.toFunction(coll.get(KeyConstants._onError, null), null); if (onError != null) { - Struct args = new StructImpl(StructImpl.TYPE_LINKED); + // LDEV-5907 use unsynchronized struct for temporary args passed to callWithNamedValues + Struct args = new StructImpl(StructImpl.TYPE_LINKED_NOT_SYNC, 4); args.set(KeyConstants._error, pe.getCatchBlock(pc.getConfig())); onError.callWithNamedValues(pc, args, true); } diff --git a/core/src/main/java/lucee/runtime/tag/Update.java b/core/src/main/java/lucee/runtime/tag/Update.java index 8a873d6cf8..bdda69df65 100755 --- a/core/src/main/java/lucee/runtime/tag/Update.java +++ b/core/src/main/java/lucee/runtime/tag/Update.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import lucee.commons.io.log.Log; +import lucee.commons.io.log.LogUtil; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContextImpl; @@ -225,7 +226,7 @@ public int doEndTag() throws PageException { // log Log log = ThreadLocalPageContext.getLog(pageContext, "datasource"); - if (log.getLogLevel() >= Log.LEVEL_INFO) { + if (LogUtil.doesInfo(log)) { log.info("update tag", "executed [" + sql.toString().trim() + "] in " + DecimalFormat.call(pageContext, query.getExecutionTime() / 1000000D) + " ms"); } } diff --git a/core/src/main/java/lucee/runtime/tag/util/QueryParamConverter.java b/core/src/main/java/lucee/runtime/tag/util/QueryParamConverter.java index 351fe4852e..632ce243c5 100644 --- a/core/src/main/java/lucee/runtime/tag/util/QueryParamConverter.java +++ b/core/src/main/java/lucee/runtime/tag/util/QueryParamConverter.java @@ -109,7 +109,7 @@ public static SQL convert(String sql, Array params) throws PageException { } public static Struct toStruct(SQLItem item, boolean fns) { - Struct sct = new StructImpl(); + Struct sct = new StructImpl(Struct.TYPE_REGULAR, 8); if (item instanceof NamedSQLItem) { NamedSQLItem nsi = (NamedSQLItem) item; sct.setEL(KeyConstants._name, nsi.getName()); diff --git a/core/src/main/java/lucee/runtime/text/xml/XMLUtil.java b/core/src/main/java/lucee/runtime/text/xml/XMLUtil.java index 4703321150..929a7b719b 100755 --- a/core/src/main/java/lucee/runtime/text/xml/XMLUtil.java +++ b/core/src/main/java/lucee/runtime/text/xml/XMLUtil.java @@ -258,7 +258,8 @@ private static TransformerFactory _newTransformerFactory() { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + Log log = ThreadLocalPageContext.getLog("application"); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } } return TransformerFactory.newInstance(); @@ -271,6 +272,7 @@ private static Class _newTransformerFactoryClass() { Thread.currentThread().setContextClassLoader(EnvClassLoader.getInstance((ConfigPro) ThreadLocalPageContext.getConfig())); Class clazz = null; + Log log = ThreadLocalPageContext.getLog("application"); try { Class c = ClassUtil.loadClass("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"); @@ -279,7 +281,7 @@ private static Class _newTransformerFactoryClass() { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } if (clazz == null) { @@ -290,7 +292,7 @@ private static Class _newTransformerFactoryClass() { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } } @@ -493,7 +495,8 @@ private static DocumentBuilderFactory _newDocumentBuilderFactory() { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + Log log = ThreadLocalPageContext.getLog("application"); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } } return DocumentBuilderFactory.newInstance(); @@ -506,6 +509,7 @@ private static Class _newDocumentBuilderFactoryClass() { Thread.currentThread().setContextClassLoader(EnvClassLoader.getInstance((ConfigPro) ThreadLocalPageContext.getConfig())); Class clazz = null; + Log log = ThreadLocalPageContext.getLog("application"); try { Class c = ClassUtil.loadClass("com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"); @@ -514,7 +518,7 @@ private static Class _newDocumentBuilderFactoryClass() { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } if (clazz == null) { @@ -525,7 +529,7 @@ private static Class _newDocumentBuilderFactoryClass() { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } } @@ -565,12 +569,13 @@ public static URL getSAXParserFactoryResource() throws IOException { public static XMLReader createXMLReader() throws SAXException { Thread.currentThread().setContextClassLoader(EnvClassLoader.getInstance((ConfigPro) ThreadLocalPageContext.getConfig())); + Log log = ThreadLocalPageContext.getLog("application"); try { return XMLReaderFactory.createXMLReader("com.sun.org.apache.xerces.internal.parsers.SAXParser"); } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } try { @@ -578,7 +583,7 @@ public static XMLReader createXMLReader() throws SAXException { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } try { @@ -586,7 +591,7 @@ public static XMLReader createXMLReader() throws SAXException { } catch (Throwable t) { ExceptionUtil.rethrowIfNecessary(t); - LogUtil.log(Log.LEVEL_DEBUG, "application", "xml", ExceptionUtil.getStacktrace(t, true)); + if (LogUtil.doesDebug(log)) log.debug("xml", ExceptionUtil.getStacktrace(t, true)); } try { @@ -1508,8 +1513,15 @@ public static InputSource toInputSource(PageContext pc, String xml, boolean canB } // xml link pc = ThreadLocalPageContext.get(pc); - Resource res = ResourceUtil.toResourceExisting(pc, xml); - return toInputSource(pc, res); + try { + Resource res = ResourceUtil.toResourceExisting(pc, xml); + return toInputSource(pc, res); + } + catch (ExpressionException cause) { + ExpressionException ee = new ExpressionException("Failed to convert the string [" + StringUtil.max(xml, 100, "...") + "] to XML"); + ExceptionUtil.initCauseEL(ee, cause); + throw ee; + } } public static boolean isPath(String xml) { diff --git a/core/src/main/java/lucee/runtime/type/BIF.java b/core/src/main/java/lucee/runtime/type/BIF.java index 0d82fd7d27..842994a8e2 100644 --- a/core/src/main/java/lucee/runtime/type/BIF.java +++ b/core/src/main/java/lucee/runtime/type/BIF.java @@ -105,6 +105,11 @@ public BIF(Config config, FunctionLibFunction flf) { this.flf = flf; } + @Override + protected final int hash() { + return java.util.Objects.hash(id, args); + } + @Override public FunctionArgument[] getFunctionArguments() { if (args == null) { diff --git a/core/src/main/java/lucee/runtime/type/FunctionArgumentImpl.java b/core/src/main/java/lucee/runtime/type/FunctionArgumentImpl.java index 8363d550f8..7952d3effc 100755 --- a/core/src/main/java/lucee/runtime/type/FunctionArgumentImpl.java +++ b/core/src/main/java/lucee/runtime/type/FunctionArgumentImpl.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import java.util.Objects; import lucee.commons.lang.CFTypes; import lucee.commons.lang.ExternalizableUtil; @@ -105,6 +106,11 @@ public FunctionArgumentImpl(Collection.Key name, String type, boolean required, this.passByReference = passByReference; } + @Override + public final int hashCode() { + return Objects.hash(name, type, required, defaultType, passByReference); + } + /** * NEVER USE THIS CONSTRUCTOR, this constructor is only for deserialize this object from stream */ diff --git a/core/src/main/java/lucee/runtime/type/KeyImpl.java b/core/src/main/java/lucee/runtime/type/KeyImpl.java index 3d8a7cc0fc..9f5aae1a49 100755 --- a/core/src/main/java/lucee/runtime/type/KeyImpl.java +++ b/core/src/main/java/lucee/runtime/type/KeyImpl.java @@ -4,17 +4,17 @@ * * 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 + * 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 + * + * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . - * + * */ package lucee.runtime.type; @@ -47,15 +47,21 @@ public class KeyImpl implements Collection.Key, Castable, Comparable, Externaliz private static final long HSTART = 0xBB40E64DA205B064L; private static final long HMULT = 7664345821815920749L; - private static final int MAX = Caster.toInteger(SystemUtil.getSystemPropOrEnvVar("lucee.cache.variableKeys", null), 5000); + private static final int MAX = Caster.toInteger(SystemUtil.getSystemPropOrEnvVar("lucee.cache.variableKeys", null), 50000); - // private boolean intern; + // Reference fields (8 bytes each) - group together to minimize padding private String key; private transient String lcKey; private transient String ucKey; + + // long field (8 bytes) + private transient long h64; + + // int fields (4 bytes each) - group together private transient int wjh; private transient int sfm = -1; - private transient long h64; + + // static field private static Map keys = new HashMap(); public KeyImpl() { @@ -139,7 +145,7 @@ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundExcept /** * only used in KeyConstants - * + * * @param key * @return */ @@ -151,7 +157,7 @@ public static Collection.Key _const(Map keys, String key) { /** * literal values set in source code - * + * * @param key * @return */ @@ -166,7 +172,7 @@ public static Collection.Key intern(String key) { } /** - * + * * used to create the keys for the method initKeys() */ public static Collection.Key initKeys(String key) { @@ -179,7 +185,7 @@ public static Collection.Key initKeys(String key) { /** * for dynamic loading of key objects - * + * * @param string * @return */ @@ -188,7 +194,7 @@ public static Collection.Key init(String key) { } /** - * + * * used to inside the rest of the source created, can be dynamic values, so a lot */ public static Collection.Key source(String key) { @@ -469,31 +475,31 @@ public CharSequence subSequence(int start, int end) { /* * public static void main(String[] args) throws Exception { // KeyConstants._percentage - * + * * modify(ResourcesImpl.getFileResourceProvider().getResource( * "/Users/mic/Projects/Lucee/Lucee6/core/src/main/java/lucee")); - * + * * } - * + * * private static void modify(Resource resource) throws IOException { boolean stop = false; for * (Resource r: resource.listResources()) { if (r.isDirectory()) modify(r); if * (r.getAbsolutePath().endsWith(".java")) { - * + * * String content = IOUtil.toString(r, "UTF-8"); String result = null; int start = -1, end; while * ((start = content.indexOf("KeyImpl.getInstance(\"", start + 1)) != -1) { end = * content.indexOf("\")", start + 22); if (end > start) { String k = content.substring(start + 21, * end); if (KeyConstants.getFieldName(k) == null) print.e("public static final Key _" + k + * " = KeyImpl._const(\"" + k + "\");"); result = content = content.substring(0, start) + * "KeyConstants._" + k + content.substring(end + 2); - * + * * stop = true; - * + * * // print.e(content); - * + * * start = end; } else break; - * + * * } if (result != null) IOUtil.write(r, result, "UTF-8", false); // if (stop) throw new * IOException("www"); } } } */ -} \ No newline at end of file +} diff --git a/core/src/main/java/lucee/runtime/type/QueryColumnImpl.java b/core/src/main/java/lucee/runtime/type/QueryColumnImpl.java index 65a1a74e7f..0c2c306e68 100755 --- a/core/src/main/java/lucee/runtime/type/QueryColumnImpl.java +++ b/core/src/main/java/lucee/runtime/type/QueryColumnImpl.java @@ -947,8 +947,4 @@ public boolean containsKey(int key) { Object _null = NullSupportHelper.NULL(); return get(key, _null) != _null; } - - /* - * @Override public int hashCode() { return CollectionUtil.hashCode(this); } - */ } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/type/QueryColumnRef.java b/core/src/main/java/lucee/runtime/type/QueryColumnRef.java index aa7b389cc8..5c38c5371a 100755 --- a/core/src/main/java/lucee/runtime/type/QueryColumnRef.java +++ b/core/src/main/java/lucee/runtime/type/QueryColumnRef.java @@ -421,8 +421,4 @@ public Array listToArray() throws PageException { throw new ApplicationException("Query is not of type QueryImpl. Use instead Query.columnArray() or Query.columnList().listToArray()."); } - - /* - * @Override public int hashCode() { return CollectionUtil.hashCode(this); } - */ } \ 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 cfb1e0e0e9..f3ea1b50ca 100755 --- a/core/src/main/java/lucee/runtime/type/QueryImpl.java +++ b/core/src/main/java/lucee/runtime/type/QueryImpl.java @@ -749,7 +749,7 @@ private static boolean fillResult(QueryImpl qry, QueryResult qr, Collection.Key if (maxrow > -1 && recordcount >= maxrow) { break; } - sct = new StructImpl(Struct.TYPE_LINKED); + sct = new StructImpl(Struct.TYPE_LINKED, StructImpl.optimalCapacity(usedColumns.length, 2)); Object val; for (int i = 0; i < usedColumns.length; i++) { diff --git a/core/src/main/java/lucee/runtime/type/StructImpl.java b/core/src/main/java/lucee/runtime/type/StructImpl.java index dc19b566ad..c34dd321dc 100644 --- a/core/src/main/java/lucee/runtime/type/StructImpl.java +++ b/core/src/main/java/lucee/runtime/type/StructImpl.java @@ -139,6 +139,22 @@ public boolean makeSynchronized() { return false; } + /** + * Calculate optimal initial capacity for a struct with known size. Accounts for HashMap's default + * load factor (0.75) to avoid resize operations. + * + * @param size expected number of entries + * @param min minimum capacity (should be power of 2) + * @return optimal capacity as power of 2 + */ + public static int optimalCapacity(int size, int min) { + // HashMap resizes when size exceeds capacity * loadFactor (0.75) + // So we need: capacity * 0.75 >= size + // Therefore: capacity >= size / 0.75 + int needed = (int) (size / 0.75f) + 1; + return MathUtil.nextPowerOfTwo(needed, min); + } + @Override public int getType() { return type; @@ -277,7 +293,7 @@ public Collection duplicate(boolean deepCopy) { } public static Struct copy(Struct src, boolean deepCopy) { - return copy(src, new StructImpl(StructImpl.TYPE_SYNC, MathUtil.nextPowerOfTwo(src.size(), StructImpl.DEFAULT_INITIAL_CAPACITY)), deepCopy); + return copy(src, new StructImpl(StructImpl.TYPE_SYNC, optimalCapacity(src.size(), StructImpl.DEFAULT_INITIAL_CAPACITY)), deepCopy); } public static Struct copy(Struct src, Struct trg, boolean deepCopy) { @@ -339,7 +355,7 @@ public java.util.Collection values() { @Override public int hashCode() { - return super.hashCode(); + return map.hashCode(); } @Override diff --git a/core/src/main/java/lucee/runtime/type/UDFGSProperty.java b/core/src/main/java/lucee/runtime/type/UDFGSProperty.java index 86533b81d2..796da72650 100755 --- a/core/src/main/java/lucee/runtime/type/UDFGSProperty.java +++ b/core/src/main/java/lucee/runtime/type/UDFGSProperty.java @@ -29,6 +29,8 @@ import lucee.runtime.PageContextImpl; import lucee.runtime.PageSource; import lucee.runtime.component.MemberSupport; +import lucee.runtime.component.Property; +import lucee.runtime.component.PropertyImpl; import lucee.runtime.dump.DumpData; import lucee.runtime.dump.DumpProperties; import lucee.runtime.engine.ThreadLocalPageContext; @@ -57,12 +59,15 @@ public abstract class UDFGSProperty extends MemberSupport implements UDFPlus { protected final FunctionArgument[] arguments; protected final String name; protected Component srcComponent; + protected Property prop; // Property reference for flyweight UDFs private UDFPropertiesBase properties; private String id; public UDFGSProperty(Component component, String name, FunctionArgument[] arguments, short rtnType) { super(Component.ACCESS_PUBLIC); - properties = UDFProperties(null, component.getPageSource(), arguments, name, rtnType); + // LDEV-3335: Support null component for stateless flyweight UDFs (bytecode-generated static accessors) + // For backwards compatibility with older bytecode, we still support non-null component + properties = UDFProperties(null, component != null ? component.getPageSource() : null, arguments, name, rtnType); this.name = name; this.arguments = arguments; this.srcComponent = component; @@ -72,6 +77,11 @@ private static UDFPropertiesBase UDFProperties(Page page, PageSource pageSource, return new UDFPropertiesLight(page, pageSource, arguments, functionName, returnType); } + @Override + protected final int hash() { + return java.util.Objects.hash(name, getPageSource()); + } + @Override public FunctionArgument[] getFunctionArguments() { return arguments; @@ -94,19 +104,28 @@ public boolean equals(Object other) { @Override public String getSource() { - PageSource ps = srcComponent.getPageSource(); - if (ps != null) return ps.getDisplayPath(); + // LDEV-3335: Handle null srcComponent for stateless flyweight UDFs + if (srcComponent != null) { + PageSource ps = srcComponent.getPageSource(); + if (ps != null) return ps.getDisplayPath(); + } + // Fall back to properties PageSource if available + if (properties != null && properties.getPageSource() != null) { + return properties.getPageSource().getDisplayPath(); + } return ""; } @Override public String id() { if (id == null) { + // LDEV-3335: Handle null srcComponent for stateless flyweight UDFs + String componentId = srcComponent != null ? srcComponent.id() : "static"; try { - id = Hash.md5(srcComponent.id() + ":" + getFunctionName()); + id = Hash.md5(componentId + ":" + getFunctionName()); } catch (NoSuchAlgorithmException e) { - id = srcComponent.id() + ":" + getFunctionName(); + id = componentId + ":" + getFunctionName(); } } return id; @@ -209,7 +228,8 @@ public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties @Override public Struct getMetaData(PageContext pc) throws PageException { - return ComponentUtil.getMetaData(pc, properties, null); + // LDEV-3335: Pass 'this' UDF instance so ComponentUtil can call getPageSource() with proper fallback + return ComponentUtil.getMetaData(pc, this, properties, null); } final Object cast(PageContext pc, FunctionArgument arg, Object value, int index) throws PageException { @@ -322,8 +342,17 @@ private static String createMessage(String format, Object value) { @Override public PageSource getPageSource() { - return srcComponent.getPageSource(); - // return this.properties.getPageSource(); + // LDEV-3335: Handle null srcComponent for stateless flyweight UDFs + if (srcComponent != null) { + return srcComponent.getPageSource(); + } + // For flyweight UDFs, get PageSource from the property's owner + if (prop != null && prop instanceof PropertyImpl) { + PageSource ps = ((PropertyImpl) prop).getOwnerPageSource(); + if (ps != null) return ps; + } + // Fall back to properties PageSource + return this.properties.getPageSource(); } @Override @@ -331,6 +360,10 @@ public boolean getBufferOutput(PageContext pc) { return pc.getApplicationContext().getBufferOutput(); } + public Property getProperty() { + return prop; + } + public Component getComponent(PageContext pc) { if (pc == null) pc = ThreadLocalPageContext.get(); if (pc != null) { diff --git a/core/src/main/java/lucee/runtime/type/UDFGetterProperty.java b/core/src/main/java/lucee/runtime/type/UDFGetterProperty.java index 983bc3ca8e..82c93a595b 100755 --- a/core/src/main/java/lucee/runtime/type/UDFGetterProperty.java +++ b/core/src/main/java/lucee/runtime/type/UDFGetterProperty.java @@ -19,10 +19,10 @@ package lucee.runtime.type; import lucee.commons.lang.CFTypes; -import lucee.commons.lang.StringUtil; import lucee.runtime.Component; import lucee.runtime.PageContext; import lucee.runtime.component.Property; +import lucee.runtime.component.PropertyImpl; import lucee.runtime.exp.PageException; import lucee.runtime.type.Collection.Key; @@ -32,14 +32,13 @@ public final class UDFGetterProperty extends UDFGSProperty { private static final FunctionArgument[] EMPTY = new FunctionArgument[0]; - private final Property prop; // private ComponentScope scope; private final Key propName; public UDFGetterProperty(Component component, Property prop) { - super(component, "get" + StringUtil.ucFirst(prop.getName()), EMPTY, CFTypes.toShortStrict(prop.getType(), CFTypes.TYPE_ANY)); - this.prop = prop; - this.propName = KeyImpl.init(prop.getName()); + super(component, ((PropertyImpl) prop).getGetterName(), EMPTY, CFTypes.toShortStrict(prop.getType(), CFTypes.TYPE_ANY)); + this.prop = prop; // Set parent's prop field + this.propName = ((PropertyImpl) prop).getNameAsKey(); } @Override @@ -72,6 +71,10 @@ public Object getDefaultValue(PageContext pc, int index, Object defaultValue) th return defaultValue; } + public Property getProperty() { + return prop; + } + @Override public String getReturnTypeAsString() { return prop.getType(); diff --git a/core/src/main/java/lucee/runtime/type/UDFImpl.java b/core/src/main/java/lucee/runtime/type/UDFImpl.java index 57ccaaa338..94a106ec85 100755 --- a/core/src/main/java/lucee/runtime/type/UDFImpl.java +++ b/core/src/main/java/lucee/runtime/type/UDFImpl.java @@ -89,6 +89,11 @@ public UDFImpl(UDFProperties properties, Component owner) { setOwnerComponent(owner); } + @Override + protected final int hash() { + return java.util.Objects.hash(getFunctionName(), getPageSource()); + } + public UDF duplicate(Component cfc) { UDFImpl udf = new UDFImpl(properties); udf.ownerComponent = cfc; diff --git a/core/src/main/java/lucee/runtime/type/UDFPropertiesImpl.java b/core/src/main/java/lucee/runtime/type/UDFPropertiesImpl.java index ba873c4a60..3df3c8efd9 100755 --- a/core/src/main/java/lucee/runtime/type/UDFPropertiesImpl.java +++ b/core/src/main/java/lucee/runtime/type/UDFPropertiesImpl.java @@ -50,27 +50,32 @@ public final class UDFPropertiesImpl extends UDFPropertiesBase { private static final long serialVersionUID = 8679484452640746605L; // do not change + // Reference fields (8 bytes each) - group together to minimize padding public String functionName; - public int returnType; public String strReturnType; - public boolean output; - public Boolean bufferOutput; public String hint; public String displayName; - public int index; + public String description; + public String strReturnFormat; public FunctionArgument[] arguments; public Struct meta; - public String description; + public Set argumentsSet; + public Object cachedWithin; + public Boolean bufferOutput; public Boolean secureJson; public Boolean verifyClient; - public String strReturnFormat; + public Integer localMode; + + // int fields (4 bytes each) - group together + public int returnType; + public int index; public int returnFormat; - public Set argumentsSet; public int access; - public Object cachedWithin; - public Integer localMode; public int modifier; + // boolean field (1 byte) - at end to minimize padding + public boolean output; + /** * NEVER USE THIS CONSTRUCTOR, this constructor is only for deserialize this object from stream */ diff --git a/core/src/main/java/lucee/runtime/type/UDFSetterProperty.java b/core/src/main/java/lucee/runtime/type/UDFSetterProperty.java index aff01b5c19..a4fae152a0 100755 --- a/core/src/main/java/lucee/runtime/type/UDFSetterProperty.java +++ b/core/src/main/java/lucee/runtime/type/UDFSetterProperty.java @@ -43,43 +43,41 @@ public final class UDFSetterProperty extends UDFGSProperty { private static final long serialVersionUID = 378348754607851563L; private static final Collection.Key VALIDATE_PARAMS = KeyConstants._validateParams; - private final Property prop; private final Key propName; private String validate; private Struct validateParams; private UDFSetterProperty(Component component, Property prop, String validate, Struct validateParams) { - super(component, "set" + StringUtil.ucFirst(prop.getName()), - new FunctionArgument[] { new FuncArgLite(KeyImpl.init(prop.getName()), prop.getType(), CFTypes.toShortStrict(prop.getType(), CFTypes.TYPE_UNKNOW), true) }, + super(component, ((PropertyImpl) prop).getSetterName(), + new FunctionArgument[] { new FuncArgLite(((PropertyImpl) prop).getNameAsKey(), prop.getType(), CFTypes.toShortStrict(prop.getType(), CFTypes.TYPE_UNKNOW), true) }, CFTypes.TYPE_VOID); this.prop = prop; - this.propName = KeyImpl.init(prop.getName()); + this.propName = ((PropertyImpl) prop).getNameAsKey(); this.validate = validate; this.validateParams = validateParams; } public UDFSetterProperty(Component component, Property prop) throws PageException { - super(component, "set" + StringUtil.ucFirst(prop.getName()), - new FunctionArgument[] { new FuncArgLite(KeyImpl.init(prop.getName()), prop.getType(), CFTypes.toShortStrict(prop.getType(), CFTypes.TYPE_UNKNOW), true) }, + super(component, ((PropertyImpl) prop).getSetterName(), + new FunctionArgument[] { new FuncArgLite(((PropertyImpl) prop).getNameAsKey(), prop.getType(), CFTypes.toShortStrict(prop.getType(), CFTypes.TYPE_UNKNOW), true) }, CFTypes.TYPE_VOID); this.prop = prop; - this.propName = KeyImpl.init(prop.getName()); - - this.validate = Caster.toString(prop.getDynamicAttributes().get(KeyConstants._validate, null), null); - if (!StringUtil.isEmpty(validate, true)) { - validate = validate.trim().toLowerCase(); - Struct da = prop.getDynamicAttributes(); - if (da != null) { - Object o = da.get(VALIDATE_PARAMS, null); - if (o != null) { - if (Decision.isStruct(o)) validateParams = Caster.toStruct(o); - else { - String str = Caster.toString(o); - if (!StringUtil.isEmpty(str, true)) { - validateParams = ORMUtil.convertToSimpleMap(str); - if (validateParams == null) throw new ExpressionException("cannot parse string [" + str + "] as struct"); - } + this.propName = ((PropertyImpl) prop).getNameAsKey(); + + // Cache getDynamicAttributes() call to avoid multiple lookups + Struct da = prop.getDynamicAttributes(); + this.validate = Caster.toString(da.get(KeyConstants._validate, null), null); + if (!StringUtil.isEmpty(this.validate, true)) { + this.validate = this.validate.trim().toLowerCase(); + Object o = da.get(VALIDATE_PARAMS, null); + if (o != null) { + if (Decision.isStruct(o)) validateParams = Caster.toStruct(o); + else { + String str = Caster.toString(o); + if (!StringUtil.isEmpty(str, true)) { + validateParams = ORMUtil.convertToSimpleMap(str); + if (validateParams == null) throw new ExpressionException("cannot parse string [" + str + "] as struct"); } } } @@ -151,4 +149,8 @@ public Object implementation(PageContext pageContext) throws Throwable { return null; } + public Property getProperty() { + return prop; + } + } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/type/query/QueryArray.java b/core/src/main/java/lucee/runtime/type/query/QueryArray.java index 796c1a0f7a..174b3f8a0d 100644 --- a/core/src/main/java/lucee/runtime/type/query/QueryArray.java +++ b/core/src/main/java/lucee/runtime/type/query/QueryArray.java @@ -174,10 +174,11 @@ public static QueryArray toQueryArray(lucee.runtime.type.QueryImpl q) throws Pag int rows = q.getRecordcount(); if (rows == 0) return qa; Key[] columns = q.getColumnNames(); + int structCapacity = StructImpl.optimalCapacity(columns.length, 2); Struct tmp; for (int r = 1; r <= rows; r++) { - qa.add(tmp = new StructImpl(Struct.TYPE_LINKED)); + qa.add(tmp = new StructImpl(Struct.TYPE_LINKED, structCapacity)); for (Key c: columns) { tmp.set(c, q.getAt(c, r, null)); } diff --git a/core/src/main/java/lucee/runtime/type/query/QueryStruct.java b/core/src/main/java/lucee/runtime/type/query/QueryStruct.java index 8970386670..058e48ad64 100644 --- a/core/src/main/java/lucee/runtime/type/query/QueryStruct.java +++ b/core/src/main/java/lucee/runtime/type/query/QueryStruct.java @@ -3,7 +3,6 @@ import lucee.commons.io.SystemUtil.TemplateLine; import lucee.commons.lang.FormatUtil; import lucee.commons.lang.StringUtil; -import lucee.commons.math.MathUtil; import lucee.runtime.PageContext; import lucee.runtime.db.SQL; import lucee.runtime.dump.DumpData; @@ -184,7 +183,7 @@ public static QueryStruct toQueryStruct(QueryImpl q, Key columnName) throws Page int rows = q.getRecordcount(); if (rows == 0) return qs; Key[] columns = q.getColumnNames(); - int colCount = MathUtil.nextPowerOfTwo(columns.length, 0); + int colCount = StructImpl.optimalCapacity(columns.length, 4); Struct tmp; for (int r = 1; r <= rows; r++) { diff --git a/core/src/main/java/lucee/runtime/type/scope/CGIImplReadOnly.java b/core/src/main/java/lucee/runtime/type/scope/CGIImplReadOnly.java index 430a94979e..8d07a2fa65 100644 --- a/core/src/main/java/lucee/runtime/type/scope/CGIImplReadOnly.java +++ b/core/src/main/java/lucee/runtime/type/scope/CGIImplReadOnly.java @@ -33,7 +33,6 @@ import lucee.commons.io.res.Resource; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; -import lucee.commons.math.MathUtil; import lucee.runtime.PageContext; import lucee.runtime.PageSource; import lucee.runtime.config.NullSupportHelper; @@ -77,7 +76,7 @@ public final class CGIImplReadOnly extends ReadOnlyStruct implements CGI, Script KeyConstants._local_addr, KeyConstants._local_host }; private static Struct staticKeys; static { - staticKeys = new StructImpl(StructImpl.DEFAULT_TYPE, MathUtil.nextPowerOfTwo(keys.length, StructImpl.DEFAULT_INITIAL_CAPACITY)); + staticKeys = new StructImpl(StructImpl.DEFAULT_TYPE, StructImpl.optimalCapacity(keys.length, StructImpl.DEFAULT_INITIAL_CAPACITY)); for (int i = 0; i < keys.length; i++) { staticKeys.setEL(keys[i], ""); } diff --git a/core/src/main/java/lucee/runtime/type/scope/ScopeContext.java b/core/src/main/java/lucee/runtime/type/scope/ScopeContext.java index a1c3bf2ee3..d4c0b6ffcb 100755 --- a/core/src/main/java/lucee/runtime/type/scope/ScopeContext.java +++ b/core/src/main/java/lucee/runtime/type/scope/ScopeContext.java @@ -18,9 +18,11 @@ */ package lucee.runtime.type.scope; +import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import jakarta.servlet.http.HttpSession; import lucee.commons.collection.MapFactory; @@ -48,6 +50,7 @@ import lucee.runtime.listener.ApplicationContext; import lucee.runtime.listener.ApplicationListener; import lucee.runtime.op.Caster; +import lucee.runtime.op.Decision; import lucee.runtime.type.Collection.Key; import lucee.runtime.type.KeyImpl; import lucee.runtime.type.Struct; @@ -81,6 +84,26 @@ public final class ScopeContext { private static final long CLIENT_MEMORY_TIMESPAN = 1 * MINUTE; private static final long SESSION_MEMORY_TIMESPAN = 1 * MINUTE; + private final static Set IGNORE_SESSION = new HashSet<>(); + private final static Set IGNORE_CLIENT = new HashSet<>(); + static { + IGNORE_SESSION.add(KeyConstants._csrf_token); + IGNORE_SESSION.add(KeyConstants._cfid); + IGNORE_SESSION.add(KeyConstants._cftoken); + IGNORE_SESSION.add(KeyConstants._urltoken); + IGNORE_SESSION.add(KeyConstants._timecreated); + IGNORE_SESSION.add(KeyConstants._lastvisit); + IGNORE_SESSION.add(KeyConstants._sessionid); + + IGNORE_CLIENT.add(KeyConstants._csrf_token); + IGNORE_CLIENT.add(KeyConstants._cfid); + IGNORE_CLIENT.add(KeyConstants._cftoken); + IGNORE_CLIENT.add(KeyConstants._urltoken); + IGNORE_CLIENT.add(KeyConstants._timecreated); + IGNORE_CLIENT.add(KeyConstants._lastvisit); + IGNORE_CLIENT.add(KeyConstants._hitcount); + } + private Map> cfSessionContexts = MapFactory.>getConcurrentMap(); private Map> cfClientContexts = MapFactory.>getConcurrentMap(); private Map applicationContexts = MapFactory.getConcurrentMap(); @@ -251,8 +274,13 @@ else if ("cookie".equals(storage)) { else { DataSource ds = pc.getDataSource(storage, null); if (ds != null && ds.isStorage()) { - scope = (StorageScope) IKStorageScopeSupport.getInstance(scopeType, new IKHandlerDatasource(), appContext.getName(), storage, pc, existing, - createIfNeeded, getLog()); + try { + scope = (StorageScope) IKStorageScopeSupport.getInstance(scopeType, new IKHandlerDatasource(), appContext.getName(), storage, pc, existing, + createIfNeeded, getLog()); + } + catch (Exception ex) { + LogUtil.log("scope-context", ex); + } } else { scope = (StorageScope) IKStorageScopeSupport.getInstance(scopeType, new IKHandlerCache(), appContext.getName(), storage, pc, existing, createIfNeeded, @@ -437,24 +465,27 @@ public Session getSessionScope(PageContext pc) throws PageException { return getJSessionScope(pc); } - public boolean hasExistingSessionScope(PageContext pc) { + public Object getExistingSessionScope(PageContext pc) { if (pc.getSessionType() == Config.SESSION_TYPE_APPLICATION) { try { - return getCFScope(pc, false, Scope.SCOPE_SESSION) != null; + return getCFScope(pc, false, Scope.SCOPE_SESSION); } catch (Exception e) { - return false; + return null; } } return hasExistingJSessionScope(pc); } - private boolean hasExistingJSessionScope(PageContext pc) { + private Object hasExistingJSessionScope(PageContext pc) { HttpSession httpSession = ((PageContextImpl) pc).getHttpServletRequest().getSession(false); - if (httpSession == null) return false; + if (httpSession == null) return null; Session session = (Session) httpSession.getAttribute(pc.getApplicationContext().getName()); - return session instanceof JSession && !session.isExpired(); + if (session instanceof JSession && !session.isExpired()) { + return session; + } + return null; } public Session getExistingCFSessionScope(String applicationName, String cfid) { @@ -781,6 +812,20 @@ public static boolean hasContent(Map map, int type) { && (type == Scope.SCOPE_CLIENT ? map.containsKey(KeyConstants._hitcount) : map.containsKey(KeyConstants._sessionid))); } + public static int hash(Map map, int type, boolean ignoreSimpleValues) { + int result = 1; + for (Entry e: map.entrySet()) { + if (type == Scope.SCOPE_CLIENT ? IGNORE_CLIENT.contains(e.getKey()) : IGNORE_SESSION.contains(e.getKey())) { + continue; + } + if (ignoreSimpleValues && Decision.isSimpleValue(e.getValue().getValue())) { + continue; + } + result = 31 * result + (e.getValue() == null ? 0 : e.getValue().hashCode()); + } + return result; + } + /** * @param cfmlFactory * @@ -892,6 +937,7 @@ public void invalidateUserScope(PageContextImpl pc, boolean migrateSessionData, boolean hasClientManagement = appContext.isSetClientManagement(); boolean hasSessionManagement = appContext.isSetSessionManagement(); + boolean isJ2EESession = pc.getSessionType() == Config.SESSION_TYPE_JEE; // get in memory scopes UserScope oldClient = null; @@ -901,8 +947,25 @@ public void invalidateUserScope(PageContextImpl pc, boolean migrateSessionData, } UserScope oldSession = null; if (hasSessionManagement) { - Map sessionContext = getSubMap(cfSessionContexts, appContext.getName()); - oldSession = (UserScope) sessionContext.get(pc.getCFID()); + if (isJ2EESession) { + // For J2EE sessions, try the HttpSession attribute first + HttpSession httpSession = pc.getSession(); + if (httpSession != null) { + Object session = httpSession.getAttribute(appContext.getName()); + if (session instanceof JSession) { + oldSession = (JSession) session; + } + } + // Fall back to cfSessionContexts (used by JSR-223/script-runner where httpSession is null) + if (oldSession == null) { + Map sessionContext = getSubMap(cfSessionContexts, appContext.getName()); + oldSession = (UserScope) sessionContext.get(pc.getCFID()); + } + } + else { + Map sessionContext = getSubMap(cfSessionContexts, appContext.getName()); + oldSession = (UserScope) sessionContext.get(pc.getCFID()); + } } if (hasSessionManagement) { @@ -920,17 +983,48 @@ public void invalidateUserScope(PageContextImpl pc, boolean migrateSessionData, if (hasSessionManagement) removeCFSessionScope(pc); if (hasClientManagement) removeClientScope(pc); + // For J2EE sessions, handle the servlet container's session (JSESSIONID) + if (isJ2EESession && hasSessionManagement) { + HttpSession httpSession = pc.getSession(); + if (httpSession != null) { + if (migrateSessionData) { + // sessionRotate: rotate to a new session ID but keep session alive + pc.getHttpServletRequest().changeSessionId(); + } + else { + // sessionInvalidate: completely destroy the JEE session (LDEV-3248) + httpSession.invalidate(); + } + } + } + pc.resetIdAndToken(); - pc.resetSession(); + // For J2EE sessionRotate with a real httpSession (Tomcat), don't reset session - we already called changeSessionId() and want to keep the data + // But for JSR-223 (where httpSession is null), we need to reset to create a new session + HttpSession httpSessionForReset = pc.getSession(); + if (!(isJ2EESession && migrateSessionData && httpSessionForReset != null)) { + pc.resetSession(); + } pc.resetClient(); - if (oldSession != null) migrate(pc, oldSession, (UserScope) getCFScope(pc, true, Scope.SCOPE_SESSION), migrateSessionData); + if (oldSession != null) { + UserScope newSession; + if (isJ2EESession) { + newSession = getSessionScope(pc); + } + else { + newSession = (UserScope) getCFScope(pc, true, Scope.SCOPE_SESSION); + } + migrate(pc, oldSession, newSession, migrateSessionData); + } if (oldClient != null) migrate(pc, oldClient, (UserScope) getCFScope(pc, true, Scope.SCOPE_CLIENT), migrateClientData); } private static void migrate(PageContextImpl pc, UserScope oldScope, UserScope newScope, boolean migrate) { if (oldScope == null || newScope == null) return; + // For J2EE sessions with changeSessionId(), old and new are the same object - no migration needed + if (oldScope == newScope) return; if (!migrate) oldScope.clear(); oldScope.resetEnv(pc); Iterator> it = oldScope.entryIterator(); @@ -943,7 +1037,13 @@ private static void migrate(PageContextImpl pc, UserScope oldScope, UserScope ne } if (newScope instanceof StorageScope) { ((StorageScope) newScope).store(pc.getConfig()); - ((StorageScope) newScope).setTokens(((StorageScope) oldScope).getTokens()); + if (oldScope instanceof StorageScope) { + ((StorageScope) newScope).setTokens(((StorageScope) oldScope).getTokens()); + } + } + else if (newScope instanceof JSession && oldScope instanceof JSession) { + // JSession doesn't implement StorageScope but has its own token handling + ((JSession) newScope).setTokens(((JSession) oldScope).getTokens()); } } diff --git a/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java b/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java index c1e345fd7b..34099eaec0 100644 --- a/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java +++ b/core/src/main/java/lucee/runtime/type/scope/storage/IKHandlerCache.java @@ -51,7 +51,7 @@ else if (val instanceof IKStorageValue) { @Override public void store(IKStorageScopeSupport storageScope, PageContext pc, String appName, String name, Map data, String strType, int type, Log log) { - if (!storageScope.hasChanges()) return; + if (!storageScope.hasChanges(pc, log)) return; try { Cache cache = getCache(ThreadLocalPageContext.get(pc), name); String key = getKey(pc.getCFID(), appName, storageScope.getTypeAsString()); 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 7d716a1a19..ea5302f3bd 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 @@ -8,6 +8,7 @@ import java.util.Map.Entry; import lucee.commons.io.log.Log; +import lucee.commons.io.log.LogUtil; import lucee.commons.lang.ExceptionUtil; import lucee.runtime.PageContext; import lucee.runtime.PageContextImpl; @@ -61,22 +62,29 @@ public IKStorageValue loadData(PageContext pc, String appName, String name, Stri query.getExecutionTime()); } } - boolean _isNew = query.getRecordcount() == 0; + boolean _isNew = query.getRecordcount() == 0; if (_isNew) { ScopeContext.debug(log, "create new " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + " in datasource [" + name + "]"); return null; } String str = Caster.toString(query.getAt(KeyConstants._data, 1)); + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "load existing " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + " from datasource [" + name + "]"); + } // old style boolean b; if ((b = str.startsWith("struct:")) || (str.startsWith("{") && str.endsWith("}"))) { + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "load old style data"); + } if (b) str = str.substring(7); try { return toIKStorageValue((Struct) pc.evaluate(str)); } catch (Exception e) { + ScopeContext.error(log, e); } return null; } @@ -117,7 +125,16 @@ public static IKStorageValue toIKStorageValue(Struct sct) throws PageException { @Override public void store(IKStorageScopeSupport storageScope, PageContext pc, String appName, final String name, Map data, String strType, int type, Log log) { - if (!storageScope.hasChanges()) return; + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, + "check if we need to store the " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + " to datasource [" + name + "]"); + } + + if (!storageScope.hasChanges(pc, log)) return; + + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "store the " + strType + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + " to datasource [" + name + "]"); + } DatasourceConnection dc = null; ConfigPro ci = (ConfigPro) ThreadLocalPageContext.getConfig(pc); diff --git a/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeItem.java b/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeItem.java index 1887f7df1a..e314b7a766 100644 --- a/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeItem.java +++ b/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeItem.java @@ -38,6 +38,11 @@ public Object getEmbededObject() { return value; } + @Override + public final int hashCode() { + return value == null ? 0 : value.hashCode(); + } + @Override public Object getEmbededObject(Object defaultValue) { return value; diff --git a/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeSupport.java b/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeSupport.java index 9449074580..5db1aac970 100644 --- a/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeSupport.java +++ b/core/src/main/java/lucee/runtime/type/scope/storage/IKStorageScopeSupport.java @@ -87,7 +87,7 @@ public abstract class IKStorageScopeSupport extends StructSupport implements Sto } protected boolean isinit = true; - protected Map data0; + protected Map data0 = null; protected long lastvisit; protected DateTime _lastvisit; protected int hitcount = 0; @@ -105,6 +105,8 @@ public abstract class IKStorageScopeSupport extends StructSupport implements Sto private String appName; private String name; + private int hash; + public IKStorageScopeSupport(PageContext pc, IKHandler handler, String appName, String name, String strType, int type, Map data, long lastModified, long timeSpan) { // !!! do not store the pagecontext or config object, this object is Serializable !!! @@ -150,22 +152,42 @@ public static Scope getInstance(int scope, IKHandler handler, String appName, St if (existing instanceof IKStorageScopeSupport) { IKStorageScopeSupport tmp = ((IKStorageScopeSupport) existing); if (tmp.lastModified() >= time && name.equalsIgnoreCase(tmp.getStorage())) { + + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "use the existing " + (Scope.SCOPE_SESSION == scope ? "session" : "client") + " scope for " + pc.getApplicationContext().getName() + + "/" + pc.getCFID() + " because it is newer or equal to the scope in storage. "); + } return existing; } } - if (Scope.SCOPE_SESSION == scope) return new IKStorageScopeSession(pc, handler, appName, name, sv.getValue(), time, getSessionTimeout(pc)); + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "use " + (Scope.SCOPE_SESSION == scope ? "session" : "client") + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + + " from storage. "); + } + + if (Scope.SCOPE_SESSION == scope) { + return new IKStorageScopeSession(pc, handler, appName, name, sv.getValue(), time, getSessionTimeout(pc)); + } else if (Scope.SCOPE_CLIENT == scope) return new IKStorageScopeClient(pc, handler, appName, name, sv.getValue(), time, getClientTimeout(pc)); } else if (existing instanceof IKStorageScopeSupport) { IKStorageScopeSupport tmp = ((IKStorageScopeSupport) existing); if (name.equalsIgnoreCase(tmp.getStorage())) { + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "use the existing " + (Scope.SCOPE_SESSION == scope ? "session" : "client") + " scope for " + pc.getApplicationContext().getName() + "/" + + pc.getCFID() + " because it is newer or equal to the scope in storage."); + } return existing; } } if (!createIfNeeded) return null; + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, + "create a new " + (Scope.SCOPE_SESSION == scope ? "session" : "client") + " scope for " + pc.getApplicationContext().getName() + "/" + pc.getCFID() + "."); + } IKStorageScopeSupport rtn = null; Map map = MapFactory.getConcurrentMap(); if (Scope.SCOPE_SESSION == scope) rtn = new IKStorageScopeSession(pc, handler, appName, name, map, 0L, getSessionTimeout(pc)); @@ -227,6 +249,11 @@ public void touchBeforeRequest(PageContext pc) { data0.put(KeyConstants._csrf_token, new IKStorageScopeItem(this.tokens, lastModifiedAtInit())); } data0.put(KeyConstants._timecreated, new IKStorageScopeItem(timecreated, lastModifiedAtInit())); + + if (storage != null) { + // we have set "ignoreSimpleValues" to true, because this is already covered by "hasChanges" + hash = ScopeContext.hash(data0, type, true); + } } public void resetEnv(PageContext pc) { @@ -399,6 +426,7 @@ public Object remove(Key key) throws PageException { if (existing != null) { hasChanges = true; return existing.remove(lastModified = System.currentTimeMillis()); + } throw new ExpressionException("can't remove key [" + key.getString() + "] from map, key doesn't exist"); } @@ -484,8 +512,29 @@ public void unstore(Config config) { /** * @return the hasChanges */ - public boolean hasChanges() { - return hasChanges; + public boolean hasChanges(PageContext pc, Log log) { + + if (hasChanges) { + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "detected a change in the root keys of the " + (Scope.SCOPE_SESSION == type ? "session" : "client") + " scope for " + + pc.getApplicationContext().getName() + "/" + pc.getCFID() + "."); + } + return true; + } + // we have set "ignoreSimpleValues" to true, because this is already covered by "hasChanges" above + if (ScopeContext.hash(data0, type, true) != hash) { + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "detected a change in one of the values in the " + (Scope.SCOPE_SESSION == type ? "session" : "client") + " scope for " + + pc.getApplicationContext().getName() + "/" + pc.getCFID() + "."); + } + + return true; + } + if (LogUtil.doesDebug(log)) { + ScopeContext.debug(log, "no change detected in the " + (Scope.SCOPE_SESSION == type ? "session" : "client") + " scope for " + pc.getApplicationContext().getName() + "/" + + pc.getCFID() + "."); + } + return false; } @Override diff --git a/core/src/main/java/lucee/runtime/type/util/ArraySupport.java b/core/src/main/java/lucee/runtime/type/util/ArraySupport.java index 1d359eae61..98fc57b7f0 100755 --- a/core/src/main/java/lucee/runtime/type/util/ArraySupport.java +++ b/core/src/main/java/lucee/runtime/type/util/ArraySupport.java @@ -349,10 +349,6 @@ public boolean equals(Object obj) { return CollectionUtil.equals(this, (Collection) obj); } - /* - * @Override public int hashCode() { return CollectionUtil.hashCode(this); } - */ - @Override public Iterator> entryArrayIterator() { return new EntryArrayIterator(this, intKeys()); 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 a0394c3497..303a8e4a94 100755 --- a/core/src/main/java/lucee/runtime/type/util/ComponentUtil.java +++ b/core/src/main/java/lucee/runtime/type/util/ComponentUtil.java @@ -93,6 +93,7 @@ import lucee.runtime.type.Struct; import lucee.runtime.type.StructImpl; import lucee.runtime.type.UDF; +import lucee.runtime.type.UDFGSProperty; import lucee.runtime.type.UDFPropertiesBase; import lucee.runtime.type.dt.DateTime; import lucee.transformer.bytecode.BytecodeContext; @@ -782,13 +783,44 @@ public static PageSource getPageSource(Component cfc) { } } - public static Component getActiveComponent(PageContext pc, Component current) { - if (pc.getActiveComponent() == null) return current; - if (pc.getActiveUDF() != null && (pc.getActiveComponent()).getPageSource() == (pc.getActiveUDF().getOwnerComponent()).getPageSource()) { + public static Component getCurrentComponent(PageContext pc, Component current) { - return pc.getActiveUDF().getOwnerComponent(); + // where are we (getCurrentPageSource) and use the matching component + PageSource currPS = pc.getCurrentPageSource(null); + if (currPS != null) { + + // current component + Component curr = current; + while (curr != null) { + if (currPS.equals(((ComponentImpl) curr)._getPageSource())) { + return curr; + } + curr = curr.getBaseComponent(); + } + + // active component + curr = pc.getActiveComponent(); + while (curr != null) { + if (currPS.equals(((ComponentImpl) curr)._getPageSource())) { + return curr; + } + curr = curr.getBaseComponent(); + } + + // owner component + if (pc.getActiveUDF() != null) { + curr = pc.getActiveUDF().getOwnerComponent(); + while (curr != null) { + if (currPS.equals(((ComponentImpl) curr)._getPageSource())) { + return curr; + } + curr = curr.getBaseComponent(); + } + } } - return pc.getActiveComponent(); + + if (pc.getActiveComponent() != null) return pc.getActiveComponent(); + return current; } public static long getCompileTime(PageContext pc, PageSource ps, long defaultValue) { @@ -879,7 +911,28 @@ public static Struct getMetaData(PageContext pc, UDF udf, UDFPropertiesBase udfP if (udfProps.getLocalMode() != null) func.set("localMode", AppListenerUtil.toLocalMode(udfProps.getLocalMode().intValue(), "")); - if (udfProps.getPageSource() != null) func.set(KeyConstants._owner, udfProps.getPageSource().getDisplayPath()); + if (udf instanceof UDFGSProperty) { + UDFGSProperty gsProp = (UDFGSProperty) udf; + PageSource ps = null; + + // LDEV-3335: For inherited accessors, use the property's original owner + Property prop = gsProp.getProperty(); + if (prop instanceof PropertyImpl) { + ps = ((PropertyImpl) prop).getOwnerPageSource(); + } + + // Fallback to the UDF's PageSource if property owner not available + if (ps == null) { + ps = gsProp.getPageSource(); + } + + if (ps != null) { + func.set(KeyConstants._owner, ps.getDisplayPath()); + } + } + else if (udfProps.getPageSource() != null) { + func.set(KeyConstants._owner, udfProps.getPageSource().getDisplayPath()); + } if (udfProps.getStartLine() > 0 && udfProps.getEndLine() > 0) { Struct pos = new StructImpl(); @@ -971,6 +1024,11 @@ public static java.util.Collection toUDFs(java.util.Collection udfbs, private static class ReturnFormatValue implements Castable, SimpleValue, CharSequence, Dumpable { + /** + * + */ + private static final long serialVersionUID = 1L; + @Override public Boolean castToBoolean(Boolean defaultValue) { return Caster.toBoolean(getReturnFormat(ThreadLocalPageContext.get()), defaultValue); @@ -1145,8 +1203,14 @@ public static void registerProperty(PageContext pc, String name, String type, Ob StructUtil.copy(dynamicAttributes, property.getDynamicAttributes(), true); } + // LDEV-3335: Set owner BEFORE setProperty() so inherited properties have the correct owner + // Only set owner for properties defined in this component, not inherited ones + // If already set (inherited from parent), don't overwrite + if (property.getOwnerPageSource() == null) { + property.setOwnerName(comp.getAbsName(), comp.getPageSource()); + } + comp.setProperty(property); - property.setOwnerName(comp.getAbsName()); } } 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 22cceb6f74..268ef3c826 100644 --- a/core/src/main/java/lucee/runtime/type/util/KeyConstants.java +++ b/core/src/main/java/lucee/runtime/type/util/KeyConstants.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import lucee.runtime.type.Collection; import lucee.runtime.type.Collection.Key; @@ -284,19 +285,32 @@ private static Collection.Key init(String key) { public static final Key __count = init("_count"); public static final Key __time = init("_time"); public static final Key _a = init("a"); + public static final Key _b = init("b"); + public static final Key _c = init("c"); + public static final Key _d = init("d"); public static final Key _e = init("e"); public static final Key _f = init("f"); public static final Key _g = init("g"); public static final Key _h = init("h"); + public static final Key _i = init("i"); + public static final Key _j = init("j"); public static final Key _k = init("k"); + public static final Key _l = init("l"); + public static final Key _m = init("m"); public static final Key _n = init("n"); public static final Key _o = init("o"); public static final Key _p = init("p"); + public static final Key _q = init("q"); + public static final Key _r = init("r"); public static final Key _s = init("s"); public static final Key _t = init("t"); public static final Key _u = init("u"); + public static final Key _v = init("v"); + public static final Key _w = init("w"); + public static final Key _x = init("x"); public static final Key _y = init("y"); public static final Key _z = init("z"); + public static final Key _aaa = init("aaa"); public static final Key _abort = init("abort"); public static final Key _access = init("access"); @@ -318,11 +332,9 @@ private static Collection.Key init(String key) { public static final Key _auth_user = init("auth_user"); public static final Key _author = init("author"); public static final Key _avg = init("avg"); - public static final Key _b = init("b"); public static final Key _body = init("body"); public static final Key _buffer = init("buffer"); public static final Key _by = init("by"); - public static final Key _c = init("c"); public static final Key _cache = init("cache"); public static final Key _call = init("call"); public static final Key _catch = init("catch"); @@ -369,7 +381,6 @@ private static Collection.Key init(String key) { public static final Key _custom3 = init("custom3"); public static final Key _custom4 = init("custom4"); public static final Key _customx = init("customx"); - public static final Key _d = init("d"); public static final Key _data = init("data"); public static final Key _data1 = init("data1"); public static final Key _data2 = init("data2"); @@ -452,7 +463,6 @@ private static Collection.Key init(String key) { public static final Key _http_Host = init("http_Host"); public static final Key _http_host = init("http_host"); public static final Key _https = init("https"); - public static final Key _i = init("i"); public static final Key _id = init("id"); public static final Key _idx = init("idx"); public static final Key _indexOf = init("indexOf"); @@ -468,7 +478,6 @@ private static Collection.Key init(String key) { public static final Key _islucee11 = init("islucee11"); public static final Key _item = init("item"); public static final Key _iterator = init("iterator"); - public static final Key _j = init("j"); public static final Key _java = init("java"); public static final Key _javaLoader = init("javaLoader"); public static final Key _jsessionid = init("jsessionid"); @@ -497,7 +506,6 @@ private static Collection.Key init(String key) { public static final Key _logid = init("logid"); public static final Key _login = init("login"); public static final Key _logout = init("logout"); - public static final Key _m = init("m"); public static final Key _main = init("main"); public static final Key _max = init("max"); public static final Key _maxEvents = init("maxEvents"); @@ -561,7 +569,6 @@ private static Collection.Key init(String key) { public static final Key _property = init("property"); public static final Key _published = init("published"); public static final Key _put = init("put"); - public static final Key _q = init("q"); public static final Key _qDir = init("qDir"); public static final Key _qry = init("qry"); public static final Key _qtest = init("qtest"); @@ -670,7 +677,6 @@ private static Collection.Key init(String key) { public static final Key _urltoken = init("urltoken"); public static final Key _usage = init("usage"); public static final Key _utility = init("utility"); - public static final Key _v = init("v"); public static final Key _v_pages = init("v_pages"); public static final Key _validate = init("validate"); public static final Key _value = init("value"); @@ -689,7 +695,6 @@ private static Collection.Key init(String key) { public static final Key _width = init("width"); public static final Key _writeLine = init("writeLine"); public static final Key _wsdl = init("wsdl"); - public static final Key _x = init("x"); public static final Key _xfa = init("xfa"); public static final Key _xml = init("xml"); public static final Key _xtags = init("xtags"); @@ -933,6 +938,7 @@ private static Collection.Key init(String key) { public static final Key _webAdminPassword = init("webAdminPassword"); public static final Key _parsebody = init("parsebody"); + public static final Key _aroundEach = init("aroundEach"); public static final Key _extended_info = init("extended_info"); public static final Key _codePrintHTML = init("codePrintHTML"); public static final Key _codePrintPlain = init("codePrintPlain"); @@ -1453,8 +1459,6 @@ private static Collection.Key init(String key) { public static final Key _LAYOUTS = init("LAYOUTS"); public static final Key _TIMEOUTHOURSVALUE = init("TIMEOUTHOURSVALUE"); public static final Key __display = init("_display"); - public static final Key _l = init("l"); - public static final Key _r = init("r"); public static final Key _GETFIELDS = init("GETFIELDS"); public static final Key _DATASOURCES = init("DATASOURCES"); public static final Key _WIDTH = init("WIDTH"); @@ -2300,7 +2304,6 @@ private static Collection.Key init(String key) { public static final Key _49 = init("49"); public static final Key _50 = init("50"); public static final Key _USD = init("USD"); - public static final Key _w = init("w"); public static final Key _distrokid = init("distrokid"); public static final Key _5d83e9016ff804dc = init("5d83e9016ff804dc"); public static final Key _51 = init("51"); @@ -3089,6 +3092,31 @@ private static Collection.Key init(String key) { public static final Key _nameSpaceSeparator = init("nameSpaceSeparator"); public static final Key _callee = init("callee"); public static final Key _computed = init("computed"); + public static final Key _overhead = init("overhead"); + public static final Key _testskip = init("testskip"); + public static final Key _salt = init("salt"); + public static final Key _hspw = init("hspw"); + public static final Key _qualifier_appendix1 = init("qualifier_appendix1"); + public static final Key _getAssert = init("getAssert"); + public static final Key _beforeEach = init("beforeEach"); + public static final Key _afterEach = init("afterEach"); + public static final Key _afterAll = init("afterAll"); + public static final Key _beforeAll = init("beforeAll"); + public static final Key _getassert = init("getassert"); + public static final Key _setassert = init("setassert"); + public static final Key _setAssert = init("setAssert"); + public static final Key _cacheHandler = init("cacheHandler"); + public static final Key _MAVEN = init("MAVEN"); + public static final Key _eventGatewayInstances = init("eventGatewayInstances"); + public static final Key _appenderArguments = init("appenderArguments"); + public static final Key _appender = init("appender"); + public static final Key _layout = init("layout"); + public static final Key _admintype = init("admintype"); + public static final Key _webadminpassword = init("webadminpassword"); + public static final Key _basedir = init("basedir"); + public static final Key _srcall = init("srcall"); + public static final Key __tick = init("_tick"); + public static final Key __start = init("_start"); public static Map getKeys() { return keys; @@ -3104,4 +3132,10 @@ public static Key getKey(String key) { return k; } + public static void sync() { + for (Entry e: getKeys().entrySet()) { + KeyImpl.getKeys().put(e.getKey(), e.getValue()); + } + } + } \ No newline at end of file diff --git a/core/src/main/java/lucee/runtime/type/util/PropertyFactory.java b/core/src/main/java/lucee/runtime/type/util/PropertyFactory.java index d06d3418a8..d33c5504a2 100644 --- a/core/src/main/java/lucee/runtime/type/util/PropertyFactory.java +++ b/core/src/main/java/lucee/runtime/type/util/PropertyFactory.java @@ -23,6 +23,7 @@ import lucee.runtime.ComponentImpl; import lucee.runtime.component.Member; import lucee.runtime.component.Property; +import lucee.runtime.component.PropertyImpl; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.PageException; import lucee.runtime.op.Caster; @@ -51,6 +52,12 @@ public static void createPropertyUDFs(ComponentImpl comp, Property property) thr PropertyFactory.addSet(comp, property); } + createCollectionPropertyUDFs(comp, property); + } + + // LDEV-3335: Separated collection methods so they can be called independently + // when getter/setter are already generated as static flyweights in bytecode + public static void createCollectionPropertyUDFs(ComponentImpl comp, Property property) throws PageException { String fieldType = Caster.toString(property.getDynamicAttributes().get(PropertyFactory.FIELD_TYPE, null), null); // add @@ -67,18 +74,22 @@ else if ("one-to-one".equalsIgnoreCase(fieldType) || "many-to-one".equalsIgnoreC } public static void addGet(ComponentImpl comp, Property prop) throws ApplicationException { - Member m = comp.getMember(Component.ACCESS_PRIVATE, KeyImpl.init("get" + prop.getName()), true, false); + PropertyImpl propImpl = (PropertyImpl) prop; + Collection.Key getterKey = propImpl.getGetterKey(); + Member m = comp.getMember(Component.ACCESS_PRIVATE, getterKey, true, false); if (!(m instanceof UDF)) { UDF udf = new UDFGetterProperty(comp, prop); - comp.registerUDF(KeyImpl.init(udf.getFunctionName()), udf); + comp.registerUDF(getterKey, udf); } } public static void addSet(ComponentImpl comp, Property prop) throws PageException { - Member m = comp.getMember(Component.ACCESS_PRIVATE, KeyImpl.init("set" + prop.getName()), true, false); + PropertyImpl propImpl = (PropertyImpl) prop; + Collection.Key setterKey = propImpl.getSetterKey(); + Member m = comp.getMember(Component.ACCESS_PRIVATE, setterKey, true, false); if (!(m instanceof UDF)) { UDF udf = new UDFSetterProperty(comp, prop); - comp.registerUDF(KeyImpl.init(udf.getFunctionName()), udf); + comp.registerUDF(setterKey, udf); } } 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 4fb5873f8a..aaff1f1017 100755 --- a/core/src/main/java/lucee/runtime/type/util/StructUtil.java +++ b/core/src/main/java/lucee/runtime/type/util/StructUtil.java @@ -103,7 +103,7 @@ public static void putAll(Struct struct, Map map) { } public static Set> entrySet(Struct sct) { - boolean linked = sct instanceof StructImpl && ((StructImpl) sct).getType() == Struct.TYPE_LINKED; + boolean linked = sct instanceof StructImpl && (((StructImpl) sct).getType() == Struct.TYPE_LINKED || ((StructImpl) sct).getType() == StructImpl.TYPE_LINKED_NOT_SYNC); Iterator> it = sct.entryIterator(); Entry e; Set> set = linked ? new LinkedHashSet>() : new HashSet>(); @@ -115,7 +115,7 @@ public static Set> entrySet(Struct sct) { } public static Set keySet(Struct sct) { - boolean linked = sct instanceof StructSupport && ((StructSupport) sct).getType() == Struct.TYPE_LINKED; + boolean linked = sct instanceof StructSupport && (((StructSupport) sct).getType() == Struct.TYPE_LINKED || ((StructSupport) sct).getType() == StructImpl.TYPE_LINKED_NOT_SYNC); Iterator it = sct.keyIterator(); Set set = linked ? new LinkedHashSet() : new HashSet(); @@ -127,7 +127,7 @@ public static Set keySet(Struct sct) { public static DumpTable toDumpTable(Struct sct, String title, PageContext pageContext, int maxlevel, DumpProperties dp) { Key[] keys = CollectionUtil.keys(sct); - if (!(sct instanceof StructSupport) || ((StructSupport) sct).getType() != Struct.TYPE_LINKED) keys = order(sct, CollectionUtil.keys(sct)); + if (!(sct instanceof StructSupport) || (((StructSupport) sct).getType() != Struct.TYPE_LINKED && ((StructSupport) sct).getType() != StructImpl.TYPE_LINKED_NOT_SYNC)) keys = order(sct, CollectionUtil.keys(sct)); DumpTable table = new DumpTable("struct", "#468faf", "#89c2d9", "#000000");// "#9999ff","#ccccff","#000000" int maxkeys = dp.getMaxKeys(); @@ -170,7 +170,7 @@ else if (sct.size() > 10 && dp.getMetainfo()) { } private static Key[] order(Struct sct, Key[] keys) { - if (sct instanceof StructImpl && ((StructImpl) sct).getType() == Struct.TYPE_LINKED) return keys; + if (sct instanceof StructImpl && (((StructImpl) sct).getType() == Struct.TYPE_LINKED || ((StructImpl) sct).getType() == StructImpl.TYPE_LINKED_NOT_SYNC)) return keys; TextComparator comp = new TextComparator(true, true); Arrays.sort(keys, comp); diff --git a/core/src/main/java/lucee/runtime/util/threading/Closer.java b/core/src/main/java/lucee/runtime/util/threading/Closer.java index a17a29dd08..8cdea5919e 100644 --- a/core/src/main/java/lucee/runtime/util/threading/Closer.java +++ b/core/src/main/java/lucee/runtime/util/threading/Closer.java @@ -4,6 +4,7 @@ import java.util.concurrent.LinkedBlockingQueue; import lucee.commons.io.log.Log; +import lucee.commons.io.log.LogUtil; public final class Closer { private CloserThread thread; @@ -48,7 +49,7 @@ public void run() { CloserJob job = jobs.poll(); if (job != null) { - if (log != null) log.debug("Closer", "executing job: " + job.getLablel()); + if (LogUtil.doesDebug(log)) log.debug("Closer", "executing job: " + job.getLablel()); long now = System.currentTimeMillis(); try { @@ -63,12 +64,12 @@ public void run() { else { long now = System.currentTimeMillis(); if (lastMod + IDLE_TIMEOUT < now) { - if (log != null) log.debug("Closer", "nothing to do, idle timeout reached, stoping observer "); + if (LogUtil.doesDebug(log)) log.debug("Closer", "nothing to do, idle timeout reached, stoping observer "); break; } - else if (log != null) log.debug("Closer", "nothing to do, remaining idle for another " + ((lastMod + IDLE_TIMEOUT) - now) + "ms"); + else if (LogUtil.doesDebug(log)) log.debug("Closer", "nothing to do, remaining idle for another " + ((lastMod + IDLE_TIMEOUT) - now) + "ms"); } - if (log != null) log.debug("Closer", "sleep for " + INTERVALL + "ms"); + if (LogUtil.doesDebug(log)) log.debug("Closer", "sleep for " + INTERVALL + "ms"); try { sleep(INTERVALL); } diff --git a/core/src/main/java/lucee/transformer/bytecode/PageImpl.java b/core/src/main/java/lucee/transformer/bytecode/PageImpl.java index 3f853803f7..64a92fa602 100755 --- a/core/src/main/java/lucee/transformer/bytecode/PageImpl.java +++ b/core/src/main/java/lucee/transformer/bytecode/PageImpl.java @@ -20,9 +20,12 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -75,6 +78,7 @@ import lucee.transformer.bytecode.statement.tag.TagComponent; import lucee.transformer.bytecode.statement.tag.TagImport; import lucee.transformer.bytecode.statement.tag.TagInterface; +import lucee.transformer.bytecode.statement.tag.TagProperty; import lucee.transformer.bytecode.statement.udf.Function; import lucee.transformer.bytecode.util.ASMConstants; import lucee.transformer.bytecode.util.ASMUtil; @@ -85,6 +89,9 @@ import lucee.transformer.bytecode.visitor.DecisionIntVisitor; import lucee.transformer.bytecode.visitor.OnFinally; import lucee.transformer.bytecode.visitor.TryCatchFinallyVisitor; +import lucee.transformer.bytecode.literal.LitBooleanImpl; +import lucee.transformer.bytecode.literal.LitNumberImpl; +import lucee.transformer.bytecode.literal.LitStringImpl; import lucee.transformer.expression.ExprString; import lucee.transformer.expression.Expression; import lucee.transformer.expression.literal.LitString; @@ -133,6 +140,24 @@ public final class PageImpl extends BodyBase implements Page { // public static final Type STRUCT_IMPL = Type.getType(StructImpl.class); public static final Method INIT_STRUCT_IMPL = new Method("", Types.VOID, new Type[] {}); + // LDEV-3335: Flyweight UDF Type/Method constants + private static final Type TYPE_MAP = Type.getType(Map.class); + private static final Type TYPE_LINKED_HASH_MAP = Type.getType(LinkedHashMap.class); + private static final Type TYPE_COLLECTION = Type.getType(Collection.class); + private static final Type TYPE_ITERATOR = Type.getType(Iterator.class); + private static final Type TYPE_UDF_GETTER_PROPERTY = Type.getType("Llucee/runtime/type/UDFGetterProperty;"); + private static final Type TYPE_UDF_SETTER_PROPERTY = Type.getType("Llucee/runtime/type/UDFSetterProperty;"); + private static final Method METHOD_MAP_PUT = new Method("put", Types.OBJECT, new Type[] { Types.OBJECT, Types.OBJECT }); + private static final Method METHOD_MAP_VALUES = new Method("values", TYPE_COLLECTION, new Type[] {}); + private static final Method METHOD_COLLECTION_ITERATOR = new Method("iterator", TYPE_ITERATOR, new Type[] {}); + private static final Method METHOD_ITERATOR_HAS_NEXT = new Method("hasNext", Type.BOOLEAN_TYPE, new Type[] {}); + private static final Method METHOD_ITERATOR_NEXT = new Method("next", Types.OBJECT, new Type[] {}); + private static final Method METHOD_PROPERTY_GET_GETTER = new Method("getGetter", Type.BOOLEAN_TYPE, new Type[] {}); + private static final Method METHOD_PROPERTY_GET_SETTER = new Method("getSetter", Type.BOOLEAN_TYPE, new Type[] {}); + private static final Method METHOD_PROPERTY_GET_GETTER_KEY = new Method("getGetterKey", Types.COLLECTION_KEY, new Type[] {}); + private static final Method METHOD_PROPERTY_GET_SETTER_KEY = new Method("getSetterKey", Types.COLLECTION_KEY, new Type[] {}); + private static final Method METHOD_UDF_CONSTRUCTOR = new Method("", Type.VOID_TYPE, new Type[] { Types.COMPONENT, Types.PROPERTY }); + // void call (lucee.runtime.PageContext) private final static Method CALL1 = new Method("call", Types.OBJECT, new Type[] { Types.PAGE_CONTEXT }); @@ -230,6 +255,12 @@ public final class PageImpl extends BodyBase implements Page { public static final Method UNDEFINED_SCOPE = new Method("us", Types.UNDEFINED, new Type[] {}); private static final Method FLUSH_AND_POP = new Method("flushAndPop", Types.VOID, new Type[] { Types.PAGE_CONTEXT, Types.BODY_CONTENT }); private static final Method CLEAR_AND_POP = new Method("clearAndPop", Types.VOID, new Type[] { Types.PAGE_CONTEXT, Types.BODY_CONTENT }); + + // Standard property attributes that are handled explicitly (not dynamic) + private static final Set STANDARD_PROPERTY_ATTRS = new HashSet<>(Arrays.asList( + "name", "type", "default", "access", "hint", "displayname", "required", "setter", "getter" + )); + public static final byte CF = (byte) 207; public static final byte _33 = (byte) 51; // private static final boolean ADD_C33 = false; @@ -992,7 +1023,15 @@ private void writeOutStatic(PageSource optionalPS, ConstrBytecodeContext constr, boolean addStatic = isComponent() || isInterface(); - if (addStatic) cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "staticStruct", "Llucee/runtime/component/StaticStruct;", null, null).visitEnd(); + if (addStatic) { + cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "staticStruct", "Llucee/runtime/component/StaticStruct;", null, null).visitEnd(); + // Generate per-class static property registry field + cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "__staticProperties", "Ljava/util/Map;", + "Ljava/util/Map;", null).visitEnd(); + // LDEV-3335: Generate flyweight accessor UDF registry field + cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "__staticAccessorUDFs", "Ljava/util/Map;", + "Ljava/util/Map;", null).visitEnd(); + } cw.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "keys", Types.COLLECTION_KEY_ARRAY.toString(), null, null).visitEnd(); @@ -1004,9 +1043,291 @@ private void writeOutStatic(PageSource optionalPS, ConstrBytecodeContext constr, ga.dup(); ga.invokeConstructor(Types.STATIC_STRUCT, CONSTR_STATIC_STRUCT); ga.putStatic(Type.getObjectType(name), "staticStruct", Types.STATIC_STRUCT); + + // Initialize __staticProperties = new LinkedHashMap<>() + ga.newInstance(Type.getType(LinkedHashMap.class)); + ga.dup(); + ga.invokeConstructor(Type.getType(LinkedHashMap.class), new Method("", Type.VOID_TYPE, new Type[] {})); + ga.putStatic(Type.getObjectType(name), "__staticProperties", Type.getType(Map.class)); + + // LDEV-3335: Initialize __staticAccessorUDFs = new LinkedHashMap<>() + ga.newInstance(TYPE_LINKED_HASH_MAP); + ga.dup(); + ga.invokeConstructor(TYPE_LINKED_HASH_MAP, new Method("", Type.VOID_TYPE, new Type[] {})); + ga.putStatic(Type.getObjectType(name), "__staticAccessorUDFs", TYPE_MAP); + } + + ///////////////// + // Register static properties for components + // IMPORTANT: Do this BEFORE creating keys array so all keys are registered first + if (addStatic && component != null && component.getBody() != null) { + List statements = component.getBody().getStatements(); + if (statements != null) { + for (Statement stmt : statements) { + if (stmt instanceof TagProperty) { + TagProperty tagProp = (TagProperty) stmt; + Tag tag = (Tag) tagProp; + + // Extract property attributes + String propName = getTagAttributeValue(tag, "name"); + String propType = getTagAttributeValue(tag, "type"); + String propAccess = getTagAttributeValue(tag, "access"); + String propHint = getTagAttributeValue(tag, "hint"); + String propDisplayname = getTagAttributeValue(tag, "displayname"); + String propRequired = getTagAttributeValue(tag, "required"); + String propSetter = getTagAttributeValue(tag, "setter"); + String propGetter = getTagAttributeValue(tag, "getter"); + Attribute propDefaultAttr = tag.getAttribute("default"); + + if (propName != null) { + // Generate: PropertyImpl prop = new PropertyImpl(); + ga.newInstance(Types.PROPERTY_IMPL); + ga.dup(); + ga.invokeConstructor(Types.PROPERTY_IMPL, new Method("", Type.VOID_TYPE, new Type[] {})); + int propLocal = ga.newLocal(Types.PROPERTY_IMPL); + ga.storeLocal(propLocal); + + // prop.setName("propName"); + ga.loadLocal(propLocal); + ga.push(propName); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setName", Type.VOID_TYPE, new Type[] { Types.STRING })); + + // prop.setNameAsKey(KeyImpl.init(propName)) - cache the key at class-load time + ga.loadLocal(propLocal); + ga.push(propName); + ga.invokeStatic(Type.getType("Llucee/runtime/type/KeyImpl;"), new Method("init", Types.COLLECTION_KEY, new Type[] { Types.STRING })); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setNameAsKey", Type.VOID_TYPE, new Type[] { Types.COLLECTION_KEY })); + + // prop.setGetterKey(KeyImpl.init("get" + propName)) - cache getter key + ga.loadLocal(propLocal); + ga.push("get" + propName); + ga.invokeStatic(Type.getType("Llucee/runtime/type/KeyImpl;"), new Method("init", Types.COLLECTION_KEY, new Type[] { Types.STRING })); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setGetterKey", Type.VOID_TYPE, new Type[] { Types.COLLECTION_KEY })); + + // prop.setSetterKey(KeyImpl.init("set" + propName)) - cache setter key + ga.loadLocal(propLocal); + ga.push("set" + propName); + ga.invokeStatic(Type.getType("Llucee/runtime/type/KeyImpl;"), new Method("init", Types.COLLECTION_KEY, new Type[] { Types.STRING })); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setSetterKey", Type.VOID_TYPE, new Type[] { Types.COLLECTION_KEY })); + + // prop.setType("type") if provided + if (propType != null) { + ga.loadLocal(propLocal); + ga.push(propType); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setType", Type.VOID_TYPE, new Type[] { Types.STRING })); + } + + // prop.setAccess("access") if provided + if (propAccess != null) { + ga.loadLocal(propLocal); + ga.push(propAccess); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setAccess", Type.VOID_TYPE, new Type[] { Types.STRING })); + } + + // prop.setHint("hint") if provided + if (propHint != null) { + ga.loadLocal(propLocal); + ga.push(propHint); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setHint", Type.VOID_TYPE, new Type[] { Types.STRING })); + } + + // prop.setDisplayname("displayname") if provided + if (propDisplayname != null) { + ga.loadLocal(propLocal); + ga.push(propDisplayname); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDisplayname", Type.VOID_TYPE, new Type[] { Types.STRING })); + } + + // prop.setRequired(boolean) only if explicitly provided + if (propRequired != null) { + ga.loadLocal(propLocal); + ga.push("true".equalsIgnoreCase(propRequired) || "yes".equalsIgnoreCase(propRequired)); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setRequired", Type.VOID_TYPE, new Type[] { Type.BOOLEAN_TYPE })); + } + + // prop.setSetter(boolean) + boolean setter = propSetter == null || "true".equalsIgnoreCase(propSetter) || "yes".equalsIgnoreCase(propSetter); + ga.loadLocal(propLocal); + ga.push(setter); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setSetter", Type.VOID_TYPE, new Type[] { Type.BOOLEAN_TYPE })); + + // prop.setGetter(boolean) + boolean getter = propGetter == null || "true".equalsIgnoreCase(propGetter) || "yes".equalsIgnoreCase(propGetter); + ga.loadLocal(propLocal); + ga.push(getter); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setGetter", Type.VOID_TYPE, new Type[] { Type.BOOLEAN_TYPE })); + + // prop.setDefault(value) if it's a simple literal + // Only handle simple literals (Literal interface) - complex expressions like now() + // or #myVar# need PageContext and must be evaluated at runtime, not class-load time + if (propDefaultAttr != null && propDefaultAttr.getValue() instanceof Literal) { + Expression defaultExpr = propDefaultAttr.getValue(); + + // Handle simple literals only - complex expressions handled at runtime + if (defaultExpr instanceof LitStringImpl) { + String value = ((LitStringImpl) defaultExpr).getString(); + ga.loadLocal(propLocal); + ga.push(value); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDefault", Type.VOID_TYPE, new Type[] { Types.OBJECT })); + } + else if (defaultExpr instanceof LitNumberImpl) { + Number value = ((LitNumberImpl) defaultExpr).getNumber(); + ga.loadLocal(propLocal); + ga.push(value.doubleValue()); + ga.box(Type.DOUBLE_TYPE); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDefault", Type.VOID_TYPE, new Type[] { Types.OBJECT })); + } + else if (defaultExpr instanceof LitBooleanImpl) { + Boolean value = ((LitBooleanImpl) defaultExpr).getBoolean(); + ga.loadLocal(propLocal); + ga.push(value.booleanValue()); + ga.box(Type.BOOLEAN_TYPE); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDefault", Type.VOID_TYPE, new Type[] { Types.OBJECT })); + } + // else: complex expression - will be handled at runtime in TagProperty + } + + // Collect dynamic attributes (non-standard attributes) + Map allAttrs = tag.getAttributes(); + List dynamicAttrs = new ArrayList<>(); + for (Attribute attr : allAttrs.values()) { + String attrName = attr.getName().toLowerCase(); + // Skip standard attributes + if (!STANDARD_PROPERTY_ATTRS.contains(attrName)) { + dynamicAttrs.add(attr); + } + } + + // If there are dynamic attributes OR explicit required, create metadata struct and set it + if (!dynamicAttrs.isEmpty() || propRequired != null) { + // Calculate exact size needed: dynamic attrs + required (if set) + int dynAttrCount = dynamicAttrs.size() + (propRequired != null ? 1 : 0); + + // Generate: Struct dynAttrs = new StructImpl(TYPE_REGULAR, size); + ga.newInstance(Types.STRUCT_IMPL); + ga.dup(); + ga.getStatic(Types.STRUCT_IMPL, "TYPE_REGULAR", Type.INT_TYPE); + ga.push(dynAttrCount); + ga.invokeConstructor(Types.STRUCT_IMPL, new Method("", Type.VOID_TYPE, new Type[] { Type.INT_TYPE, Type.INT_TYPE })); + int dynAttrsLocal = ga.newLocal(Types.STRUCT); + ga.storeLocal(dynAttrsLocal); + + // For each dynamic attribute: dynAttrs.setEL(key, value) + for (Attribute dynAttr : dynamicAttrs) { + String dynAttrValue = getTagAttributeValue(tag, dynAttr.getName()); + if (dynAttrValue != null) { + ga.loadLocal(dynAttrsLocal); + + // Create key at class-load time + ga.push(dynAttr.getName()); + ga.invokeStatic(Type.getType("Llucee/runtime/type/KeyImpl;"), new Method("init", Types.COLLECTION_KEY, new Type[] { Types.STRING })); + + ga.push(dynAttrValue); + ga.invokeInterface(Types.STRUCT, SET_EL); + ga.pop(); // Pop return value + } + } + + // Add required to dynamic attributes if explicitly set + if (propRequired != null) { + ga.loadLocal(dynAttrsLocal); + + // Create "required" key at class-load time + ga.push("required"); + ga.invokeStatic(Type.getType("Llucee/runtime/type/KeyImpl;"), new Method("init", Types.COLLECTION_KEY, new Type[] { Types.STRING })); + + ga.push("true".equalsIgnoreCase(propRequired) || "yes".equalsIgnoreCase(propRequired) ? "yes" : "no"); + ga.invokeInterface(Types.STRUCT, SET_EL); + ga.pop(); // Pop return value + } + + // prop.setDynamicAttributes(dynAttrs); + ga.loadLocal(propLocal); + ga.loadLocal(dynAttrsLocal); + ga.invokeVirtual(Types.PROPERTY_IMPL, new Method("setDynamicAttributes", Type.VOID_TYPE, new Type[] { Types.STRUCT })); + } + + // __staticProperties.put(propName.toLowerCase(), prop); + ga.getStatic(Type.getObjectType(name), "__staticProperties", Type.getType(Map.class)); + ga.push(propName.toLowerCase()); + ga.loadLocal(propLocal); + ga.invokeInterface(Type.getType(Map.class), new Method("put", Type.getType(Object.class), new Type[] { Type.getType(Object.class), Type.getType(Object.class) })); + ga.pop(); // Pop return value from map.put() + } + } + } + } } - // Array initialization + + // LDEV-3335: Generate flyweight accessor UDFs from static properties + // Iterate over __staticProperties.values() and create getter/setter UDFs + if (addStatic && component != null) { + // for (Property prop : __staticProperties.values()) + ga.getStatic(Type.getObjectType(name), "__staticProperties", TYPE_MAP); + ga.invokeInterface(TYPE_MAP, METHOD_MAP_VALUES); + ga.invokeInterface(TYPE_COLLECTION, METHOD_COLLECTION_ITERATOR); + int iteratorLocal = ga.newLocal(TYPE_ITERATOR); + ga.storeLocal(iteratorLocal); + + Label loopStart = ga.newLabel(); + Label loopEnd = ga.newLabel(); + + ga.mark(loopStart); + ga.loadLocal(iteratorLocal); + ga.invokeInterface(TYPE_ITERATOR, METHOD_ITERATOR_HAS_NEXT); + ga.visitJumpInsn(Opcodes.IFEQ, loopEnd); + + ga.loadLocal(iteratorLocal); + ga.invokeInterface(TYPE_ITERATOR, METHOD_ITERATOR_NEXT); + ga.checkCast(Types.PROPERTY_IMPL); + int propLocal = ga.newLocal(Types.PROPERTY_IMPL); + ga.storeLocal(propLocal); + + // if (prop.getGetter()) + Label skipGetter = ga.newLabel(); + ga.loadLocal(propLocal); + ga.invokeVirtual(Types.PROPERTY_IMPL, METHOD_PROPERTY_GET_GETTER); + ga.visitJumpInsn(Opcodes.IFEQ, skipGetter); + + // __staticAccessorUDFs.put(prop.getGetterKey(), new UDFGetterProperty(null, prop)) + ga.getStatic(Type.getObjectType(name), "__staticAccessorUDFs", TYPE_MAP); + ga.loadLocal(propLocal); + ga.invokeVirtual(Types.PROPERTY_IMPL, METHOD_PROPERTY_GET_GETTER_KEY); + ga.newInstance(TYPE_UDF_GETTER_PROPERTY); + ga.dup(); + ga.visitInsn(Opcodes.ACONST_NULL); // null component for flyweight + ga.loadLocal(propLocal); + ga.invokeConstructor(TYPE_UDF_GETTER_PROPERTY, METHOD_UDF_CONSTRUCTOR); + ga.invokeInterface(TYPE_MAP, METHOD_MAP_PUT); + ga.pop(); + + ga.mark(skipGetter); + + // if (prop.getSetter()) + Label skipSetter = ga.newLabel(); + ga.loadLocal(propLocal); + ga.invokeVirtual(Types.PROPERTY_IMPL, METHOD_PROPERTY_GET_SETTER); + ga.visitJumpInsn(Opcodes.IFEQ, skipSetter); + + // __staticAccessorUDFs.put(prop.getSetterKey(), new UDFSetterProperty(null, prop)) + ga.getStatic(Type.getObjectType(name), "__staticAccessorUDFs", TYPE_MAP); + ga.loadLocal(propLocal); + ga.invokeVirtual(Types.PROPERTY_IMPL, METHOD_PROPERTY_GET_SETTER_KEY); + ga.newInstance(TYPE_UDF_SETTER_PROPERTY); + ga.dup(); + ga.visitInsn(Opcodes.ACONST_NULL); // null component + ga.loadLocal(propLocal); + ga.invokeConstructor(TYPE_UDF_SETTER_PROPERTY, METHOD_UDF_CONSTRUCTOR); + ga.invokeInterface(TYPE_MAP, METHOD_MAP_PUT); + ga.pop(); + + ga.mark(skipSetter); + + ga.goTo(loopStart); + ga.mark(loopEnd); + } + // Array initialization - MUST be done AFTER property processing so all keys are registered ga.push(keys.size()); // Array size ga.newArray(Types.COLLECTION_KEY); @@ -1022,7 +1343,6 @@ private void writeOutStatic(PageSource optionalPS, ConstrBytecodeContext constr, } ga.putStatic(Type.getObjectType(name), "keys", Types.COLLECTION_KEY_ARRAY); - ///////////////// ga.returnValue(); ga.endMethod(); @@ -1036,6 +1356,107 @@ private void writeOutStatic(PageSource optionalPS, ConstrBytecodeContext constr, ga.endMethod(); } + // Generate: public Map getStaticProperties() { return __staticProperties; } + // Override ComponentPageImpl.getStaticProperties() to return the static property map + if (addStatic) { + Method getStaticPropsMethod = new Method("getStaticProperties", Type.getType(Map.class), new Type[] {}); + final GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, getStaticPropsMethod, null, null, cw); + ga.getStatic(Type.getObjectType(name), "__staticProperties", Type.getType(Map.class)); + ga.returnValue(); + ga.endMethod(); + } + + // LDEV-3335: Generate: public Map getStaticAccessorUDFs() { return __staticAccessorUDFs; } + // Override ComponentPageImpl.getStaticAccessorUDFs() to return the flyweight UDF map + if (addStatic) { + Method getStaticAccessorUDFsMethod = new Method("getStaticAccessorUDFs", TYPE_MAP, new Type[] {}); + final GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, getStaticAccessorUDFsMethod, null, null, cw); + ga.getStatic(Type.getObjectType(name), "__staticAccessorUDFs", TYPE_MAP); + ga.returnValue(); + ga.endMethod(); + } + + // Generate: public void initPropertiesStub(ComponentImpl impl) throws PageException + // Optimized property initialization that directly accesses static __staticProperties field + if (addStatic && component != null) { + Method initPropsStubMethod = new Method("initPropertiesStub", Type.VOID_TYPE, new Type[] { Types.COMPONENT_IMPL }); + final GeneratorAdapter ga = new GeneratorAdapter(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, initPropsStubMethod, null, + new Type[] { Types.PAGE_EXCEPTION }, cw); + + // if (__staticProperties != null && !__staticProperties.isEmpty()) { + Label afterNullCheck = ga.newLabel(); + Label continueProcessing = ga.newLabel(); + + // Get __staticProperties and check for null + ga.getStatic(Type.getObjectType(name), "__staticProperties", Type.getType(Map.class)); + ga.dup(); // Duplicate for null check + Label notNull = ga.newLabel(); + ga.visitJumpInsn(Opcodes.IFNONNULL, notNull); // If not null, continue + // If null: pop the dup and jump to end + ga.pop(); + ga.goTo(afterNullCheck); + + // Not null: check if empty + ga.mark(notNull); + ga.invokeInterface(Type.getType(Map.class), new Method("isEmpty", Type.BOOLEAN_TYPE, new Type[] {})); + ga.visitJumpInsn(Opcodes.IFEQ, continueProcessing); // IFEQ = if equal to zero (if false/not empty) + // If empty: jump to end + ga.goTo(afterNullCheck); + + // Continue processing: Get __staticProperties again for iteration + ga.mark(continueProcessing); + ga.getStatic(Type.getObjectType(name), "__staticProperties", Type.getType(Map.class)); + ga.invokeInterface(Type.getType(Map.class), new Method("values", Type.getType(java.util.Collection.class), new Type[] {})); + ga.invokeInterface(Type.getType(java.util.Collection.class), new Method("iterator", Type.getType(java.util.Iterator.class), new Type[] {})); + + int iteratorLocal = ga.newLocal(Type.getType(java.util.Iterator.class)); + ga.storeLocal(iteratorLocal); + + // Loop: while (iterator.hasNext()) + Label loopStart = ga.newLabel(); + Label loopEnd = ga.newLabel(); + + ga.mark(loopStart); + ga.loadLocal(iteratorLocal); + ga.invokeInterface(Type.getType(java.util.Iterator.class), new Method("hasNext", Type.BOOLEAN_TYPE, new Type[] {})); + ga.visitJumpInsn(Opcodes.IFEQ, loopEnd); // IFEQ = if equal to zero (if false) + + // PropertyImpl prop = iterator.next(); + ga.loadLocal(iteratorLocal); + ga.invokeInterface(Type.getType(java.util.Iterator.class), new Method("next", Type.getType(Object.class), new Type[] {})); + ga.checkCast(Types.PROPERTY_IMPL); + int propLocal = ga.newLocal(Types.PROPERTY_IMPL); + ga.storeLocal(propLocal); + + // impl.setProperty(prop); - this handles everything: registration, defaults, and UDF creation + ga.loadArg(0); // impl + ga.loadLocal(propLocal); + ga.invokeVirtual(Types.COMPONENT_IMPL, new Method("setProperty", Type.VOID_TYPE, new Type[] { Types.PROPERTY })); + + // Continue loop + ga.goTo(loopStart); + + ga.mark(loopEnd); + ga.mark(afterNullCheck); + + // return; + ga.returnValue(); + ga.endMethod(); + } + + } + + private String getTagAttributeValue(Tag tag, String attrName) { + Attribute attr = tag.getAttribute(attrName); + if (attr != null && attr.getValue() != null) { + try { + return attr.getValue().toString(); + } + catch (Exception e) { + // Can't get literal value + } + } + return null; } private void writeOutStaticConstructor(ConstrBytecodeContext constr, List keys, ClassWriter cw, TagCIObject component, String name) throws TransformerException { diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/For.java b/core/src/main/java/lucee/transformer/bytecode/statement/For.java index 85e99f2a7b..2cae4b3b66 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/For.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/For.java @@ -131,19 +131,19 @@ public void dump(Struct sct) { sct.setEL(KeyConstants._type, "ForStatement"); // init - { + if ( this.init != null ) { Struct init = new StructImpl(Struct.TYPE_LINKED); this.init.dump(init); sct.setEL(KeyConstants._init, init); } // test - { + if ( condition != null ) { Struct test = new StructImpl(Struct.TYPE_LINKED); condition.dump(test); sct.setEL(KeyConstants._test, test); } // alternate - { + if ( this.update != null ) { Struct update = new StructImpl(Struct.TYPE_LINKED); this.update.dump(update); sct.setEL(KeyConstants._update, update); diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagLoop.java b/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagLoop.java index 3d52f17884..6c26c1c135 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagLoop.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagLoop.java @@ -234,6 +234,7 @@ private void writeOutTypeTimes(BytecodeContext bc) throws TransformerException { adapter.storeLocal(times); ForVisitor fiv = new ForVisitor(); + loopVisitor = fiv; fiv.visitBegin(adapter, 1, false); getBody().writeOut(bc); fiv.visitEnd(bc, times, true, getStart()); diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagProperty.java b/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagProperty.java index 92dc54e40c..524f660d61 100644 --- a/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagProperty.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/tag/TagProperty.java @@ -21,38 +21,28 @@ import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.commons.Method; -import lucee.runtime.op.Caster; import lucee.transformer.Factory; import lucee.transformer.Position; import lucee.transformer.TransformerException; import lucee.transformer.bytecode.BytecodeContext; import lucee.transformer.bytecode.statement.FlowControlFinal; -import lucee.transformer.bytecode.util.ASMConstants; import lucee.transformer.bytecode.util.Types; import lucee.transformer.expression.Expression; +import lucee.transformer.expression.literal.Literal; +import lucee.transformer.expression.literal.LitBoolean; +import lucee.transformer.expression.literal.LitInteger; +import lucee.transformer.expression.literal.LitLong; +import lucee.transformer.expression.literal.LitNumber; +import lucee.transformer.expression.literal.LitString; import lucee.transformer.statement.tag.Attribute; import lucee.transformer.statement.tag.Tag; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - /** - * Optimized bytecode generation for cfproperty tags. - * Bypasses tag lifecycle overhead by calling ComponentUtil.registerProperty directly. + * Optimized bytecode generation for cfproperty tags. Property metadata registration happens in + * static initializer, complex default values are evaluated at runtime. */ public final class TagProperty extends TagBase { - private static final Method REGISTER_PROPERTY = new Method("registerProperty", Type.VOID_TYPE, new Type[] { - Types.PAGE_CONTEXT, Types.STRING, Types.STRING, Types.OBJECT, - Types.STRING, Types.STRING, Types.STRING, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE - }); - - private static final Method REGISTER_PROPERTY_WITH_DYNAMIC = new Method("registerProperty", Type.VOID_TYPE, new Type[] { - Types.PAGE_CONTEXT, Types.STRING, Types.STRING, Types.OBJECT, - Types.STRING, Types.STRING, Types.STRING, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE, Types.STRUCT - }); - public TagProperty(Factory f, Position start, Position end) { super(f, start, end); } @@ -64,160 +54,60 @@ public FlowControlFinal getFlowControlFinal() { @Override public void _writeOut(BytecodeContext bc) throws TransformerException { - final GeneratorAdapter adapter = bc.getAdapter(); - Tag tag = (Tag) this; - + // Property metadata registration now happens in static initializer () + // See PageImpl.writeOutStatic() for static property registration + // However, complex default values (arrays, structs, expressions) need per-instance evaluation + Tag tag = this; bc.visitLine(tag.getStart()); - try { - String name = null; - String type = null; - Attribute defaultValueAttr = null; - String access = null; - String hint = null; - String displayname = null; - boolean required = false; - boolean setter = true; - boolean getter = true; - - List dynamicAttrs = new ArrayList<>(); - Iterator it = tag.getAttributes().values().iterator(); - while (it.hasNext()) { - Attribute attr = it.next(); - String attrName = attr.getName().toLowerCase(); - - if ("name".equals(attrName)) { - name = getLiteralString(attr); - } - else if ("type".equals(attrName)) { - type = getLiteralString(attr); - } - else if ("default".equals(attrName)) { - defaultValueAttr = attr; - } - else if ("access".equals(attrName)) { - access = getLiteralString(attr); - } - else if ("hint".equals(attrName)) { - hint = getLiteralString(attr); - } - else if ("displayname".equals(attrName)) { - displayname = getLiteralString(attr); - } - else if ("required".equals(attrName)) { - String val = getLiteralString(attr); - required = Caster.toBoolean(val, false); - } - else if ("setter".equals(attrName)) { - String val = getLiteralString(attr); - setter = Caster.toBoolean(val, true); - } - else if ("getter".equals(attrName)) { - String val = getLiteralString(attr); - getter = Caster.toBoolean(val, true); - } - else { - dynamicAttrs.add(attr); - } - } - - adapter.loadArg(0); + Attribute defaultAttr = tag.getAttribute("default"); + Attribute nameAttr = tag.getAttribute("name"); - if (name != null) { - adapter.push(name); - } - else { - ASMConstants.NULL(adapter); - } + // Only handle complex defaults - simple literals are already handled in CLINIT + if (defaultAttr != null && nameAttr != null && defaultAttr.getValue() != null) { + Expression defaultExpr = defaultAttr.getValue(); + String propName = getLiteralString(nameAttr); - if (type != null) { - adapter.push(type); - } - else { - ASMConstants.NULL(adapter); - } - if (defaultValueAttr != null) { - defaultValueAttr.getValue().writeOut(bc, Expression.MODE_REF); - } - else { - ASMConstants.NULL(adapter); - } + // Check if it's a complex expression (not a simple literal) + boolean isComplex = !isSimpleLiteral(defaultExpr); - if (access != null) { - adapter.push(access); - } - else { - ASMConstants.NULL(adapter); - } + if (isComplex && propName != null) { + final GeneratorAdapter adapter = bc.getAdapter(); - if (hint != null) { - adapter.push(hint); - } - else { - ASMConstants.NULL(adapter); - } + // Evaluate the default expression with the current PageContext + defaultExpr.writeOut(bc, Expression.MODE_REF); + int defaultLocal = adapter.newLocal(Types.OBJECT); + adapter.storeLocal(defaultLocal); - if (displayname != null) { - adapter.push(displayname); - } - else { - ASMConstants.NULL(adapter); - } - - adapter.push(required); - - adapter.push(setter); - - adapter.push(getter); - - boolean hasExplicitRequired = tag.getAttributes().containsKey("required") && tag.getAttribute("required") != null; - - if (!dynamicAttrs.isEmpty() || hasExplicitRequired) { - adapter.newInstance(Types.STRUCT_IMPL); - adapter.dup(); - adapter.invokeConstructor(Types.STRUCT_IMPL, new Method("", Type.VOID_TYPE, new Type[] {})); - - for (Attribute dynAttr : dynamicAttrs) { - adapter.dup(); - adapter.push(dynAttr.getName()); - dynAttr.getValue().writeOut(bc, Expression.MODE_REF); - adapter.invokeInterface(Types.STRUCT, new Method("setEL", Types.OBJECT, new Type[] {Types.STRING, Types.OBJECT})); - adapter.pop(); - } - - // Only add required if explicitly set - if (hasExplicitRequired) { - adapter.dup(); - adapter.push("required"); - adapter.push(required ? "yes" : "no"); - adapter.invokeInterface(Types.STRUCT, new Method("setEL", Types.OBJECT, new Type[] {Types.STRING, Types.OBJECT})); - adapter.pop(); - } - - adapter.invokeStatic(Type.getType("Llucee/runtime/type/util/ComponentUtil;"), REGISTER_PROPERTY_WITH_DYNAMIC); - } - else { - adapter.invokeStatic(Type.getType("Llucee/runtime/type/util/ComponentUtil;"), REGISTER_PROPERTY); + // Get PageContext from arg0 and set the value in variables scope + // pc.variablesScope().setEL(KeyImpl.init(propName), defaultValue) + adapter.loadArg(0); // Load PageContext pc + adapter.invokeVirtual(Types.PAGE_CONTEXT, new Method("variablesScope", Types.VARIABLES, new Type[] {})); + adapter.push(propName); + adapter.invokeStatic(Type.getType("Llucee/runtime/type/KeyImpl;"), new Method("init", Types.COLLECTION_KEY, new Type[] { Types.STRING })); + adapter.loadLocal(defaultLocal); + adapter.invokeInterface(Types.SCOPE, new Method("setEL", Types.OBJECT, new Type[] { Types.COLLECTION_KEY, Types.OBJECT })); + adapter.pop(); // Pop return value } + } - bc.visitLine(tag.getEnd()); + bc.visitLine(tag.getEnd()); + } - } catch (Exception e) { - if (e instanceof TransformerException) throw (TransformerException) e; - throw new TransformerException(bc, e, tag.getStart()); - } + private boolean isSimpleLiteral(Expression expr) { + return expr instanceof LitString || expr instanceof LitNumber || + expr instanceof LitBoolean || expr instanceof LitInteger || + expr instanceof LitLong; } private String getLiteralString(Attribute attr) { - try { - if (attr != null && attr.getValue() != null) { - return attr.getValue().toString(); + if (attr != null && attr.getValue() != null) { + Expression expr = attr.getValue(); + if (expr instanceof Literal) { + return ((Literal) expr).getString(); } } - catch (Exception e) { - // Fall back to null if we can't get literal value - } return null; } -} \ No newline at end of file +} 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 fc4bd63b18..62acc13272 100644 --- a/core/src/main/java/lucee/transformer/bytecode/util/JavaProxyFactory.java +++ b/core/src/main/java/lucee/transformer/bytecode/util/JavaProxyFactory.java @@ -48,6 +48,7 @@ import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.ExtendableClassLoader; import lucee.commons.lang.PhysicalClassLoader; +import lucee.commons.lang.PhysicalClassLoaderFactory; import lucee.commons.lang.StringUtil; import lucee.loader.engine.CFMLEngine; import lucee.loader.engine.CFMLEngineFactory; @@ -504,7 +505,7 @@ public static ClassLoader getRPCClassLoaderFromClass(PageContext pc, Class clazz return cl; } else if (cl instanceof BundleClassLoader) { - return PhysicalClassLoader.getRPCClassLoader(pc.getConfig(), (BundleClassLoader) cl, false); + return PhysicalClassLoaderFactory.getRPCClassLoader(pc.getConfig(), (BundleClassLoader) cl, false); } } return null; 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 f3486b7c6e..4fb6abe074 100755 --- a/core/src/main/java/lucee/transformer/bytecode/util/Types.java +++ b/core/src/main/java/lucee/transformer/bytecode/util/Types.java @@ -160,6 +160,9 @@ public final class Types { public static final Type COMPONENT_IMPL = Type.getType(lucee.runtime.ComponentImpl.class); public static final Type INTERFACE_IMPL = Type.getType(lucee.runtime.InterfaceImpl.class); + public static final Type PROPERTY_IMPL = Type.getType(lucee.runtime.component.PropertyImpl.class); + public static final Type COMPONENT_PROPERTIES = Type.getType(lucee.runtime.ComponentProperties.class); + public static final Type PROPERTY = Type.getType(lucee.runtime.component.Property.class); public static final Type DATE_TIME = Type.getType(lucee.runtime.type.dt.DateTime.class); diff --git a/core/src/main/java/lucee/transformer/cfml/evaluator/impl/Component.java b/core/src/main/java/lucee/transformer/cfml/evaluator/impl/Component.java index 3765e9970a..a296d76075 100755 --- a/core/src/main/java/lucee/transformer/cfml/evaluator/impl/Component.java +++ b/core/src/main/java/lucee/transformer/cfml/evaluator/impl/Component.java @@ -29,6 +29,7 @@ import lucee.commons.lang.StringUtil; import lucee.runtime.PageSource; import lucee.runtime.config.Constants; +import lucee.runtime.interpreter.JSONExpressionInterpreter; import lucee.runtime.type.util.ComponentUtil; import lucee.runtime.type.util.ListUtil; import lucee.transformer.Page; @@ -259,6 +260,27 @@ else throw new EvaluatorException( "Value [" + ls.getString() + "] from attribute [modifier] of the tag [" + tlt.getFullName() + "] is invalid, valid values are [none, abstract, final]"); } } + + // javasettings - validate it's not empty (catches misparsing of struct literals) + attr = tag.getAttribute("javasettings"); + if (attr != null) { + Expression expr = attr.getValue(); + if (expr instanceof LitString) { + String val = ((LitString) expr).getString(); + if (StringUtil.isEmpty(val, true)) { + throw new EvaluatorException( + "Invalid javasettings attribute. Struct literal syntax is not supported for javasettings, use a json string: javasettings='{maven:[\"groupId:artifactId:version\"]}'"); + } + // Validate JSON syntax at compile time (LDEV-5927) + try { + new JSONExpressionInterpreter().interpret(null, val); + } + catch (Exception e) { + throw new EvaluatorException("Invalid JSON in javasettings attribute: " + e.getMessage() + + ". Ensure the JSON is valid: javasettings='{maven:[\"groupId:artifactId:version\"]}'"); + } + } + } } private String toString(Set set) { diff --git a/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java b/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java index 062c5ae58d..d27229dda0 100644 --- a/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java +++ b/core/src/main/java/lucee/transformer/dynamic/DynamicClassLoader.java @@ -4,7 +4,8 @@ import java.io.IOException; import java.io.InputStream; import java.lang.instrument.UnmodifiableClassException; -import java.lang.ref.SoftReference; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.util.Map; @@ -32,7 +33,7 @@ public final class DynamicClassLoader extends ClassLoader implements ExtendableC private final Map allLoadedClasses = new ConcurrentHashMap<>(); // this includes all renames private final Map unavaiClasses = new ConcurrentHashMap<>(); - private final Map> instances = new ConcurrentHashMap<>(); + private final Map> instances = new ConcurrentHashMap<>(); private static final AtomicLong counter = new AtomicLong(Long.MAX_VALUE - 1); private static long _start = 0L; @@ -100,27 +101,27 @@ public DynamicClassLoader(Resource directory, ClassLoader[] parentClassLoaders, public Object loadInstance(String name) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { - SoftReference ref = instances.get(name); + Reference ref = instances.get(name); Object value; if (ref != null && (value = ref.get()) != null) { return value; } Class clazz = loadClass(name, false, true); value = clazz.getConstructor().newInstance(); - instances.put(name, new SoftReference(value)); + instances.put(name, new WeakReference(value)); return value; } public Object loadInstance(String name, byte[] barr) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, UnmodifiableClassException, IOException { - SoftReference ref = instances.get(name); + Reference ref = instances.get(name); Object value; if (ref != null && (value = ref.get()) != null) { return value; } Class clazz = loadClass(name, barr); value = clazz.getConstructor().newInstance(); - instances.put(name, new SoftReference(value)); + instances.put(name, new WeakReference(value)); return value; } diff --git a/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java b/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java index ab0a9a2098..21a9494188 100644 --- a/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java +++ b/core/src/main/java/lucee/transformer/dynamic/DynamicInvoker.java @@ -393,6 +393,21 @@ public DynamicClassLoader getCL(Class clazz) { return cl; } + public int remove(ClassLoader parent) { + int count = 0; + DynamicClassLoader cl = loaders.get(parent.hashCode()); + if (cl != null) { + synchronized (token) { + cl = loaders.get(parent.hashCode()); + if (cl != null) { + count++; + loaders.remove(parent.hashCode()); + } + } + } + return count; + } + public void cleanup() { Set set = new java.util.HashSet<>(); for (DynamicClassLoader cl: loaders.values()) { diff --git a/core/src/main/java/lucee/transformer/dynamic/meta/dynamic/ClazzDynamic.java b/core/src/main/java/lucee/transformer/dynamic/meta/dynamic/ClazzDynamic.java index ab17c7e8da..7f93c86ad6 100644 --- a/core/src/main/java/lucee/transformer/dynamic/meta/dynamic/ClazzDynamic.java +++ b/core/src/main/java/lucee/transformer/dynamic/meta/dynamic/ClazzDynamic.java @@ -6,12 +6,11 @@ import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; -import java.lang.ref.Reference; -import java.lang.ref.SoftReference; import java.security.CodeSource; import java.security.ProtectionDomain; import java.util.HashSet; import java.util.IdentityHashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; @@ -64,23 +63,16 @@ public final class ClazzDynamic extends Clazz { private static Map clids = new IdentityHashMap<>(); private static String systemId; - private static Map> classes = new IdentityHashMap<>(); - // private static Map> classes = new ConcurrentHashMap<>(); - - /* - * private static double generateClassLoderId = 0; private static double path = 0; private static - * double isFile = 0; private static double deserialize = 0; private static double put = 0; private - * static double neww = 0; private static double serialize = 0; private static double done = 0; - * private static int count = 0; - */ + private static Map classes = new ConcurrentHashMap<>(); + private static Map> membersCollectionOld = new ConcurrentHashMap<>(); + private static Map> membersCollection = new ConcurrentHashMap<>(); public static Clazz getInstance(Class clazz, Resource dir, Log log) { - Clazz cd = null; - Reference sr = classes.get(clazz); - if (sr == null || (cd = sr.get()) == null) { + Clazz cd = classes.get(clazz); + if (cd == null) { synchronized (clazz) { - sr = classes.get(clazz); - if (sr == null || (cd = sr.get()) == null) { + cd = classes.get(clazz); + if (cd == null) { if (LogUtil.doesDebug(log)) log.debug("dynamic", "extract metadata from [" + clazz.getName() + "]"); try { cd = new ClazzDynamic(clazz, log); @@ -89,13 +81,40 @@ public static Clazz getInstance(Class clazz, Resource dir, Log log) { if (log != null) log.error("dynamic", ioe); cd = new ClazzReflection(clazz, log); } - classes.put(clazz, new SoftReference(cd)); + classes.put(clazz, cd); } } } return cd; } + public static int remove(PhysicalClassLoader pcl) { + int count = 0; + // Use iterator to safely remove during iteration + synchronized (classes) { + Iterator it = classes.keySet().iterator(); + while (it.hasNext()) { + Class clazz = it.next(); + if (clazz.getClassLoader() == pcl) { + it.remove(); + count++; + } + } + } + + synchronized (membersCollection) { + Iterator it = membersCollection.keySet().iterator(); + while (it.hasNext()) { + Class clazz = it.next(); + if (clazz.getClassLoader() == pcl) { + it.remove(); + count++; + } + } + } + return count; + } + public static String generateClassLoderId(Class clazz) { ClassLoader cl = clazz.getClassLoader(); String jv = HashUtil.create64BitHashAsString(System.getProperty("java.version"), Character.MAX_RADIX); @@ -637,9 +656,6 @@ private static Map cloneIt(Map m return cloned; } - private static Map> membersCollectionOld = new ConcurrentHashMap<>(); - private static Map> membersCollection = new IdentityHashMap<>(); - public static void serialize(Serializable o, OutputStream os) throws IOException { ObjectOutputStream oos = null; try { diff --git a/core/src/main/java/lucee/transformer/library/tag/TagLibFactory.java b/core/src/main/java/lucee/transformer/library/tag/TagLibFactory.java index 904d98cd9e..32a881f51a 100755 --- a/core/src/main/java/lucee/transformer/library/tag/TagLibFactory.java +++ b/core/src/main/java/lucee/transformer/library/tag/TagLibFactory.java @@ -80,6 +80,9 @@ public final class TagLibFactory extends DefaultHandler { private TagLibTagAttr att; private boolean insideAtt = false; + private TagLibTagAttrGroup attrGroup; // LDEV-5901 + private boolean insideAttrGroup = false; // LDEV-5901 + private String inside; private StringBuilder content = new StringBuilder(); private TagLibTagScript script; @@ -197,9 +200,11 @@ public void startElement(String uri, String name, String qName, Attributes attri inside = qName; this.attributes = SaxUtil.toMap(attributes); + if (qName.equals("tag")) startTag(); else if (qName.equals("attribute")) startAtt(); else if (qName.equals("script")) startScript(); + else if (qName.equals("group")) startAttrGroup(); // LDEV-5901 } @@ -217,12 +222,13 @@ public void endElement(String uri, String name, String qName) { /* * if(tag!=null && tag.getName().equalsIgnoreCase("input")) { * print.ln(tag.getName()+"-"+att.getName()+":"+inside+"-"+insideTag+"-"+insideAtt); - * + * * } */ if (qName.equals("tag")) endTag(); else if (qName.equals("attribute")) endAtt(); else if (qName.equals("script")) endScript(); + else if (qName.equals("group")) endAttrGroup(); // LDEV-5901 } @@ -282,6 +288,13 @@ else if (insideScript) { if (inside.equals("context")) script.setContext(value); } + // LDEV-5901: Handle attribute group elements + else if (insideAttrGroup) { + if (inside.equals("name")) attrGroup.setName(value); + else if (inside.equals("label")) attrGroup.setLabel(value); + else if (inside.equals("description")) attrGroup.setDescription(value); + else if (inside.equals("attributes")) attrGroup.setAttributes(value); + } // Tag Args else { // TODO TEI-class @@ -441,6 +454,22 @@ private void endAtt() { insideAtt = false; } + /** + * LDEV-5901: Called when element starts + */ + private void startAttrGroup() { + attrGroup = new TagLibTagAttrGroup(); + insideAttrGroup = true; + } + + /** + * LDEV-5901: Called when element ends + */ + private void endAttrGroup() { + tag.setAttributeGroup(attrGroup); + insideAttrGroup = false; + } + /** * Gibt die interne TagLib zurueck. * diff --git a/core/src/main/java/lucee/transformer/library/tag/TagLibTag.java b/core/src/main/java/lucee/transformer/library/tag/TagLibTag.java index 24c09eabdf..fc5ebebe7d 100755 --- a/core/src/main/java/lucee/transformer/library/tag/TagLibTag.java +++ b/core/src/main/java/lucee/transformer/library/tag/TagLibTag.java @@ -21,9 +21,11 @@ import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -105,6 +107,7 @@ public final class TagLibTag { private Map setters = new HashMap(); private TagLibTagAttr attrFirst; private TagLibTagAttr attrLast; + private List attributeGroups = new ArrayList(); // LDEV-5901 private ClassDefinition cdAttributeEvaluator; private boolean handleException; @@ -207,10 +210,28 @@ public Map getAttributes() { return attributes; } + /** + * LDEV-5901: Returns attribute groups for documentation purposes + * + * @return List of attribute groups + */ + public List getAttributeGroups() { + return attributeGroups; + } + + /** + * LDEV-5901: Adds an attribute group + * + * @param group The attribute group to add + */ + public void setAttributeGroup(TagLibTagAttrGroup group) { + attributeGroups.add(group); + } + /** * Gibt ein bestimmtes Attribut anhand seines Namens zurueck, falls dieses Attribut nicht existiert * wird null zurueckgegeben. - * + * * @param name Name des Attribut das zurueckgegeben werden soll. * @return Attribute das angfragt wurde oder null. */ diff --git a/core/src/main/java/lucee/transformer/library/tag/TagLibTagAttrGroup.java b/core/src/main/java/lucee/transformer/library/tag/TagLibTagAttrGroup.java new file mode 100644 index 0000000000..af313ef1a9 --- /dev/null +++ b/core/src/main/java/lucee/transformer/library/tag/TagLibTagAttrGroup.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2024, Lucee Association Switzerland + * + * 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.transformer.library.tag; + +/** + * LDEV-5901: Represents an attribute group for documentation purposes. + * Groups related attributes by action/mode for clearer documentation. + */ +public final class TagLibTagAttrGroup { + + private String name; + private String label; + private String description; + private String attributes; // comma-separated attribute names + + public TagLibTagAttrGroup() { + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setAttributes(String attributes) { + this.attributes = attributes; + } + + public String getAttributes() { + return attributes; + } +} diff --git a/core/src/main/java/lucee/transformer/util/PageSourceCode.java b/core/src/main/java/lucee/transformer/util/PageSourceCode.java index 4828f573df..f70a51f51a 100644 --- a/core/src/main/java/lucee/transformer/util/PageSourceCode.java +++ b/core/src/main/java/lucee/transformer/util/PageSourceCode.java @@ -83,4 +83,9 @@ public PageSource getPageSource() { public Charset getCharset() { return charset; } + + @Override + public SourceCode subCFMLString( int start, int count ) { + return new PageSourceCode( ps, String.valueOf( text, start, count ), charset, getWriteLog(), getSourceOffset() + start ); + } } \ No newline at end of file diff --git a/core/src/main/java/resource/component/org/lucee/cfml/HelperBase.cfc b/core/src/main/java/resource/component/org/lucee/cfml/HelperBase.cfc index 10a4128255..1a4cd48cc8 100644 --- a/core/src/main/java/resource/component/org/lucee/cfml/HelperBase.cfc +++ b/core/src/main/java/resource/component/org/lucee/cfml/HelperBase.cfc @@ -27,13 +27,17 @@ * Add a new param */ public HelperBase function addParam() { - // Only validate the param being added (arguments itself) + // Only validate the param being added (arguments itself) if (arguments.list ?: false) { - if (structKeyExists(arguments, "value") && trim(arguments.value) == '') { - var errorMsg = structKeyExists(arguments, "name") - ? "param [#arguments.name#] may not be empty" - : "param with list [#arguments.list#] Value may not be empty"; - throw(errorMsg, "expression"); + if (structKeyExists(arguments, "value")) { + var val = arguments.value; + var isEmpty = isSimpleValue(val) ? trim(val) == '' : (isArray(val) && arrayLen(val) == 0); + if (isEmpty) { + var errorMsg = structKeyExists(arguments, "name") + ? "param [#arguments.name#] may not be empty" + : "param with list [#arguments.list#] Value may not be empty"; + throw(errorMsg, "expression"); + } } } ArrayAppend(variables.params, arguments); diff --git a/core/src/main/java/resource/dtd/web-cfmtaglibrary_1_0.dtd b/core/src/main/java/resource/dtd/web-cfmtaglibrary_1_0.dtd index 0225f2fae2..0bf1b38960 100644 --- a/core/src/main/java/resource/dtd/web-cfmtaglibrary_1_0.dtd +++ b/core/src/main/java/resource/dtd/web-cfmtaglibrary_1_0.dtd @@ -61,7 +61,7 @@ the expression language of this Tag Library (required) - + + + + + + diff --git a/core/src/main/java/resource/fld/core-base.fld b/core/src/main/java/resource/fld/core-base.fld index 08d3a823b9..8894b157d0 100755 --- a/core/src/main/java/resource/fld/core-base.fld +++ b/core/src/main/java/resource/fld/core-base.fld @@ -2660,7 +2660,62 @@ The returned AST uses **neutral, language-agnostic node types** following ESTree struct - + + + + compress + lucee.runtime.functions.system.Compress + Compress given source to zip or gzip format. For additional formats (tar, tgz, bzip, etc.) install the compress extension. + + format + string + Yes + + Format to compress the files: + - gzip + - zip + + + + source + string + Yes + + Path (relative or absolute) to source file or directory to compress. + For **gzip** (single file format) must use path including the name of the file + + + + target + string + Yes + + Path (relative or absolute) including the name, where you want to save the compressed output-file + + + + includeBaseFolder + boolean + No + true + + Compress the directory or just the content of the directory + + + + mode + string + no + 777 + + Mode to store the files (only used by compress extension for tar formats) + + + + boolean + + + cos @@ -4904,8 +4959,44 @@ EntitySave(entity, [forceInsert]) + + + extract + lucee.runtime.functions.system.Extract + Extract the data of a compressed file. For additional formats (tar, tgz, bzip, etc.) install the compress extension. + + format + string + Yes + + Format of the compressed file: + - gzip + - zip + + + + source + string + Yes + + Path (relative or absolute) to the source file or a directory with compressed files to extract + + + + target + string + Yes + + Path (relative or absolute) to the directory, where you want to extract the data. + For **gzip** (single file format) must use path including the name of the file + + + + boolean + + - + FileAppend lucee.runtime.functions.file.FileAppend @@ -6527,6 +6618,12 @@ When used, the function automatically validates that the secret exists and throw No Name of the Secret Provider to read from, as configured in .CFConfig.json. If not defined, the function checks all configured providers in the order they are defined in .CFConfig.json until it finds the secret. + + resolve + boolean + No + if set to true the secret is resolved right away, if set to false, it is resolved when used. + any @@ -16472,6 +16569,23 @@ you can use tag cfadmin instead. we will add support for this function in a futu component Yes component to return info for it + + + struct + + + + + + luceeStructInfo + lucee.runtime.functions.struct.StructInfo + hidden + + + struct + struct + Yes + struct diff --git a/core/src/main/java/resource/tld/core-base.tld b/core/src/main/java/resource/tld/core-base.tld index 854c319bc1..c2ec6e45d7 100644 --- a/core/src/main/java/resource/tld/core-base.tld +++ b/core/src/main/java/resource/tld/core-base.tld @@ -188,6 +188,50 @@ the page. To accomplish this, cfcache creates temporary files that contain the static HTML returned from a page. You can use cfcache for simple URLs and URLs that contain URL parameters. fixed + + + cache + + `action="cache"` - Server and client-side template caching (alias `"optimal"`) + useCache,useQueryString,stripWhiteSpace,timeSpan,idleTime + + + get + + `action="get"` - Retrieve value from object cache + id,name,metadata,cacheName,throwOnError + + + put + + `action="put"` - Store value in object cache + id,value,timeSpan,idleTime,cacheName,throwOnError + + + content + + `action="content"` - Cache only the body of the tag + key,useCache,useQueryString,stripWhiteSpace,dependsOn,cacheName,timeSpan,idleTime + + + flush + + `action="flush"` - Clear cached pages + id,expireURL,directory,cacheDirectory,cacheName,throwOnError + + + clientCache + + `action="clientCache"` - Browser-side caching only + timeSpan,idleTime + + + serverCache + + `action="serverCache"` - Server-side caching only + directory,cacheDirectory,port,useQueryString,cacheName,timeSpan,idleTime + + string action @@ -195,73 +239,71 @@ cache false true - - - cache (default): server-side and client-side template caching. + + - cache (default): server-side and client-side template caching (alias "optimal"). - flush: refresh cached pages (template caching). - clientCache: browser-side caching only. To cache a personalized page, use this option. - serverCache: server-side caching only. Not recommended. - - optimal: same as "cache". - content: same as cache, but cache only the body of the tag, not the complete template (template caching). - put: adds a key value pair to object cache (see function cachePut for more details) - get: gets value matching given key from object cache (see function cacheGet for more details) - + string key false true - key to access cache - + key to access cache + any id false true - Id of the cached object - + Id of the cached object + boolean throwOnError false true - A Boolean value specifying whether t throw an error if the + A Boolean value specifying whether to throw an error if the flush action encounters an error. Otherwise the action does not generate an error if it fails. If this attribute is true you can handle the error in a cfcatch block, for example, if a specified id value is invalid - - + string name false true - name of return variable, valid with action="get" - + name of return variable, valid with action="get" + string metadata false true - Name of the struct variable - + Name of the struct variable + any value false true - For action="set", object which needs to be stored - + For action="set", object which needs to be stored + string cacheName false true - definition of the cache used by name, when not set the "default Object Cache" defined in Lucee Administrator is used instead. - + definition of the cache used by name, when not set the "default Object Cache" defined in Lucee Administrator is used instead. + string username @@ -374,7 +416,6 @@ A return value from the CreateTimeSpan function, for example, "#CreateTimeSpan(0 A list of additional variables which invalidate the cache when changed. unimplemented - @@ -825,6 +866,75 @@ A return value from the CreateTimeSpan function, for example, "#CreateTimeSpan(0 Lets you retrieve information about a data source, including details about the database, tables, queries, procedures, foreign keys, indexes, and version information about the database, driver, and JDBC. This tag supports only JDBC drivers, and does not support ODBC-based drivers, including the Microsoft Access driver. fixed + + + dbNames + + `type="dbNames"` - Get database name and type + pattern + + + tables + + `type="tables"` - Get information about all tables + pattern,Filter + + + columns + + `type="columns"` - Get column info with FK/PK relationships + table + + + columns_minimal + + `type="columns_minimal"` - Get column info without FK/PK (much faster) + table + + + version + + `type="version"` - Get database version info + + + + procedures + + `type="procedures"` - Get information about all procedures + pattern + + + procedure_columns + + `type="procedure_columns"` - Get column info for a procedure + procedure + + + foreignKeys + + `type="foreignKeys"` - Get foreign key information + table + + + index + + `type="index"` - Get index information + table + + + users + + `type="users"` - List database users + + + + terms + + `type="terms"` - Get vendor preferred terminology + + + + object datasource @@ -955,6 +1065,51 @@ Each Database implementation has it's own supported types Handles interactions with directories. fixed + + + list + + `action="list"` - Get directory contents + directory,name,type,filter,filterDelimiters,listInfo,recurse,sort + + + create + + `action="create"` - Create a new directory + directory,createPath,mode,nameConflict,storeAcl,storeLocation + + + delete + + `action="delete"` - Delete a directory + directory,recurse + + + forceDelete + + `action="forceDelete"` - Force delete a directory and all contents + directory + + + rename + + `action="rename"` - Rename a directory + directory,newDirectory,name,createPath,storeAcl,storeLocation + + + copy + + `action="copy"` - Copy directory to new location + directory,destination,filter,recurse,nameConflict,createPath,storeAcl,storeLocation + + + info + + `action="info"` - Retrieve directory metadata + directory,name + + + string action @@ -1501,6 +1656,74 @@ Example: Handles all interactions with files. The attributes you use with cffile depend on the value of the action attribute. For example, if the action = "write", use the attributes associated with writing a text file. fixed + + + read + + `action="read"` - Read text content from a file + file,variable,charset,cachedWithin + + + readBinary + + `action="readBinary"` - Read binary content from a file + file,variable,cachedWithin + + + write + + `action="write"` - Write text content to a file + file,output,charset,addNewLine,fixNewLine,createPath,mode,attributes,storeAcl + + + append + + `action="append"` - Append content to existing file + file,output,charset,addNewLine,fixNewLine,createPath,mode,attributes,storeAcl + + + upload + + `action="upload"` - Handle file uploads from forms + destination,fileField,nameConflict,accept,strict,result,allowedExtensions,blockedExtensions,attributes,storeAcl + + + uploadAll + + `action="uploadAll"` - Handle multiple file uploads from forms + destination,nameConflict,accept,strict,result,allowedExtensions,blockedExtensions,attributes,storeAcl + + + copy + + `action="copy"` - Copy file to new location + source,destination,mode,attributes,storeAcl + + + move + + `action="move"` (alias `"rename"`) - Move or rename a file + source,destination,mode,attributes,storeAcl + + + delete + + `action="delete"` - Delete a file + file,storeAcl + + + info + + `action="info"` - Retrieve file metadata + file,variable + + + touch + + `action="touch"` - Create empty file or update timestamp + file,createPath,storeAcl + + object storeAcl @@ -1605,7 +1828,6 @@ if set to false, it supports file extension/mimetypes in the accept attribute if set to false (default), expects all parent directories to exist, true will generate necessary directories - string file @@ -1633,7 +1855,7 @@ if set to false, it supports file extension/mimetypes in the accept attribute false true 5.0.0.0 - + possible values are: String "request": If original content was created within the current request, cached content data is used. a timeSpan (created with function CreateTimeSpan): If original content date falls within the time span, cached content data is used. @@ -1752,6 +1974,110 @@ To use cached data, the tag must be called with the exact same arguments. Only u Lets users implement File Transfer Protocol (FTP) operations. fixed + + + open + + `action="open"` - Open persistent FTP connection + connection,server,username,password,port,timeout,retryCount,passive,transferMode,stopOnError,secure,key,passphrase,fingerprint,proxyServer,proxyPort,proxyUser,proxyPassword + + + close + + `action="close"` - Close FTP connection + connection + + + getFile + + `action="getFile"` - Download file from FTP server + connection,remoteFile,localFile,failIfExists,transferMode,ASCIIExtensionList + + + putFile + + `action="putFile"` - Upload file to FTP server + connection,remoteFile,localFile,transferMode,ASCIIExtensionList + + + listDir + + `action="listDir"` - List contents of FTP directory + connection,directory,name + + + createDir + + `action="createDir"` - Create directory on FTP server + connection,directory + + + removeDir + + `action="removeDir"` - Remove directory from FTP server + connection,directory + + + changeDir + + `action="changeDir"` - Change current directory on FTP server + connection,directory + + + getCurrentDir + + `action="getCurrentDir"` - Get current working directory path + connection,result + + + getCurrentUrl + + `action="getCurrentUrl"` - Get current working directory as URL + connection,result + + + exists + + `action="exists"` - Check if file or directory exists + connection,item,result + + + existsFile + + `action="existsFile"` - Check if file exists + connection,remoteFile,result + + + existsDir + + `action="existsDir"` - Check if directory exists + connection,directory,result + + + rename + + `action="rename"` - Rename file or directory on FTP server + connection,existing,new + + + remove + + `action="remove"` - Remove file from FTP server + connection,item + + + copy + + `action="copy"` - Copy file on FTP server + connection,existing,new + + + quote + + `action="quote"` - Send raw FTP command to server + connection + + string action @@ -2169,6 +2495,56 @@ To use cached data, the tag must be called with the exact same arguments. Only u GET operations and create a query object from a text file. POST operations lets you upload MIME file types to a server, or post cookie, form field, URL, file, or CGI variables directly to a specified server. fixed + + + get + + `method="get"` - Retrieve content from a URL + getAsBinary,resolveURL,cachedWithin,name,columns,path,file,delimiter,textQualifier,firstRowAsHeaders + + + post + + `method="post"` - Send data to a server + charset,multipart,multiPartType + + + put + + `method="put"` - Upload/replace resource on server + charset + + + delete + + `method="delete"` - Remove resource from server + + + + head + + `method="head"` - Retrieve headers only, no body content + + + + options + + `method="options"` - Get allowed methods for resource + + + + trace + + `method="trace"` - Diagnostic method for request/response chain + + + + patch + + `method="patch"` - Partially update existing resource + charset + + string url @@ -2206,7 +2582,7 @@ To use cached data, the tag must be called with the exact same arguments. Only u result false true - return variable name, default "cfhhtp" + return variable name, default "cfhttp" boolean @@ -3063,6 +3439,39 @@ To use cached data, the function must be called with the exact same arguments. Provides an interface to LDAP Lightweight Directory Access Protocol directory servers like the Netscape Directory Server. fixed + + + query + + Search and retrieve LDAP entries + name,maxRows,start,scope,attributes,filter,filterFile,sort,sortControl,startRow,separator,delimiter,returnAsBinary + + + add + + Add new entry to LDAP directory + dn,attributes,separator,delimiter + + + modify + + Update existing LDAP entry + dn,attributes,modifyType,separator,delimiter + + + modifyDn + + Change distinguished name of entry + dn,attributes + + + delete + + Remove entry from LDAP directory + dn + + + string returnAsBinary @@ -3543,6 +3952,56 @@ To use cached data, the function must be called with the exact same arguments. Looping is a very powerful programming technique that lets you repeat a set of instructions or display output repeatedly until one or more conditions are met. cfloop supports five types of loops. fixed + + + index + + Index loop - Loop with a counter from/to/step + index,item,from,to,step + + + condition + + Conditional loop - Loop while a condition is true + condition + + + list + + List loop - Iterate through delimited strings + list,index,item,delimiters + + + array + + Array loop - Iterate through array elements + array,index,item + + + collection + + Collection/Struct loop - Iterate through struct keys and values (aliases: `key` for `index`, `value` for `item`) + collection,struct,index,item + + + query + + Query loop - Iterate through query recordset + query,startRow,endRow,maxRows,group + + + file + + File loop - Read file line by line (aliases: `from`/`to` for `startLine`/`endLine`) + file,index,item,characters,startLine,from,endLine,to,charset + + + times + + Times loop - Loop a specific number of times + times + + string index @@ -4196,6 +4655,27 @@ Lucee uses the number of characters in the file. Passes SQL statements to a data source. Not limited to queries. fixed + + + database + + Execute SQL against a datasource + dataSource,username,password,blockFactor,timeout,cachedAfter,cachedWithin,timezone,sql,async,listener,cacheId,cacheName,tags + + + qoq + + Query existing query objects + dbType,cachedWithin,sql,psq + + + hql + + Execute HQL queries against ORM entities + dbType,ormOptions,unique + + + string name @@ -4853,6 +5333,45 @@ Params: [ is performed in order to populate the data on the page. fixed + + + update + + `action="update"` - Create or update a scheduled task + task,url,startDate,startTime,interval,endDate,endTime,publish,file,path,requestTimeOut,username,password,proxyServer,proxyPort,proxyUser,proxyPassword,userAgent,resolveURL,port,paused,hidden,readonly,autoDelete,unique,serverPassword + + + run + + `action="run"` - Execute a scheduled task immediately + task + + + delete + + `action="delete"` - Remove a scheduled task + task + + + list + + `action="list"` - Get all scheduled tasks + result + + + pause + + `action="pause"` - Pause execution of a scheduled task + task + + + resume + + `action="resume"` - Resume execution of a paused task + task + + + string action @@ -5445,6 +5964,38 @@ To use cached data, the current query must use the same SQL statement, data sour Each thread gets its own isolated variable scope (`thread`) that persists across the thread's lifetime and can be accessed from other parts of your application. mixed + + + run + + `action="run"` - Create and execute a new thread with custom processing (supports dynamic attributes) + name,type,priority,retryInterval,separateScopes + + + join + + `action="join"` - Synchronize with other threads, waiting for their completion + name,timeout,throwOnError + + + sleep + + `action="sleep"` - Pause the current thread for a specified duration + duration + + + terminate + + `action="terminate"` - Forcibly stop a thread immediately + name + + + interrupt + + `action="interrupt"` - Request cooperative termination of a thread + name + + string action @@ -5564,14 +6115,30 @@ To use cached data, the current query must use the same SQL statement, data sour 6.2.1.24 Only applicable with `action="join"`. Determines whether exceptions thrown in joined threads should propagate to the joining thread. - + * When `true`: If any of the joined threads have encountered errors, the first error found will be thrown as an exception in the current thread. This allows for easier error detection by propagating errors up the call stack. - + * When `false` (default): Errors in joined threads remain isolated in their respective thread scopes and won't affect the current thread's execution. You must explicitly check the thread status to identify errors. - + This attribute is useful for implementing fail-fast behavior in situations where thread errors should immediately stop dependent operations. + + boolean + separateScopes + false + true + 6.1.0.151 + + Only applicable with `action="run"`. Controls whether the thread gets its own isolated copy of the caller's scopes or shares them directly. Defaults to `true`. + + * When `true` (default): The thread receives a deep copy of the caller's scopes at thread creation time. Changes made within the thread do not affect the parent context, and vice versa. This provides isolation and prevents concurrency issues. + + * When `false`: The thread directly accesses the caller's scopes. Changes made in the thread are immediately visible to the parent context. Use with caution as this can lead to race conditions and thread safety issues. + + This attribute is useful when you need threads to share state, but requires careful synchronization to avoid data corruption. + + @@ -7737,6 +8304,86 @@ Valid only for cfinput type="text". for session and application variables. Session and application variables are stored in memory. mixed + + + session + + Configure session scope behavior and storage + sessionManagement,sessionType,sessionStorage,sessionTimeout,sessionCluster,sessionCookie + + + client + + Configure client scope behavior and storage + clientManagement,clientStorage,clientTimeout,clientCluster,setClientCookies,authCookie + + + cache + + Configure application caching behavior + caches,cacheObject,cacheFunction,cacheQuery,cacheTemplate,cacheResource,cacheInclude,cacheHTTP,cacheFile,cacheWebservice + + + database + + Configure datasources for the application + datasource,defaultDatasource,datasources + + + debugging + + Control debug output and logging + showdebug,showdoc,showmetric,showtest,debuggingDatabase,debuggingException,debuggingDump,debuggingTracing,debuggingTimer,debuggingImplicitAccess,debuggingQueryUsage,debuggingThread,debuggingTemplate + + + paths + + Configure custom paths for components, tags, and functions + mappings,functionPaths,customTagPaths,componentPaths + + + orm + + Configure Hibernate ORM settings + ormEnabled,ormSettings + + + mail + + Configure SMTP mail servers and listeners + mailservers,mailListener + + + remote + + Configure S3, FTP and proxy settings + s3,ftp,proxy + + + security + + Configure security and cookie policies + setDomainCookies,secureJson,secureJsonPrefix,scriptProtect,blockedExtForFileUpload + + + timeouts + + Configure application and request timeouts + applicationTimeout,requestTimeout + + + behavior + + General application behavior and configuration + bufferOutput,cgiReadonly,loginStorage,localMode,locale,timeZone,webCharset,resourceCharset,scopeCascading,searchImplicitScopes,searchResults,enableNULLSupport,nullSupport,preciseMath,typeChecking,compression,wstype,onMissingTemplate,SerializationSettings,tag,logs,cachedAfter,javaSettings,xmlFeatures,regex,sameFormFieldsAsArray,sameURLFieldsAsArray,variableUsage,suppressRemoteComponentContent + + + query + + Configure query behavior and listeners + queryListener,psq,triggerDataMember,InvokeImplicitAccessor + + string name @@ -9261,6 +9908,33 @@ Each type-value pair must start with a hyphen. Reads, writes, and deletes keys and values in the system registry. The cfregistry tag is supported on all platforms, including Linux, Solaris, and HP-UX. fixed + + + getAll + + Retrieve all keys and values from a branch + type,name,sort + + + get + + Retrieve a single registry value + entry,type,variable + + + set + + Create or update a registry entry + entry,type,value + + + delete + + Remove a registry entry + entry + + + string action diff --git a/core/src/main/java/resource/tld/dtd/web-cfmtaglibrary_1_0.dtd b/core/src/main/java/resource/tld/dtd/web-cfmtaglibrary_1_0.dtd index 0225f2fae2..0bf1b38960 100644 --- a/core/src/main/java/resource/tld/dtd/web-cfmtaglibrary_1_0.dtd +++ b/core/src/main/java/resource/tld/dtd/web-cfmtaglibrary_1_0.dtd @@ -61,7 +61,7 @@ the expression language of this Tag Library (required) - + + + + + + diff --git a/loader/build.xml b/loader/build.xml index 8036c2bd2f..e607fbe696 100644 --- a/loader/build.xml +++ b/loader/build.xml @@ -2,7 +2,7 @@ - + diff --git a/loader/pom.xml b/loader/pom.xml index 288d7fe64c..18f4161d5b 100644 --- a/loader/pom.xml +++ b/loader/pom.xml @@ -3,7 +3,7 @@ org.lucee lucee - 7.1.0.7-ALPHA + 7.1.0.9-ALPHA jar Lucee Loader Build diff --git a/loader/src/main/java/lucee/loader/engine/BundleProvider.java b/loader/src/main/java/lucee/loader/engine/BundleProvider.java index 0985267824..f9e836b5ca 100644 --- a/loader/src/main/java/lucee/loader/engine/BundleProvider.java +++ b/loader/src/main/java/lucee/loader/engine/BundleProvider.java @@ -807,7 +807,7 @@ private static int compare(final Version left, final Version right) { index = q.indexOf('-'); String qra = index == -1 ? "" : q.substring(index + 1).trim(); String qrn = index == -1 ? q : q.substring(0, index); - int qr = Util.isEmpty(qln) ? Integer.MIN_VALUE : Integer.parseInt(qrn); + int qr = Util.isEmpty(qrn) ? Integer.MIN_VALUE : Integer.parseInt(qrn); if (ql > qr) return 5; if (ql < qr) return -5; diff --git a/loader/src/main/java/lucee/loader/util/Util.java b/loader/src/main/java/lucee/loader/util/Util.java index f3c6a434ec..4fe7d74d2d 100755 --- a/loader/src/main/java/lucee/loader/util/Util.java +++ b/loader/src/main/java/lucee/loader/util/Util.java @@ -507,7 +507,7 @@ public static boolean isNewerThan(final Version left, final Version right) { index = q.indexOf('-'); String qra = index == -1 ? "" : q.substring(index + 1).trim(); String qrn = index == -1 ? q : q.substring(0, index); - int qr = isEmpty(qln) ? Integer.MIN_VALUE : Integer.parseInt(qrn); + int qr = isEmpty(qrn) ? Integer.MIN_VALUE : Integer.parseInt(qrn); if (ql > qr) return true; if (ql < qr) return false; diff --git a/test/_setupTestServices.cfc b/test/_setupTestServices.cfc index 6c9fb21954..a0180ecb35 100644 --- a/test/_setupTestServices.cfc +++ b/test/_setupTestServices.cfc @@ -626,7 +626,7 @@ component { class: 'com.mysql.cj.jdbc.Driver' , bundleName: 'com.mysql.cj' , bundleVersion: server.getDefaultBundleVersion( 'com.mysql.cj', '9.3.0' ) - , connectionString: 'jdbc:mysql://#mySQL.server#:#mySQL.port#/#mySQL.database#?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=true&useSSL=false' & arguments.connectionString + , connectionString: 'jdbc:mysql://#mySQL.server#:#mySQL.port#/#mySQL.database#?useUnicode=true&characterEncoding=UTF-8&useLegacyDatetimeCode=true&useSSL=false&allowPublicKeyRetrieval=true' & arguments.connectionString , username: mySQL.username , password: mySQL.password }.append( arguments.options ); diff --git a/test/functions/Compress.cfc b/test/functions/Compress.cfc index 83ac26f65d..635a48c96a 100644 --- a/test/functions/Compress.cfc +++ b/test/functions/Compress.cfc @@ -1,48 +1,78 @@ -component extends="org.lucee.cfml.test.LuceeTestCase"{ -function test() { - loop list="tgz:m,zip:m,tbz:m,tar:m,gzip:s,bzip:s" item="local.format2type" { - var format=listFirst(format2type,":"); - var type=listLast(format2type,":"); +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults, testBox ) { + describe( "Compress/Extract functions", function() { + + // core formats (always available) + it( "can compress and extract zip", function() { + doCompressExtract( "zip", "m" ); + }); + + it( "can compress and extract gzip", function() { + doCompressExtract( "gzip", "s" ); + }); + + // extension formats (require compress extension) + it( title="can compress and extract tgz", skip=!hasCompressExtension(), body=function() { + doCompressExtract( "tgz", "m" ); + }); + + it( title="can compress and extract tar", skip=!hasCompressExtension(), body=function() { + doCompressExtract( "tar", "m" ); + }); + + it( title="can compress and extract tbz", skip=!hasCompressExtension(), body=function() { + doCompressExtract( "tbz", "m" ); + }); + + it( title="can compress and extract bzip", skip=!hasCompressExtension(), body=function() { + doCompressExtract( "bzip", "s" ); + }); + + }); + } + + private boolean function hasCompressExtension() { + return extensionExists( "8D7FB0DF-08BB-1589-FE3975678F07DB17" ); + } + + private void function doCompressExtract( required string format, required string type ) { + var tmp = getTempDirectory() & "compress-test-" & createUUID() & "/"; + var srcDir = tmp & "src"; + var trgDir = tmp & "trg"; + var trg2Dir = tmp & "trg2"; + + if ( directoryExists( tmp ) ) directoryDelete( tmp, true ); + directoryCreate( tmp ); try { - var curr=getDirectoryFromPath(getCurrentTemplatePath()); - // source - var srcDir=curr&"srctmp"&format; - var src=srcDir&"/susi.txt"; - if(directoryExists(srcDir)) directoryDelete(srcDir,true); - directoryCreate(srcDir); - fileWrite(src,"Susi Sorglos foehnte Ihr Haar..."); + var src = srcDir & "/susi.txt"; + directoryCreate( srcDir ); + fileWrite( src, "Susi Sorglos foehnte Ihr Haar..." ); // target - var trgDir=curr&"trgtmp"&format; - var trg=trgDir&"/susi."&format; - if(directoryExists(trgDir)) directoryDelete(trgDir,true); - directoryCreate(trgDir); - - compress(format:format, source:type=="m"?srcDir:src, target:trg , includeBaseFolder:true); - - //dump(label:"does the compressed file for #format# exists?",var:yesNoFormat(fileExists(trg))); - assertTrue(fileExists(trg)); - - // target 2 - var trg2Dir=curr&"trg2tmp"&format; - var trg2=trg2Dir&"/susi."&format&".txt"; - if(directoryExists(trg2Dir)) directoryDelete(trg2Dir,true); - directoryCreate(trg2Dir); - - extract(format,trg,type=="m"?trg2Dir:trg2); - if(type=="m") assertTrue(yesNoFormat(fileExists(trg2Dir&"/srctmp"&format&"/susi.txt"))); - //dump(label:"do we have the files from extraction of #format#?",var:yesNoFormat(fileExists(trg2Dir&"/srctmp"&format&"/susi.txt"))); - else assertTrue(fileExists(trg2)); - //dump(label:"do we have the file from extraction of #format#?",var:yesNoFormat(fileExists(trg2))); + var trg = trgDir & "/susi." & format; + directoryCreate( trgDir ); + compress( format: format, source: type == "m" ? srcDir : src, target: trg, includeBaseFolder: true ); + assertTrue( fileExists( trg ), "compressed file should exist for format #format#" ); + + // target 2 - extract + var trg2 = trg2Dir & "/susi." & format & ".txt"; + directoryCreate( trg2Dir ); + + extract( format, trg, type == "m" ? trg2Dir : trg2 ); + if ( type == "m" ) { + assertTrue( fileExists( trg2Dir & "/src/susi.txt" ), "extracted file should exist for format #format#" ); + } + else { + assertTrue( fileExists( trg2 ), "extracted file should exist for format #format#" ); + } } finally { - if(directoryExists(srcDir)) directoryDelete(srcDir,true); - if(directoryExists(trgDir)) directoryDelete(trgDir,true); - if(directoryExists(trg2Dir)) directoryDelete(trg2Dir,true); + if ( directoryExists( tmp ) ) directoryDelete( tmp, true ); } } -} + } diff --git a/test/functions/GetMetaData.cfc b/test/functions/GetMetaData.cfc index 226ceafbfe..b64446dc6c 100644 --- a/test/functions/GetMetaData.cfc +++ b/test/functions/GetMetaData.cfc @@ -4,75 +4,178 @@ component extends="org.lucee.cfml.test.LuceeTestCase" { describe( title = "Test suite for getMetaData", body = function() { - it( title = 'Checking regular array', body = function( currentSpec ) { - var data=[1,2,3]; - var meta=data.getmetadata(); - - assertEquals("class,datatype,dimensions,name,type", listSort( meta.keyList(), "text") ); - assertEquals("any",meta.datatype); - assertEquals(1,meta.dimensions); - assertEquals("unsynchronized",meta.type); + describe( title = "Array metadata", body = function() { + + it( title = 'Checking regular array', body = function( currentSpec ) { + var data=[1,2,3]; + var meta=data.getmetadata(); + + assertEquals("class,datatype,dimensions,name,type", listSort( meta.keyList(), "text") ); + assertEquals("any",meta.datatype); + assertEquals(1,meta.dimensions); + assertEquals("unsynchronized",meta.type); + }); + + it( title = 'Checking 2 dim array', body = function( currentSpec ) { + var data=arrayNew(2); + var meta=data.getmetadata(); + + assertEquals("class,datatype,dimensions,name,type", listSort( meta.keyList(), "text") ); + assertEquals("any",meta.datatype); + assertEquals(2,meta.dimensions); + assertEquals("unsynchronized",meta.type); + }); + + it( title = 'Checking typed array', body = function( currentSpec ) { + var data=arrayNew(1,"string"); + var meta=data.getmetadata(); + + assertEquals("class,datatype,dimensions,name,type", listSort( meta.keyList(), "text" ) ); + assertEquals("string",meta.datatype); + assertEquals(1,meta.dimensions); + assertEquals("unsynchronized",meta.type); + }); + }); - it( title = 'Checking 2 dim array', body = function( currentSpec ) { - var data=arrayNew(2); - var meta=data.getmetadata(); + describe( title = "Struct metadata", body = function() { - assertEquals("class,datatype,dimensions,name,type", listSort( meta.keyList(), "text") ); - assertEquals("any",meta.datatype); - assertEquals(2,meta.dimensions); - assertEquals("unsynchronized",meta.type); - }); + it( title = 'Checking regular struct', body = function( currentSpec ) { + var data={a:1}; + var meta=data.getmetadata(); - it( title = 'Checking typed array', body = function( currentSpec ) { - var data=arrayNew(1,"string"); - var meta=data.getmetadata(); + assertEquals("class,name,ordered,type", listSort( meta.keyList(), "text" ) ); + assertEquals("unordered",meta.ordered); + //assertEquals("regular",meta.type); + }); - assertEquals("class,datatype,dimensions,name,type", listSort( meta.keyList(), "text" ) ); - assertEquals("string",meta.datatype); - assertEquals(1,meta.dimensions); - assertEquals("unsynchronized",meta.type); - }); + it( title = 'Checking ordered struct', body = function( currentSpec ) { + var data=[a:1]; + var meta=data.getmetadata(); + + assertEquals("class,name,ordered,type", listSort( meta.keyList(), "text" ) ); + assertEquals("ordered",meta.ordered); + assertEquals("ordered",meta.type); + }); + it( title = 'Checking soft struct', body = function( currentSpec ) { + var data=structNew("soft"); + var meta=data.getmetadata(); - it( title = 'Checking regular struct', body = function( currentSpec ) { - var data={a:1}; - var meta=data.getmetadata(); + assertEquals("class,name,ordered,type", listSort( meta.keyList(), "text" ) ); + assertEquals("unordered",meta.ordered); + assertEquals("soft",meta.type); + }); - assertEquals("class,name,ordered,type", listSort( meta.keyList(), "text" ) ); - assertEquals("unordered",meta.ordered); - //assertEquals("regular",meta.type); }); - it( title = 'Checking ordered struct', body = function( currentSpec ) { - var data=[a:1]; - var meta=data.getmetadata(); + describe( title = "UDF metadata", body = function() { + + it( title = "Checking UDFs", body = () => { + var metaA = getMetadata( exampleFunctionWithDescriptionInAnnotation ); + expect( metaA ).toBeStruct(); + expect( metaA ).toHaveKey( "description" ); + expect( metaA.description ).toBe( "Description shows up." ); + + var metaB = getMetadata( exampleFunctionWithDescriptionInDocblock ); + expect( metaB ).toBeStruct(); + expect( metaB ).toHaveKey( "description" ); + expect( metaB.description ).toBe( "Description does not show up." ); + }, labels = [ "metadata" ], skip = true ); - assertEquals("class,name,ordered,type", listSort( meta.keyList(), "text" ) ); - assertEquals("ordered",meta.ordered); - assertEquals("ordered",meta.type); }); - it( title = 'Checking soft struct', body = function( currentSpec ) { - var data=structNew("soft"); - var meta=data.getmetadata(); + describe( title = "Regular UDF metadata", body = function() { + + it( title = "Checking regular UDF owner field", body = () => { + var obj = new getMetaData.GetMetaDataComponent(); + + // Test regular getter + var getterMeta = getMetadata( obj.getMessage ); + expect( getterMeta ).toBeStruct(); + expect( getterMeta ).toHaveKey( "owner" ); + expect( getterMeta.owner ).toInclude( "GetMetaDataComponent.cfc" ); + + // Test regular setter + var setterMeta = getMetadata( obj.setMessage ); + expect( setterMeta ).toBeStruct(); + expect( setterMeta ).toHaveKey( "owner" ); + expect( setterMeta.owner ).toInclude( "GetMetaDataComponent.cfc" ); + }, labels = [ "metadata" ] ); + + it( title = "Checking regular UDF owner field with mixin", body = () => { + // Use two DIFFERENT components to prove owner tracking works correctly + var comp1 = new getMetaData.GetMetaDataComponent(); + var comp2 = new getMetaData.GetMetaDataComponent2(); + + // Inject comp1's getter into comp2 (mixin pattern) + comp2.injectedFromComp1 = comp1.getMessage; + + // Owner should point to Component (comp1), NOT Component2 (comp2) + var meta = getMetadata( comp2.injectedFromComp1 ); + expect( meta ).toBeStruct(); + expect( meta ).toHaveKey( "owner" ); + expect( meta.owner ).toInclude( "GetMetaDataComponent.cfc" ); + expect( meta.owner ).notToInclude( "GetMetaDataComponent2.cfc" ); + }, labels = [ "metadata", "mixin" ] ); + + it( title = "Checking regular UDF owner field with inheritance", body = () => { + var obj = new getMetaData.GetMetaDataComponentChild(); + + // Test regular getter from parent + var getterMeta = getMetadata( obj.getMessage ); + expect( getterMeta ).toBeStruct(); + expect( getterMeta ).toHaveKey( "owner" ); + expect( getterMeta.owner ).toInclude( "GetMetaDataComponent.cfc" ); + }, labels = [ "metadata", "inheritance" ] ); - assertEquals("class,name,ordered,type", listSort( meta.keyList(), "text" ) ); - assertEquals("unordered",meta.ordered); - assertEquals("soft",meta.type); }); - it( title = "Checking UDFs", body = () => { - var metaA = getMetadata( exampleFunctionWithDescriptionInAnnotation ); - expect( metaA ).toBeStruct(); - expect( metaA ).toHaveKey( "description" ); - expect( metaA.description ).toBe( "Description shows up." ); - - var metaB = getMetadata( exampleFunctionWithDescriptionInDocblock ); - expect( metaB ).toBeStruct(); - expect( metaB ).toHaveKey( "description" ); - expect( metaB.description ).toBe( "Description does not show up." ); - }, labels = [ "metadata" ], skip = true ); + describe( title = "Accessor UDF metadata", body = function() { + + it( title = "Checking accessor UDF owner field", body = () => { + var obj = new getMetaData.GetMetaDataAccessorComponent(); + + // Test auto-generated getter + var getterMeta = getMetadata( obj.getMessage ); + expect( getterMeta ).toBeStruct(); + expect( getterMeta ).toHaveKey( "owner" ); + expect( getterMeta.owner ).toInclude( "GetMetaDataAccessorComponent.cfc" ); + + // Test auto-generated setter + var setterMeta = getMetadata( obj.setMessage ); + expect( setterMeta ).toBeStruct(); + expect( setterMeta ).toHaveKey( "owner" ); + expect( setterMeta.owner ).toInclude( "GetMetaDataAccessorComponent.cfc" ); + }, labels = [ "metadata", "accessor" ] ); + + it( title = "Checking accessor UDF owner field with mixin", body = () => { + // Use two DIFFERENT components to prove owner tracking works correctly + var comp1 = new getMetaData.GetMetaDataAccessorComponent(); + var comp2 = new getMetaData.GetMetaDataAccessorComponent2(); + + // Inject comp1's getter into comp2 (mixin pattern) + comp2.injectedFromComp1 = comp1.getMessage; + + // Owner should point to Component (comp1), NOT Component2 (comp2) + var meta = getMetadata( comp2.injectedFromComp1 ); + expect( meta ).toBeStruct(); + expect( meta ).toHaveKey( "owner" ); + expect( meta.owner ).toInclude( "GetMetaDataAccessorComponent.cfc" ); + expect( meta.owner ).notToInclude( "GetMetaDataAccessorComponent2.cfc" ); + }, labels = [ "metadata", "accessor", "mixin" ] ); + + it( title = "Checking accessor UDF owner field with inheritance", body = () => { + var obj = new getMetaData.GetMetaDataAccessorComponentChild(); + + // Test auto-generated getter from parent + var getterMeta = getMetadata( obj.getMessage ); + expect( getterMeta ).toBeStruct(); + expect( getterMeta ).toHaveKey( "owner" ); + expect( getterMeta.owner ).toInclude( "GetMetaDataAccessorComponent.cfc" ); + }, labels = [ "metadata", "accessor", "inheritance" ] ); + + }); }); diff --git a/test/functions/ListGetDuplicates.cfc b/test/functions/ListGetDuplicates.cfc index 3eafeea7ff..289a0616f7 100644 --- a/test/functions/ListGetDuplicates.cfc +++ b/test/functions/ListGetDuplicates.cfc @@ -1,4 +1,4 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" { +component extends="org.lucee.cfml.test.LuceeTestCase" labels="listGetDuplicates" { function run( testResults , testBox ) { describe( "Test suite for listGetDuplicates", function() { it(title="checking listGetDuplicates function, having simple list with duplicate values", body = function( currentSpec ) { @@ -61,17 +61,91 @@ component extends="org.lucee.cfml.test.LuceeTestCase" { expect(result).toBe(',a', list); }); - xit(title="checking listGetDuplicates function, includeEmptyFields=true", body = function( currentSpec ) { + it(title="checking listGetDuplicates function, includeEmptyFields=true", body = function( currentSpec ) { var list = 'a,b,a,,d,,a,'; var result = listGetDuplicates(list=list, includeEmptyFields=true); expect(result).toBe('a,', list); }); - xit(title="checking listGetDuplicates function, includeEmptyFields=false", body = function( currentSpec ) { + it(title="checking listGetDuplicates function, includeEmptyFields=false", body = function( currentSpec ) { var list = 'a,b,,c,d,,a,'; var result = listGetDuplicates(list=list, includeEmptyFields=false); expect(result).toBe('a', list); }); }); + + describe( "Test suite for string.listGetDuplicates() member function", function() { + it(title="checking string.listGetDuplicates() member function, having simple list with duplicate values", body = function( currentSpec ) { + var list = '1,7,,7,10,6,7,8'; + var result = list.listGetDuplicates(); + expect(result).toBe('7', list); + }); + + it(title="checking string.listGetDuplicates() member function, having duplicate value at last", body = function( currentSpec ) { + var list = '1,7,77,10,6,7'; + var result = list.listGetDuplicates(); + expect(result).toBe('7', list); + }); + + it(title="checking string.listGetDuplicates() member function, having duplicate value at last", body = function( currentSpec ) { + var list = '1,7,7,,10,6,7'; + var result = list.listGetDuplicates(); + expect(result).toBe('7', list); + }); + + it(title="checking string.listGetDuplicates() member function, having empty value at last", body = function( currentSpec ) { + var list = '1,7,7,10,6, '; + var result = list.listGetDuplicates(); + expect(result).toBe('7', list); + }); + + it(title="checking string.listGetDuplicates() member function, having two duplicates, alt delim", body = function( currentSpec ) { + var list = '1+7+1+7'; + var result = list.listGetDuplicates("+"); + expect(result).toBe('1+7', list); + }); + }); + + describe( "Test suite for string.listGetDuplicates() member function - multipleDelimiters", function() { + it(title="checking string.listGetDuplicates() member function", body = function( currentSpec ) { + var list = 'a,!b,!c,!d,!a'; + var result = list.listGetDuplicates(delimiter=",!"); + expect(result).toBe('a', list); + }); + }); + + describe( "Test suite for string.listGetDuplicates() member function - ignore case", function() { + it(title="checking string.listGetDuplicates() member function, ignoreCase=true", body = function( currentSpec ) { + var list = 'a,b,c,d,A'; + var result = list.listGetDuplicates(ignoreCase=true); + expect(result).toBe('a', list); + }); + + it(title="checking string.listGetDuplicates() member function, ignoreCase=false", body = function( currentSpec ) { + var list = 'a,b,c,d,A'; + var result = list.listGetDuplicates(ignoreCase=false); + expect(result).toBe('', list); + }); + }); + + describe( "Test suite for string.listGetDuplicates() member function - includeEmptyFields", function() { + it(title="checking string.listGetDuplicates() member function, includeEmptyFields=true, empty duplicate first", body = function( currentSpec ) { + var list = 'a,b,c,,d,,a,'; + var result = list.listGetDuplicates(includeEmptyFields=true); + expect(result).toBe(',a', list); + }); + + it(title="checking string.listGetDuplicates() member function, includeEmptyFields=true", body = function( currentSpec ) { + var list = 'a,b,a,,d,,a,'; + var result = list.listGetDuplicates(includeEmptyFields=true); + expect(result).toBe('a,', list); + }); + + it(title="checking string.listGetDuplicates() member function, includeEmptyFields=false", body = function( currentSpec ) { + var list = 'a,b,,c,d,,a,'; + var result = list.listGetDuplicates(includeEmptyFields=false); + expect(result).toBe('a', list); + }); + }); } } diff --git a/test/functions/getMetaData/GetMetaDataAccessorComponent.cfc b/test/functions/getMetaData/GetMetaDataAccessorComponent.cfc new file mode 100644 index 0000000000..a72118f157 --- /dev/null +++ b/test/functions/getMetaData/GetMetaDataAccessorComponent.cfc @@ -0,0 +1,4 @@ +component { + property name="message" type="string"; + property name="count" type="numeric"; +} diff --git a/test/functions/getMetaData/GetMetaDataAccessorComponent2.cfc b/test/functions/getMetaData/GetMetaDataAccessorComponent2.cfc new file mode 100644 index 0000000000..a035e94f9c --- /dev/null +++ b/test/functions/getMetaData/GetMetaDataAccessorComponent2.cfc @@ -0,0 +1,4 @@ +component { + property name="otherMessage" type="string"; + property name="otherCount" type="numeric"; +} diff --git a/test/functions/getMetaData/GetMetaDataAccessorComponentChild.cfc b/test/functions/getMetaData/GetMetaDataAccessorComponentChild.cfc new file mode 100644 index 0000000000..e80b7939ec --- /dev/null +++ b/test/functions/getMetaData/GetMetaDataAccessorComponentChild.cfc @@ -0,0 +1,3 @@ +component extends="GetMetaDataAccessorComponent" { + +} diff --git a/test/functions/getMetaData/GetMetaDataComponent.cfc b/test/functions/getMetaData/GetMetaDataComponent.cfc new file mode 100644 index 0000000000..b2e9cc4096 --- /dev/null +++ b/test/functions/getMetaData/GetMetaDataComponent.cfc @@ -0,0 +1,19 @@ +component { + + function getMessage() { + return variables.message ?: ""; + } + + function setMessage( required string message ) { + variables.message = arguments.message; + } + + function getCount() { + return variables.count ?: 0; + } + + function setCount( required numeric count ) { + variables.count = arguments.count; + } + +} diff --git a/test/functions/getMetaData/GetMetaDataComponent2.cfc b/test/functions/getMetaData/GetMetaDataComponent2.cfc new file mode 100644 index 0000000000..474e7740cf --- /dev/null +++ b/test/functions/getMetaData/GetMetaDataComponent2.cfc @@ -0,0 +1,11 @@ +component { + + function getOtherMessage() { + return variables.otherMessage ?: ""; + } + + function setOtherMessage( required string otherMessage ) { + variables.otherMessage = arguments.otherMessage; + } + +} diff --git a/test/functions/getMetaData/GetMetaDataComponentChild.cfc b/test/functions/getMetaData/GetMetaDataComponentChild.cfc new file mode 100644 index 0000000000..61af7f2fee --- /dev/null +++ b/test/functions/getMetaData/GetMetaDataComponentChild.cfc @@ -0,0 +1,3 @@ +component extends="GetMetaDataComponent" { + +} diff --git a/test/general/Mappings.cfc b/test/general/Mappings.cfc index be08616163..29f2923e94 100644 --- a/test/general/Mappings.cfc +++ b/test/general/Mappings.cfc @@ -100,7 +100,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ archive="" primary="physical" trusted="no"; - var after=arrayLen(c.getMappings()); var has=false; loop array=c.getMappings() item="local.mapping" { @@ -112,7 +111,41 @@ component extends="org.lucee.cfml.test.LuceeTestCase"{ expect(before+1==after).toBeTrue(); }); + it( title='test mappings via admin API', body=function( currentSpec ) { + admin + action="getMappings" + type="web" + password="#request.WEBADMINPASSWORD#" + returnVariable="local.mappingsBefore"; + var before=mappingsBefore.recordCount; + var virtual="/testMappings"&createUniqueID(); + admin + action="updateMapping" + type="web" + password="#request.WEBADMINPASSWORD#" + virtual=virtual + physical="#getDirectoryFromPath(getCurrentTemplatePath())#" + toplevel="true" + archive="" + primary="physical" + trusted="no"; + + admin + action="getMappings" + type="web" + password="#request.WEBADMINPASSWORD#" + returnVariable="local.mappingsAfter"; + var after=mappingsAfter.recordCount; + + var has=false; + loop query=mappingsAfter { + if( virtual==mappingsAfter.virtual ) has=true; + } + + expect( has ).toBeTrue(); + expect( before+1==after ).toBeTrue(); + }); }); diff --git a/test/orm/Hibernate.cfc b/test/orm/Hibernate.cfc index 49a1b92e8e..dfb745b727 100644 --- a/test/orm/Hibernate.cfc +++ b/test/orm/Hibernate.cfc @@ -98,6 +98,14 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="orm" { assertEquals("0",trim(result.fileContent)); } + public void function testMappedSuperClass() { + if (noOrm()) return; + local.uri=createURI("mappedSuperClass/index.cfm"); + local.result=_InternalRequest(uri); + assertEquals(200,result.status); + assertEquals("",trim(result.fileContent)); + } + // ormSettings dialects tests public void function testDialectMYSQL() skip="notHasMysql" { if (noOrm()) return; diff --git a/test/orm/mappedSuperClass/Application.cfc b/test/orm/mappedSuperClass/Application.cfc new file mode 100644 index 0000000000..eb53146997 --- /dev/null +++ b/test/orm/mappedSuperClass/Application.cfc @@ -0,0 +1,14 @@ +component { + + this.name = "orm" & hash( getCurrentTemplatePath() ); + this.datasource = server.getDatasource( "h2", server._getTempDir( "orm-mappedSuperClass" ) ); + this.ormEnabled = true; + this.ormSettings = { + dbcreate = "dropcreate" + }; + + public function onRequestStart() { + setting requesttimeout=10; + } + +} diff --git a/test/orm/mappedSuperClass/BaseEntity.cfc b/test/orm/mappedSuperClass/BaseEntity.cfc new file mode 100644 index 0000000000..76b207a0cb --- /dev/null +++ b/test/orm/mappedSuperClass/BaseEntity.cfc @@ -0,0 +1,5 @@ +component accessors="true" mappedSuperClass="true" { + property name="id" type="numeric"; + property name="createdDate" type="date"; + property name="modifiedDate" type="date"; +} diff --git a/test/orm/mappedSuperClass/Person.cfc b/test/orm/mappedSuperClass/Person.cfc new file mode 100644 index 0000000000..903f0c6ef0 --- /dev/null +++ b/test/orm/mappedSuperClass/Person.cfc @@ -0,0 +1,6 @@ +component extends="BaseEntity" persistent="true" table="person" accessors="true" { + property name="firstName" type="string"; + property name="lastName" type="string"; + // Override inherited property - should NOT be persisted to DB + property name="modifiedDate" persistent="false"; +} diff --git a/test/orm/mappedSuperClass/index.cfm b/test/orm/mappedSuperClass/index.cfm new file mode 100644 index 0000000000..94fe3d33a6 --- /dev/null +++ b/test/orm/mappedSuperClass/index.cfm @@ -0,0 +1,80 @@ + + + try { + // Force ORM reload + ormReload(); + + // Test 1: Create entity instance + person = new Person(); + + // Test 2: Verify inherited properties are accessible via accessors + person.setId( 1 ); + person.setCreatedDate( now() ); + person.setModifiedDate( now() ); + person.setFirstName( "John" ); + person.setLastName( "Doe" ); + + if ( person.getId() != 1 ) { + throw( "Test 2 FAILED: getId() returned '#person.getId()#', expected 1" ); + } + if ( person.getFirstName() != "John" ) { + throw( "Test 2 FAILED: getFirstName() returned '#person.getFirstName()#', expected 'John'" ); + } + + // Test 3: Verify properties are in component metadata + md = getComponentMetadata( person ); + props = md.properties; + propNames = []; + for ( prop in props ) { + arrayAppend( propNames, prop.name ); + } + + if ( !arrayFind( propNames, "id" ) ) { + throw( "Test 3 FAILED: 'id' property not found in metadata" ); + } + if ( !arrayFind( propNames, "createdDate" ) ) { + throw( "Test 3 FAILED: 'createdDate' property not found in metadata" ); + } + if ( !arrayFind( propNames, "modifiedDate" ) ) { + throw( "Test 3 FAILED: 'modifiedDate' property not found in metadata" ); + } + if ( !arrayFind( propNames, "firstName" ) ) { + throw( "Test 3 FAILED: 'firstName' property not found in metadata" ); + } + + // Test 4: Verify persistent="false" override on inherited property + modifiedDateProp = {}; + for ( prop in props ) { + if ( prop.name == "modifiedDate" ) { + modifiedDateProp = prop; + break; + } + } + if ( structIsEmpty( modifiedDateProp ) ) { + throw( "Test 4 FAILED: modifiedDate property not found" ); + } + if ( !structKeyExists( modifiedDateProp, "persistent" ) || modifiedDateProp.persistent != false ) { + throw( "Test 4 FAILED: modifiedDate should have persistent=false, got: #serializeJSON( modifiedDateProp )#" ); + } + + // Test 5: Save and load entity (verifies DB schema doesn't include modifiedDate column) + entitySave( person ); + ormFlush(); + + ormClearSession(); + loaded = entityLoadByPK( "Person", 1 ); + if ( isNull( loaded ) ) { + throw( "Test 6 FAILED: Entity not found after save" ); + } + if ( loaded.getFirstName() != "John" ) { + throw( "Test 6 FAILED: Loaded entity has wrong firstName: '#loaded.getFirstName()#'" ); + } + + // All tests passed - output nothing + + } + catch ( any e ) { + writeOutput( e.message ); + rethrow; + } + diff --git a/test/tickets/LDEV2930.cfc b/test/tickets/LDEV2930.cfc index a3d3ccefd3..b759d8bbf4 100644 --- a/test/tickets/LDEV2930.cfc +++ b/test/tickets/LDEV2930.cfc @@ -1,4 +1,4 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" labels="syntax" skip=true { +component extends="org.lucee.cfml.test.LuceeTestCase" labels="syntax" { function run( testResults , testBox ) { describe( "Test suite for LDEV-2930", function() { it(title="break should work inside a times loop LDEV-2930", body = function( currentSpec ) { @@ -14,7 +14,7 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="syntax" skip=true var result = _InternalRequest( template:uri ); - expect(result.filecontent.trim()).toBe(""); + expect(result.filecontent.trim()).toBe("Lucee Lucee Lucee"); }); }); } diff --git a/test/tickets/LDEV2930/timesLoopContinue.cfm b/test/tickets/LDEV2930/timesLoopContinue.cfm index 9c3aaaa89b..5f41055d41 100644 --- a/test/tickets/LDEV2930/timesLoopContinue.cfm +++ b/test/tickets/LDEV2930/timesLoopContinue.cfm @@ -1,6 +1,6 @@ - loop times="5" { - echo("Hi There"); + loop times="3" { + echo("Lucee "); continue; } \ No newline at end of file diff --git a/test/tickets/LDEV3335.cfc b/test/tickets/LDEV3335.cfc index 2ca703536b..ad66fa52c7 100644 --- a/test/tickets/LDEV3335.cfc +++ b/test/tickets/LDEV3335.cfc @@ -1,33 +1,120 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" skip="true" { - function beforeAll() { +component extends="org.lucee.cfml.test.LuceeTestCase" { + function beforeAll() { variables.uri = createURI("LDEV3335"); } function run( testResults, testBox ){ - describe( title="Testcase for LDEV3335", body=function(){ - it( title="Check size of the component with no accessors", body=function( currentSpec ){ - local.result = _InternalRequest( - template : "#uri#\test.cfm", - FORM : { scene : 1 } + describe( title="Basic accessor functionality", body=function(){ + it( title="Basic accessors work", body=function( currentSpec ){ + var cfc = new LDEV3335.testWithAccessors(); + cfc.setA("test value"); + expect(cfc.getA()).toBe("test value"); + }); + it( title="Multiple instances are isolated", body=function( currentSpec ){ + var inst1 = new LDEV3335.testWithAccessors(); + var inst2 = new LDEV3335.testWithAccessors(); + var inst3 = new LDEV3335.testWithAccessors(); + inst1.setA("inst1"); + inst2.setA("inst2"); + inst3.setA("inst3"); + expect(inst1.getA()).toBe("inst1"); + expect(inst2.getA()).toBe("inst2"); + expect(inst3.getA()).toBe("inst3"); + }); + it( title="Default property values work", body=function( currentSpec ){ + var cfc = new LDEV3335.testWithAccessors(); + expect(cfc.getA()).toBe("1"); + }); + }); + + describe( title="Manual method overrides", body=function(){ + it( title="Manual getter overrides auto-generated accessor", body=function( currentSpec ){ + var cfc = new LDEV3335.testManualGetter(); + expect(cfc.getName()).toBe("UPPERCASE"); + }); + it( title="Manual setter overrides auto-generated accessor", body=function( currentSpec ){ + var cfc = new LDEV3335.testManualSetter(); + cfc.setValue(5); + expect(cfc.getValue()).toBe(10); // Manual setter doubles the value + }); + }); + + describe( title="Inheritance behavior", body=function(){ + it( title="Child inherits parent property accessors", body=function( currentSpec ){ + var child = new LDEV3335.testInheritChild(); + expect(child.getParentProp()).toBe("from parent"); + }); + it( title="Child manual method overrides parent accessor", body=function( currentSpec ){ + var child = new LDEV3335.testInheritChildOverride(); + expect(child.getParentProp()).toBe("CHILD OVERRIDE"); + }); + }); + + describe( title="Property attributes", body=function(){ + it( title="Property types are respected (numeric)", body=function( currentSpec ){ + var cfc = new LDEV3335.testPropertyTypes(); + expect(isNumeric(cfc.getAge())).toBeTrue(); + }); + it( title="Property types are respected (boolean)", body=function( currentSpec ){ + var cfc = new LDEV3335.testPropertyTypes(); + expect(isBoolean(cfc.getActive())).toBeTrue(); + }); + it( title="getter=false / setter=false flags respected", body=function( currentSpec ){ + var cfc = new LDEV3335.testGetterSetterFlags(); + // Should have getter + expect(cfc.getReadOnly()).toBe("readonly value"); + // Should NOT have setter + expect(function(){ + cfc.setReadOnly("new"); + }).toThrow(); + }); + }); + + describe( title="Dynamic method behavior", body=function(){ + it( title="Dynamic mixin method doesn't interfere with accessors", body=function( currentSpec ){ + var cfc = new LDEV3335.testPropertyTypes(); + // Accessor should work before mixin + cfc.setAge(25); + expect(cfc.getAge()).toBe(25); + // Add a dynamic method (mixin) + cfc.customMethod = function(){ return "mixin works"; }; + expect(cfc.customMethod()).toBe("mixin works"); + // Accessor should still work after mixin + cfc.setAge(30); + expect(cfc.getAge()).toBe(30); + }); + it( title="Dynamic override of accessor method", body=function( currentSpec ){ + var cfc = new LDEV3335.testPropertyTypes(); + cfc.setAge(25); + expect(cfc.getAge()).toBe(25); + // Override the getter with a dynamic method + cfc.getAge = function(){ return 999; }; + // Should use the dynamic override + expect(cfc.getAge()).toBe(999); + }); + }); + + describe( title="Static scope inheritance tests", body=function(){ + it( title="Access static method on base component", body=function( currentSpec ){ + var result = LDEV3335.BaseComponent::baseStaticMethod(); + expect(result).toBe("base static method"); + }); + it( title="Access static method on child component", body=function( currentSpec ){ + var result = LDEV3335.ChildComponent::childStaticMethod(); + expect(result).toBe("child static method"); + }); + it( title="Access base static method through child (bcp null issue)", body=function( currentSpec ){ + var result = LDEV3335.ChildComponent::baseStaticMethod(); + expect(result).toBe("base static method"); + }); + it( title="Access static method via variable reference (benchmark pattern)", body=function( currentSpec ){ + local.result = _InternalRequest( + template : "#uri#\testStaticViaVariable.cfm" ); - expect(trim(result.fileContent)).toBeLT(1000); - }); - it( title="Check size of the component with manual setters/getters", body=function( currentSpec ){ - local.result = _InternalRequest( - template : "#uri#\test.cfm", - FORM : { scene : 2 } - ); - expect(trim(result.fileContent)).toBeLT(5000); - }); - it( title="Check size of the component with accessors", body=function( currentSpec ){ - local.result = _InternalRequest( - template : "#uri#\test.cfm", - FORM : { scene : 3 } - ); - expect(trim(result.fileContent)).toBeLT(5000); - }); - }); - } - private string function createURI(string calledName){ + expect(trim(result.fileContent)).toBe('{"table":true,"name":true}'); + }); + }); + } + private string function createURI(string calledName){ var baseURI = "/test/#listLast(getDirectoryFromPath(getCurrenttemplatepath()),"\/")#/"; return baseURI&""&calledName; } diff --git a/test/tickets/LDEV3335/BaseComponent.cfc b/test/tickets/LDEV3335/BaseComponent.cfc new file mode 100644 index 0000000000..d4d1241e17 --- /dev/null +++ b/test/tickets/LDEV3335/BaseComponent.cfc @@ -0,0 +1,9 @@ +component { + static { + variables.baseStaticVar = "base value"; + } + + public static function baseStaticMethod() { + return "base static method"; + } +} diff --git a/test/tickets/LDEV3335/ChildComponent.cfc b/test/tickets/LDEV3335/ChildComponent.cfc new file mode 100644 index 0000000000..3ffdd7a49b --- /dev/null +++ b/test/tickets/LDEV3335/ChildComponent.cfc @@ -0,0 +1,9 @@ +component extends="BaseComponent" { + static { + variables.childStaticVar = "child value"; + } + + public static function childStaticMethod() { + return "child static method"; + } +} diff --git a/test/tickets/LDEV3335/StaticComponent.cfc b/test/tickets/LDEV3335/StaticComponent.cfc new file mode 100644 index 0000000000..91156cce5d --- /dev/null +++ b/test/tickets/LDEV3335/StaticComponent.cfc @@ -0,0 +1,10 @@ +component { + static.args = [ "table", "name" ]; + static function toSQL() { + var adapterArgs = structNew("ordered"); + arrayEach(static.args, function( value ) { + adapterArgs[ arguments.value ] = true; + }); + return adapterArgs; + } +} \ No newline at end of file diff --git a/test/tickets/LDEV3335/benchmark.cfm b/test/tickets/LDEV3335/benchmark.cfm new file mode 100644 index 0000000000..3fa6d237de --- /dev/null +++ b/test/tickets/LDEV3335/benchmark.cfm @@ -0,0 +1,90 @@ + +// Benchmark for LDEV-3335: accessor performance and memory usage +// Run with JFR enabled to profile memory allocation and method calls +// JFR will capture actual object allocations and sizes + +function runBenchmark( componentType, iterations=50000 ) { + var startTime = getTickCount(); + var components = []; + + systemOutput( "Starting benchmark for: #componentType#", true ); + systemOutput( "Creating #iterations# instances...", true ); + + // Create instances - JFR will track allocations + for ( var i = 1; i <= iterations; i++ ) { + switch( componentType ) { + case "noAccessors": + components[ i ] = new testNoAccessors(); + break; + case "manual": + components[ i ] = new testMannual(); + break; + case "withAccessors": + components[ i ] = new testWithAccessors(); + break; + } + } + + var endTime = getTickCount(); + var duration = endTime - startTime; + + systemOutput( "Results for #componentType#:", true ); + systemOutput( " - Creation time: #duration#ms", true ); + systemOutput( " - Instances per second: #numberFormat( iterations / (duration/1000) )#", true ); + systemOutput( " - Array size: #arrayLen( components )# instances", true ); + systemOutput( "", true ); + + return { + type: componentType, + iterations: iterations, + duration: duration, + instancesPerSec: iterations / (duration/1000) + }; +} + +// Run benchmarks +systemOutput( "=".repeat( 80 ), true ); +systemOutput( "LDEV-3335 Accessor Performance Benchmark", true ); +systemOutput( "=".repeat( 80 ), true ); +systemOutput( "", true ); + +// Warmup round to ensure JIT compilation +systemOutput( "Running warmup round (30,000 instances)...", true ); +runBenchmark( "noAccessors", 30000 ); +runBenchmark( "manual", 30000 ); +runBenchmark( "withAccessors", 30000 ); +systemOutput( "Warmup complete!", true ); +systemOutput( "", true ); + +// Actual benchmark runs +results = []; +results[ 1 ] = runBenchmark( "noAccessors" ); +results[ 2 ] = runBenchmark( "manual" ); +results[ 3 ] = runBenchmark( "withAccessors" ); + +// Summary comparison +systemOutput( "=".repeat( 80 ), true ); +systemOutput( "SUMMARY COMPARISON", true ); +systemOutput( "=".repeat( 80 ), true ); + +baseline = results[ 1 ]; + +for ( result in results ) { + timeOverhead = ((result.duration - baseline.duration) / baseline.duration) * 100; + + systemOutput( "#result.type#:", true ); + systemOutput( " Time overhead vs baseline: #numberFormat( timeOverhead, "0.00" )#%", true ); + systemOutput( "", true ); +} + +// Memory info +runtime = createObject( "java", "java.lang.Runtime" ).getRuntime(); +systemOutput( "JVM Memory Info:", true ); +systemOutput( " Total Memory: #numberFormat( runtime.totalMemory() / 1024 / 1024 )# MB", true ); +systemOutput( " Free Memory: #numberFormat( runtime.freeMemory() / 1024 / 1024 )# MB", true ); +systemOutput( " Used Memory: #numberFormat( (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024 )# MB", true ); +systemOutput( " Max Memory: #numberFormat( runtime.maxMemory() / 1024 / 1024 )# MB", true ); +systemOutput( "", true ); + +systemOutput( "Benchmark complete!", true ); + diff --git a/test/tickets/LDEV3335/testAccessorMethods.cfm b/test/tickets/LDEV3335/testAccessorMethods.cfm new file mode 100644 index 0000000000..0ef959888c --- /dev/null +++ b/test/tickets/LDEV3335/testAccessorMethods.cfm @@ -0,0 +1,41 @@ + +// Test that accessor methods (getters/setters) are created properly +// Run with script-runner to verify accessors work + +component = new testWithAccessors(); + +// Test setters exist and work +try { + component.setA( "value a" ); + component.setB( "value b" ); + component.setC( "value c" ); + component.setD( "value d" ); + systemOutput( "SUCCESS: All setters exist and work", true ); +} catch ( any e ) { + systemOutput( "ERROR: Setter failed - #e.message#", true ); + throw e; +} + +// Test getters exist and work +try { + if ( component.getA() != "value a" ) { + throw( "getA() returned wrong value: #component.getA()#" ); + } + if ( component.getB() != "value b" ) { + throw( "getB() returned wrong value: #component.getB()#" ); + } + if ( component.getC() != "value c" ) { + throw( "getC() returned wrong value: #component.getC()#" ); + } + if ( component.getD() != "value d" ) { + throw( "getD() returned wrong value: #component.getD()#" ); + } + systemOutput( "SUCCESS: All getters exist and work", true ); +} catch ( any e ) { + systemOutput( "ERROR: Getter failed - #e.message#", true ); + throw e; +} + +systemOutput( "", true ); +systemOutput( "All accessor tests passed!", true ); + diff --git a/test/tickets/LDEV3335/testGetterSetterFlags.cfc b/test/tickets/LDEV3335/testGetterSetterFlags.cfc new file mode 100644 index 0000000000..bb2d45ea70 --- /dev/null +++ b/test/tickets/LDEV3335/testGetterSetterFlags.cfc @@ -0,0 +1,5 @@ +component accessors="true" { + property name="readOnly" setter="false" default="readonly value"; + property name="writeOnly" getter="false" default="writeonly value"; + property name="normal" default="normal value"; +} diff --git a/test/tickets/LDEV3335/testInheritChild.cfc b/test/tickets/LDEV3335/testInheritChild.cfc new file mode 100644 index 0000000000..d9857cd649 --- /dev/null +++ b/test/tickets/LDEV3335/testInheritChild.cfc @@ -0,0 +1,3 @@ +component extends="testInheritParent" { + // Inherits parentProp accessor from parent +} diff --git a/test/tickets/LDEV3335/testInheritChildOverride.cfc b/test/tickets/LDEV3335/testInheritChildOverride.cfc new file mode 100644 index 0000000000..af00d8f688 --- /dev/null +++ b/test/tickets/LDEV3335/testInheritChildOverride.cfc @@ -0,0 +1,6 @@ +component extends="testInheritParent" { + // Override parent's auto-generated getter with manual one + function getParentProp() { + return "CHILD OVERRIDE"; + } +} diff --git a/test/tickets/LDEV3335/testInheritParent.cfc b/test/tickets/LDEV3335/testInheritParent.cfc new file mode 100644 index 0000000000..a0059305fa --- /dev/null +++ b/test/tickets/LDEV3335/testInheritParent.cfc @@ -0,0 +1,3 @@ +component accessors="true" { + property name="parentProp" default="from parent"; +} diff --git a/test/tickets/LDEV3335/testManualGetter.cfc b/test/tickets/LDEV3335/testManualGetter.cfc new file mode 100644 index 0000000000..4f7d592600 --- /dev/null +++ b/test/tickets/LDEV3335/testManualGetter.cfc @@ -0,0 +1,7 @@ +component accessors="true" { + property name="name" default="uppercase"; + + function getName() { + return ucase( variables.name ); + } +} diff --git a/test/tickets/LDEV3335/testManualSetter.cfc b/test/tickets/LDEV3335/testManualSetter.cfc new file mode 100644 index 0000000000..926095daf2 --- /dev/null +++ b/test/tickets/LDEV3335/testManualSetter.cfc @@ -0,0 +1,7 @@ +component accessors="true" { + property name="value" default="0"; + + function setValue( required numeric val ) { + variables.value = arguments.val * 2; + } +} diff --git a/test/tickets/LDEV3335/testNestedComponent.cfc b/test/tickets/LDEV3335/testNestedComponent.cfc new file mode 100644 index 0000000000..1bdd7122e0 --- /dev/null +++ b/test/tickets/LDEV3335/testNestedComponent.cfc @@ -0,0 +1,10 @@ +component accessors="true" { + property name="value" default="outer"; + + function testNested() { + inner = new testWithAccessors(); + inner.setA( "inner value" ); + // Now call our own getter - should return "outer", not "inner value" + return this.getValue() & ":" & inner.getA(); + } +} diff --git a/test/tickets/LDEV3335/testOwnerField.cfm b/test/tickets/LDEV3335/testOwnerField.cfm new file mode 100644 index 0000000000..21a2c19320 --- /dev/null +++ b/test/tickets/LDEV3335/testOwnerField.cfm @@ -0,0 +1,19 @@ + +// Test if owner field exists on regular accessor UDFs (no mixins) + +obj = new LDEV3335.testOwnerFieldComponent(); + +// Get metadata for the auto-generated getter +meta = getMetaData(obj.getMessage); + +systemOutput("=== Testing owner field on regular accessor UDF (no mixins) ===", true); +systemOutput("", true); +systemOutput("Metadata keys: " & structKeyList(meta), true); +systemOutput("", true); + +if (structKeyExists(meta, "owner")) { + systemOutput("✓ owner field EXISTS: " & meta.owner, true); +} else { + systemOutput("✗ owner field MISSING", true); +} + diff --git a/test/tickets/LDEV3335/testOwnerFieldAccess.cfm b/test/tickets/LDEV3335/testOwnerFieldAccess.cfm new file mode 100644 index 0000000000..2c2fad651f --- /dev/null +++ b/test/tickets/LDEV3335/testOwnerFieldAccess.cfm @@ -0,0 +1,46 @@ + +// Test different ways to access the owner field + +obj = new LDEV3335.testOwnerFieldComponent(); + +systemOutput("=== Testing different ways to access owner field ===", true); +systemOutput("", true); + +// Method 1: getMetaData() +try { + meta = getMetaData(obj.getMessage); + systemOutput("1. getMetaData(obj.getMessage).owner: " & (structKeyExists(meta, "owner") ? meta.owner : "NOT FOUND"), true); +} catch (any e) { + systemOutput("1. getMetaData() FAILED: " & e.message, true); +} + +// Method 2: Direct UDF reference +try { + udf = obj.getMessage; + systemOutput("2. Direct UDF reference type: " & getMetaData(udf).name, true); +} catch (any e) { + systemOutput("2. Direct UDF reference FAILED: " & e.message, true); +} + +// Method 3: Component metadata - does it show property accessor owners? +try { + compMeta = getMetaData(obj); + systemOutput("", true); + systemOutput("3. Component metadata functions: " & compMeta.functions.len(), true); + + loop array=compMeta.functions index="i" item="func" { + if (func.name == "getMessage") { + systemOutput(" getMessage in component metadata has owner? " & structKeyExists(func, "owner"), true); + if (structKeyExists(func, "owner")) { + systemOutput(" owner: " & func.owner, true); + } + } + } +} catch (any e) { + systemOutput("3. Component metadata FAILED: " & e.message, true); +} + +systemOutput("", true); +systemOutput("=== Conclusion ===", true); +systemOutput("The 'owner' field is part of UDF metadata, accessible via getMetaData(udfReference)", true); + diff --git a/test/tickets/LDEV3335/testOwnerFieldComponent.cfc b/test/tickets/LDEV3335/testOwnerFieldComponent.cfc new file mode 100644 index 0000000000..b435af2c55 --- /dev/null +++ b/test/tickets/LDEV3335/testOwnerFieldComponent.cfc @@ -0,0 +1,3 @@ +component { + property name="message" type="string"; +} diff --git a/test/tickets/LDEV3335/testPropertyOrder.cfm b/test/tickets/LDEV3335/testPropertyOrder.cfm new file mode 100644 index 0000000000..026cdf5058 --- /dev/null +++ b/test/tickets/LDEV3335/testPropertyOrder.cfm @@ -0,0 +1,31 @@ + +// Test to verify property order is preserved +// This is CRITICAL for ORM, serialization, and backward compatibility + +cfc = new component accessors="true" { + property name="zulu" type="string" default="last"; + property name="alpha" type="string" default="first"; + property name="mike" type="string" default="middle"; +}; + +meta = getMetaData( cfc ); +props = meta.properties; + +systemOutput( "Property count: #arrayLen( props )#", true ); +systemOutput( "Property 1: #props[ 1 ].name#", true ); +systemOutput( "Property 2: #props[ 2 ].name#", true ); +systemOutput( "Property 3: #props[ 3 ].name#", true ); + +// Properties MUST be in declaration order, not alphabetical! +if ( props[ 1 ].name != "zulu" ) { + throw( "Property order broken! Expected 'zulu' first, got '#props[ 1 ].name#'" ); +} +if ( props[ 2 ].name != "alpha" ) { + throw( "Property order broken! Expected 'alpha' second, got '#props[ 2 ].name#'" ); +} +if ( props[ 3 ].name != "mike" ) { + throw( "Property order broken! Expected 'mike' third, got '#props[ 3 ].name#'" ); +} + +systemOutput( "✓ Property order preserved correctly!", true ); + diff --git a/test/tickets/LDEV3335/testPropertyTypes.cfc b/test/tickets/LDEV3335/testPropertyTypes.cfc new file mode 100644 index 0000000000..dea69a2967 --- /dev/null +++ b/test/tickets/LDEV3335/testPropertyTypes.cfc @@ -0,0 +1,5 @@ +component accessors="true" { + property name="age" type="numeric" default="25"; + property name="active" type="boolean" default="true"; + property name="name" type="string" default="John"; +} diff --git a/test/tickets/LDEV3335/testStaticScopeInheritance.cfm b/test/tickets/LDEV3335/testStaticScopeInheritance.cfm new file mode 100644 index 0000000000..1988227775 --- /dev/null +++ b/test/tickets/LDEV3335/testStaticScopeInheritance.cfm @@ -0,0 +1,43 @@ + +// Test for static scope access on components with inheritance +// This reproduces the bcp null error from benchmark tests + +try { + systemOutput( "Testing static scope access on inherited components...", true ); + systemOutput( "", true ); + + // Test 1: Access static method on base component + systemOutput( "Test 1: Base component static method", true ); + BaseComp = new LDEV3335.BaseComponent(); + result = BaseComp::baseStaticMethod(); + systemOutput( " Result: #result#", true ); + systemOutput( " PASS", true ); + systemOutput( "", true ); + + // Test 2: Access static method on child component + systemOutput( "Test 2: Child component static method", true ); + ChildComp = new LDEV3335.ChildComponent(); + result = ChildComp::childStaticMethod(); + systemOutput( " Result: #result#", true ); + systemOutput( " PASS", true ); + systemOutput( "", true ); + + // Test 3: Access base static method through child (THIS IS WHERE IT FAILS) + systemOutput( "Test 3: Base static method accessed through child component", true ); + result = ChildComp::baseStaticMethod(); + systemOutput( " Result: #result#", true ); + systemOutput( " PASS", true ); + systemOutput( "", true ); + + systemOutput( "All tests passed!", true ); +} +catch ( any e ) { + systemOutput( "ERROR: #e.message#", true ); + systemOutput( "Detail: #e.detail#", true ); + systemOutput( "Type: #e.type#", true ); + systemOutput( "", true ); + systemOutput( "Stack trace:", true ); + systemOutput( e.stacktrace, true ); + throw( e ); +} + diff --git a/test/tickets/LDEV3335/testStaticViaVariable.cfm b/test/tickets/LDEV3335/testStaticViaVariable.cfm new file mode 100644 index 0000000000..ce49463aa6 --- /dev/null +++ b/test/tickets/LDEV3335/testStaticViaVariable.cfm @@ -0,0 +1,8 @@ + + // sigh, need to call this so static.args is initialized + cfc = new StaticComponent(); + loop times=10 { + StaticComponent::toSQL(); + } + echo( SerializeJson(StaticComponent::toSQL()) ); + diff --git a/test/tickets/LDEV4955.cfc b/test/tickets/LDEV4955.cfc index c7e37a283e..c375a6cf34 100644 --- a/test/tickets/LDEV4955.cfc +++ b/test/tickets/LDEV4955.cfc @@ -57,6 +57,6 @@ component extends="org.lucee.cfml.test.LuceeTestCase" { private function _expand ( str ){ var webFactory = createObject("java", "lucee.runtime.config.ConfigUtil"); - return webFactory.replaceConfigPlaceHolders(str ); + return webFactory.replaceConfigPlaceHolders(getPageContext().getConfig(),str ); } } \ No newline at end of file diff --git a/test/tickets/LDEV5605.cfc b/test/tickets/LDEV5605.cfc index 6762b2719f..ab5409d094 100644 --- a/test/tickets/LDEV5605.cfc +++ b/test/tickets/LDEV5605.cfc @@ -1,4 +1,4 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { +component extends="org.lucee.cfml.test.LuceeTestCase" skip=false { function run( testResults , testBox ) { describe( title="Test inline component exception has stacktrace", body=function() { @@ -9,19 +9,50 @@ component extends="org.lucee.cfml.test.LuceeTestCase" skip=true { throw "ERROR IN INLINE COMPONENT METHOD"; // stack trace should start here } } - function test(){ - dump( z.test() ); // but stack trace starts here - } var v = false; try { - test(); + z.test(); } catch ( e ){ v = e; } expect( v ).toBeStruct(); expect( v.tagContext[ 1 ].line ).toBe( 9 ); expect( v.tagContext[ 1 ].template ).toInclude( "LDEV5605.cfc" ); + }); + + it(title="test stacktrace for nested inline component", body = function( currentSpec ) { + var outer = new component { + function getInner(){ + return new component { + function test(){ + throw "ERROR IN NESTED INLINE COMPONENT"; // line 28 + } + }; + } + } + var inner = outer.getInner(); + var v = false; + try { + inner.test(); + } catch ( e ){ + v = e; + } + expect( v ).toBeStruct(); + expect( v.tagContext[ 1 ].line ).toBe( 28 ); + expect( v.tagContext[ 1 ].template ).toInclude( "LDEV5605.cfc" ); + }); + it(title="test stacktrace for sub component", body = function( currentSpec ) { + var sub = new LDEV5605.SubComp$SubComp(); + var v = false; + try { + sub.test(); + } catch ( e ){ + v = e; + } + expect( v ).toBeStruct(); + expect( v.tagContext[ 1 ].line ).toBe( 9 ); + expect( v.tagContext[ 1 ].template ).toInclude( "SubComp.cfc" ); }); }); diff --git a/test/tickets/LDEV5605/SubComp.cfc b/test/tickets/LDEV5605/SubComp.cfc new file mode 100644 index 0000000000..bfa6f62813 --- /dev/null +++ b/test/tickets/LDEV5605/SubComp.cfc @@ -0,0 +1,11 @@ +component { + function dummy() { + return "main"; + } +} + +component name="SubComp" { + function test(){ + throw "ERROR IN SUB COMPONENT"; // line 9 + } +} diff --git a/test/tickets/LDEV5632.cfc b/test/tickets/LDEV5632.cfc index 7c35ae00d1..c6f9c953c3 100644 --- a/test/tickets/LDEV5632.cfc +++ b/test/tickets/LDEV5632.cfc @@ -8,16 +8,20 @@ component extends="org.lucee.cfml.test.LuceeTestCase" labels="javasettings" { } var trg=variables.loadPaths&"dd-java-agent.jar"; - if(!fileExists(trg)) - fileCopy("https://dtdg.co/latest-java-tracer", trg); + if(!fileExists(trg)){ + // https://dtdg.co/latest-java-tracer + http url="https://repo1.maven.org/maven2/com/datadoghq/dd-java-agent/1.9.0/dd-java-agent-1.9.0.jar" file="dd-java-agent.jar" path=variables.loadPaths throwOnError=true redirect="true" timeout=10; + } var trg=variables.loadPaths&"opentelemetry-api.jar"; - if(!fileExists(trg)) - fileCopy("https://repo1.maven.org/maven2/io/opentelemetry/opentelemetry-api/1.50.0/opentelemetry-api-1.50.0.jar", trg); + if(!fileExists(trg)){ + http url="https://repo1.maven.org/maven2/io/opentelemetry/opentelemetry-api/1.50.0/opentelemetry-api-1.50.0.jar" file="opentelemetry-api.jar" path=variables.loadPaths throwOnError=true redirect="true" timeout=10; + } var trg=variables.loadPaths&"opentelemetry-context.jar"; - if(!fileExists(trg)) - fileCopy("https://repo1.maven.org/maven2/io/opentelemetry/opentelemetry-context/1.50.0/opentelemetry-context-1.50.0.jar", trg); + if(!fileExists(trg)){ + http url="https://repo1.maven.org/maven2/io/opentelemetry/opentelemetry-context/1.50.0/opentelemetry-context-1.50.0.jar" file="opentelemetry-context.jar" path=variables.loadPaths throwOnError=true redirect="true" timeout=10; + } } function afterAll() { diff --git a/test/tickets/LDEV5634.cfc b/test/tickets/LDEV5634.cfc index 0f4094d6b9..0f3795ba99 100644 --- a/test/tickets/LDEV5634.cfc +++ b/test/tickets/LDEV5634.cfc @@ -1,32 +1,121 @@ -component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq" skip=true { +component extends="org.lucee.cfml.test.LuceeTestCase" labels="qoq" { - function run( testResults , testBox ) { - describe( title="Test method matching", body=function() { + function beforeAll() { + variables.news = queryNew( "id,title,status", "integer,varchar,varchar" ); + queryAddRow( news ); + querySetCell( news, "id", 1 ); + querySetCell( news, "title", "Dewey defeats Truman" ); + querySetCell( news, "status", "published" ); + queryAddRow( news ); + querySetCell( news, "id", 2 ); + querySetCell( news, "title", "Men walk on Moon" ); + querySetCell( news, "status", javaCast( "null", "" ) ); + } - it(title="qoq should throw an error when a where clause column doesn't exist", body = function( currentSpec ) { - var news = queryNew("id,title", "integer,varchar"); - queryAddRow(news); - querySetCell(news, "id", "1"); - querySetCell(news, "title", "Dewey defeats Truman"); - queryAddRow(news); - querySetCell(news, "id", "2"); - querySetCell(news, "title", "Men walk on Moon"); + function run( testResults, testBox ) { + describe( title="LDEV-5634: QoQ should error on non-existent columns", body=function() { - expect(function(){ + it( title="WHERE clause with non-existent column should throw", body=function( currentSpec ) { + expect( function() { ``` SELECT * FROM news WHERE bob = 1 + ``` + } ).toThrow( "", "", "column [bob] not found" ); + } ); + + it( title="SELECT with non-existent column should throw", body=function( currentSpec ) { + expect( function() { + ``` + + SELECT bob + FROM news + + ``` + } ).toThrow( "", "", "column [bob] not found" ); + } ); + + it( title="ORDER BY with non-existent column should throw", body=function( currentSpec ) { + expect( function() { + ``` + + SELECT * + FROM news + ORDER BY bob + + ``` + } ).toThrow( "", "", "column [bob] not found" ); + } ); + it( title="GROUP BY with non-existent column should throw", body=function( currentSpec ) { + expect( function() { ``` - }).toThrow("", "", "column [bob] not found in query"); + + SELECT id + FROM news + GROUP BY bob + + ``` + } ).toThrow( "", "", "column [bob] not found" ); + } ); + + it( title="HAVING with non-existent column should throw", body=function( currentSpec ) { + expect( function() { + ``` + + SELECT id, count(*) as cnt + FROM news + GROUP BY id + HAVING bob > 1 + + ``` + } ).toThrow( "", "", "column [bob] not found" ); + } ); + + } ); + + describe( title="LDEV-5634: QoQ IS NULL/IS NOT NULL should still work", body=function() { + + it( title="IS NULL with valid column should work", body=function( currentSpec ) { + ``` + + SELECT * + FROM news + WHERE status IS NULL + + ``` + expect( q.recordCount ).toBe( 1 ); + expect( q.id ).toBe( 2 ); + } ); + + it( title="IS NOT NULL with valid column should work", body=function( currentSpec ) { + ``` + + SELECT * + FROM news + WHERE status IS NOT NULL + + ``` + expect( q.recordCount ).toBe( 1 ); + expect( q.id ).toBe( 1 ); + } ); - }); + it( title="valid WHERE clause should still work", body=function( currentSpec ) { + ``` + + SELECT * + FROM news + WHERE id = 1 + + ``` + expect( q.recordCount ).toBe( 1 ); + expect( q.title ).toBe( "Dewey defeats Truman" ); + } ); - - }); + } ); } } \ No newline at end of file diff --git a/test/tickets/LDEV5763.cfc b/test/tickets/LDEV5763.cfc index 76c4506b40..7c628472b9 100644 --- a/test/tickets/LDEV5763.cfc +++ b/test/tickets/LDEV5763.cfc @@ -2,19 +2,80 @@ component extends = "org.lucee.cfml.test.LuceeTestCase" { function run( testResults, textbox ) { - describe(title="LDEV-5763 cfml struct for javasettings hides all functions", body=function(){ + describe( title="LDEV-5763 javasettings attribute variations", body=function() { + + describe( title="Tag-in-script syntax", body=function() { + + it( title="struct literal (now throws validation error)", body=function( currentSpec ) { + expect( function() { + var obj = new LDEV5763.LDEV5763_cfml(); + }).toThrow(); + }); + + it( title="string format (WORKS)", body=function( currentSpec ) { + var obj = new LDEV5763.LDEV5763_json(); + var meta = getMetadata( obj ); + expect( meta.functions ).toHaveLength( 2 ); + }); + + }); + + describe( title="Function-style syntax", body=function() { + + it( title="struct literal (parse error)", body=function( currentSpec ) { + expect( function() { + var obj = new LDEV5763.LDEV5763_function_struct(); + }).toThrow(); + }); + + xit( title="string format (ASI bug - separate issue)", body=function( currentSpec ) { + var obj = new LDEV5763.LDEV5763_function_string(); + var meta = getMetadata( obj ); + expect( meta.functions ).toHaveLength( 1 ); + }); + + }); + + describe( title="Traditional tag syntax", body=function() { + + it( title="string format (WORKS)", body=function( currentSpec ) { + var obj = createObject( "component", "LDEV5763.LDEV5763_tag_string" ); + var meta = getMetadata( obj ); + expect( meta.functions ).toHaveLength( 1 ); + }); + + it( title="unquoted struct literal", body=function( currentSpec ) { + expect( function() { + var obj = createObject( "component", "LDEV5763.LDEV5763_tag_unquoted_struct" ); + }).toThrow(); + }); + + it( title="struct via expression (WORKS)", body=function( currentSpec ) { + var obj = createObject( "component", "LDEV5763.LDEV5763_tag_struct" ); + var meta = getMetadata( obj ); + expect( meta.functions ).toHaveLength( 1 ); + }); - xit(title = "java settings cfml", body = function ( currentSpec ){ - var obj = new LDEV5763.LDEV5763_cfml(); - var meta = getMetadata(obj); - expect(meta.functions).toHaveLength( 2 ); }); - it(title = "java settings json", body = function ( currentSpec ){ - var obj = new LDEV5763.LDEV5763_json(); - var meta = getMetadata(obj); - expect(meta.functions).toHaveLength( 2 ); + describe( title="Edge cases", body=function() { + + it( title="multiple maven coordinates string (WORKS)", body=function( currentSpec ) { + var obj = new LDEV5763.LDEV5763_multiple(); + var meta = getMetadata( obj ); + expect( meta.functions ).toHaveLength( 1 ); + expect( meta.javasettings ).toInclude( "commons-lang3" ); + expect( meta.javasettings ).toInclude( "commons-io" ); + }); + + it( title="multiple maven coordinates struct (now throws validation error)", body=function( currentSpec ) { + expect( function() { + var obj = new LDEV5763.LDEV5763_multiple_struct(); + }).toThrow(); + }); + }); + }); } diff --git a/test/tickets/LDEV5763/LDEV5763_cfml.cfc b/test/tickets/LDEV5763/LDEV5763_cfml.cfc index 8ef9a31f64..53b874eabc 100644 --- a/test/tickets/LDEV5763/LDEV5763_cfml.cfc +++ b/test/tickets/LDEV5763/LDEV5763_cfml.cfc @@ -1,40 +1,16 @@ +// Tag-in-script syntax with struct literal +// Expected: BUG - currently compiles but results in 0 functions component output=false - javasettings = { - maven = ["org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r"] + javasettings = { + maven = ["org.apache.commons:commons-lang3:3.12.0"] } { - public function test(){ - + public function test() { + return "cfml_struct"; } - function readFileCommits(required string repoPath, required string filePath) { - var commits = []; - try { - var FileRepository = createObject( - "java", - "org.eclipse.jgit.storage.file.FileRepositoryBuilder" - ); - var repository = FileRepository().setGitDir(createObject( - "java", "java.io.File", repoPath & "/.git" - )).readEnvironment().findGitDir().build(); - - var Git = createObject("java", "org.eclipse.jgit.api.Git"); - var git = Git.init().setDirectory(createObject("java", "java.io.File", repoPath)).call(); - - // Use LogCommand to get list of commits for the file - var logCmd = git.log().addPath(arguments.filePath); - var commitIter = logCmd.call().iterator(); - - while (commitIter.hasNext()) { - var commit = commitIter.next(); - commits.append(commit.getName()); // getName() returns the commit hash - } - - repository.close(); - } catch (any e) { - throw(message="Failed to enumerate commits: " & e.message, cause=e); - } - return commits; + function helper() { + return "helper"; } } diff --git a/test/tickets/LDEV5763/LDEV5763_function_string.cfc b/test/tickets/LDEV5763/LDEV5763_function_string.cfc new file mode 100644 index 0000000000..bff5ec6a97 --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_function_string.cfc @@ -0,0 +1,7 @@ +// Function-style syntax with string +// Expected: WORKS +component( output=false, javasettings = '{ maven = ["org.apache.commons:commons-lang3:3.12.0"] }' ) { + public function test() { + return "function_string"; + } +} diff --git a/test/tickets/LDEV5763/LDEV5763_function_struct.cfc b/test/tickets/LDEV5763/LDEV5763_function_struct.cfc new file mode 100644 index 0000000000..eac16630be --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_function_struct.cfc @@ -0,0 +1,12 @@ +// Function-style syntax with struct literal +// Expected: Parse/compile error +component( + output=false, + javasettings = { + maven = ["org.apache.commons:commons-lang3:3.12.0"] + } +) { + public function test() { + return "function_struct"; + } +} diff --git a/test/tickets/LDEV5763/LDEV5763_json.cfc b/test/tickets/LDEV5763/LDEV5763_json.cfc index fd81c3d86c..656f9946e6 100644 --- a/test/tickets/LDEV5763/LDEV5763_json.cfc +++ b/test/tickets/LDEV5763/LDEV5763_json.cfc @@ -1,40 +1,16 @@ +// Tag-in-script syntax with string format +// Expected: WORKS component output=false - javasettings = '{ - maven = ["org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r"] + javasettings = '{ + maven = ["org.apache.commons:commons-lang3:3.12.0"] }' { - public function test(){ - + public function test() { + return "json_string"; } - function readFileCommits(required string repoPath, required string filePath) { - var commits = []; - try { - var FileRepository = createObject( - "java", - "org.eclipse.jgit.storage.file.FileRepositoryBuilder" - ); - var repository = FileRepository().setGitDir(createObject( - "java", "java.io.File", repoPath & "/.git" - )).readEnvironment().findGitDir().build(); - - var Git = createObject("java", "org.eclipse.jgit.api.Git"); - var git = Git.init().setDirectory(createObject("java", "java.io.File", repoPath)).call(); - - // Use LogCommand to get list of commits for the file - var logCmd = git.log().addPath(arguments.filePath); - var commitIter = logCmd.call().iterator(); - - while (commitIter.hasNext()) { - var commit = commitIter.next(); - commits.append(commit.getName()); // getName() returns the commit hash - } - - repository.close(); - } catch (any e) { - throw(message="Failed to enumerate commits: " & e.message, cause=e); - } - return commits; + function helper() { + return "helper"; } } diff --git a/test/tickets/LDEV5763/LDEV5763_multiple.cfc b/test/tickets/LDEV5763/LDEV5763_multiple.cfc new file mode 100644 index 0000000000..0aa3a4e498 --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_multiple.cfc @@ -0,0 +1,14 @@ +// Multiple maven coordinates in string format +// Expected: WORKS +component output=false + javasettings = '{ + maven = [ + "org.apache.commons:commons-lang3:3.12.0", + "commons-io:commons-io:2.11.0" + ] + }' +{ + public function test() { + return "multiple_maven"; + } +} diff --git a/test/tickets/LDEV5763/LDEV5763_multiple_struct.cfc b/test/tickets/LDEV5763/LDEV5763_multiple_struct.cfc new file mode 100644 index 0000000000..fe83698ec1 --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_multiple_struct.cfc @@ -0,0 +1,15 @@ +// Multiple maven coordinates with struct literal +// Expected: BUG - currently compiles but results in 0 functions +component output=false + javasettings = { + maven = [ + "org.apache.commons:commons-lang3:3.12.0", + "commons-io:commons-io:2.11.0" + ] + } +{ + public function test() { + return "multiple_struct"; + } + +} diff --git a/test/tickets/LDEV5763/LDEV5763_tag_string.cfc b/test/tickets/LDEV5763/LDEV5763_tag_string.cfc new file mode 100644 index 0000000000..d725ba70da --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_tag_string.cfc @@ -0,0 +1,9 @@ + + + + + + + diff --git a/test/tickets/LDEV5763/LDEV5763_tag_struct.cfc b/test/tickets/LDEV5763/LDEV5763_tag_struct.cfc new file mode 100644 index 0000000000..36d18d4bac --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_tag_struct.cfc @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/tickets/LDEV5763/LDEV5763_tag_unquoted_struct.cfc b/test/tickets/LDEV5763/LDEV5763_tag_unquoted_struct.cfc new file mode 100644 index 0000000000..b1e499cdfd --- /dev/null +++ b/test/tickets/LDEV5763/LDEV5763_tag_unquoted_struct.cfc @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/test/tickets/LDEV5898.cfc b/test/tickets/LDEV5898.cfc new file mode 100644 index 0000000000..42f692af69 --- /dev/null +++ b/test/tickets/LDEV5898.cfc @@ -0,0 +1,30 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="ast" { + + function run( testResults, testBox ) { + describe( "LDEV-5898: For.dump() should handle null init/condition/update", function() { + + it( title="astFromPath() should work with for loop with null init", body=function() { + var testFile = getDirectoryFromPath( getCurrentTemplatePath() ) & "LDEV5898/for-null-init.cfm"; + var ast = astFromPath( testFile ); + expect( ast ).toBeStruct(); + expect( ast.type ).toBe( "Program" ); + }); + + it( title="astFromPath() should work with for loop with null condition", body=function() { + var testFile = getDirectoryFromPath( getCurrentTemplatePath() ) & "LDEV5898/for-null-condition.cfm"; + var ast = astFromPath( testFile ); + expect( ast ).toBeStruct(); + expect( ast.type ).toBe( "Program" ); + }); + + it( title="astFromPath() should work with for loop with null update", body=function() { + var testFile = getDirectoryFromPath( getCurrentTemplatePath() ) & "LDEV5898/for-null-update.cfm"; + var ast = astFromPath( testFile ); + expect( ast ).toBeStruct(); + expect( ast.type ).toBe( "Program" ); + }); + + }); + } + +} diff --git a/test/tickets/LDEV5898/for-null-condition.cfm b/test/tickets/LDEV5898/for-null-condition.cfm new file mode 100644 index 0000000000..025f9bf87d --- /dev/null +++ b/test/tickets/LDEV5898/for-null-condition.cfm @@ -0,0 +1,7 @@ + +// For loop with null condition - infinite loop with break +for (i = 1; ; i++) { + if (i > 3) break; + writeOutput(i); +} + diff --git a/test/tickets/LDEV5898/for-null-init.cfm b/test/tickets/LDEV5898/for-null-init.cfm new file mode 100644 index 0000000000..18d647b4ca --- /dev/null +++ b/test/tickets/LDEV5898/for-null-init.cfm @@ -0,0 +1,7 @@ + +// For loop with null init - loop from array +arr = [1, 2, 3]; +for ( ; i < arrayLen(arr); i++) { + writeOutput(arr[i]); +} + diff --git a/test/tickets/LDEV5898/for-null-update.cfm b/test/tickets/LDEV5898/for-null-update.cfm new file mode 100644 index 0000000000..4a83526b60 --- /dev/null +++ b/test/tickets/LDEV5898/for-null-update.cfm @@ -0,0 +1,7 @@ + +// For loop with null update - manual increment +for (i = 1; i <= 3; ) { + writeOutput(i); + i++; +} + diff --git a/test/tickets/LDEV5899.cfc b/test/tickets/LDEV5899.cfc new file mode 100644 index 0000000000..9c036ba359 --- /dev/null +++ b/test/tickets/LDEV5899.cfc @@ -0,0 +1,216 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="mapping" skip=true{ + + function run( testResults, testBox ) { + describe( "LDEV-5899: updateMapping cache refresh", function() { + + it( "should refresh cache with type='server' (first)", function() { + testMappingRefresh( "server", "first" ); + }); + + it( "should refresh cache with type='server' (second)", function() { + testMappingRefresh( "server", "second" ); + }); + + it( "should refresh cache with type='web' (first)", function() { + testMappingRefresh( "web", "first" ); + }); + + it( "should refresh cache with type='web' (second)", function() { + testMappingRefresh( "web", "second" ); + }); + + }); + + describe( "LDEV-5899: removeMapping cache refresh", function() { + + it( "should refresh cache with type='server' (first)", function() { + testRemoveMapping( "server", "first" ); + }); + + it( "should refresh cache with type='server' (second)", function() { + testRemoveMapping( "server", "second" ); + }); + + it( "should refresh cache with type='web' (first)", function() { + testRemoveMapping( "web", "first" ); + }); + + it( "should refresh cache with type='web' (second)", function() { + testRemoveMapping( "web", "second" ); + }); + + }); + + describe( "LDEV-5899: createArchive with append cache refresh", function() { + + it( "should refresh cache with type='server' (first)", function() { + testCreateArchiveAppend( "server", "first" ); + }); + + it( "should refresh cache with type='server' (second)", function() { + testCreateArchiveAppend( "server", "second" ); + }); + + it( "should refresh cache with type='web' (first)", function() { + testCreateArchiveAppend( "web", "first" ); + }); + + it( "should refresh cache with type='web' (second)", function() { + testCreateArchiveAppend( "web", "second" ); + }); + + }); + } + + function afterAll() { + var adminPassword = request.SERVERADMINPASSWORD; + var orders = [ "first", "second" ]; + var types = [ "server", "web" ]; + var prefixes = [ "update", "remove", "archive" ]; + + for ( var order in orders ) { + for ( var type in types ) { + for ( var prefix in prefixes ) { + admin action="removeMapping" type="#type#" password="#adminPassword#" virtual="/ldev5899-#prefix#-#type#-#order#"; + } + } + } + }; + + private function testMappingRefresh( required string type, string order="first" ) { + var adminPassword = request.SERVERADMINPASSWORD; + var testVirtual = "/ldev5899-update-#type#-#order#"; + var testPath = expandPath( "{temp-directory}cache/LDEV5899/update/#type#/#order#/" ); + + directoryCreate( testPath, true, true ); + + // Populate cache first by getting all mappings + admin + action="getMappings" + type="#type#" + password="#adminPassword#" + returnVariable="local.existingMappings"; + + // Now create mapping - this should refresh the cache + admin + action="updateMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#" + physical="#testPath#" + toplevel="true" + archive="" + primary="physical" + trusted="no"; + + // Try to get the mapping - if cache wasn't refreshed, this will fail + admin + action="getMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#" + returnVariable="local.mapping"; + + expect( mapping ).toHaveKey( "physical" ); + } + + private function testRemoveMapping( required string type, string order="first" ) { + var adminPassword = request.SERVERADMINPASSWORD; + var testVirtual = "/ldev5899-remove-#type#-#order#"; + var testPath = expandPath( "{temp-directory}cache/LDEV5899/remove/#type#/#order#/" ); + + directoryCreate( testPath, true, true ); + + // Create mapping + admin + action="updateMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#" + physical="#testPath#" + toplevel="true" + archive="" + primary="physical" + trusted="no"; + + // Populate cache by getting mappings + admin + action="getMappings" + type="#type#" + password="#adminPassword#" + returnVariable="local.mappings"; + + // Remove mapping - this should refresh the cache + admin + action="removeMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#"; + + // Check if mapping is gone from cache + admin + action="getMappings" + type="#type#" + password="#adminPassword#" + returnVariable="local.mappings"; + + var found = queryReduce( mappings, function( result, row ) { + return result || row.virtual == testVirtual; + }, false ); + + expect( found ).toBeFalse(); + } + + private function testCreateArchiveAppend( required string type, string order="first" ) { + var adminPassword = request.SERVERADMINPASSWORD; + var testVirtual = "/ldev5899-archive-#type#-#order#"; + var testPath = expandPath( "{temp-directory}cache/LDEV5899/archive/#type#/#order#/" ); + var archiveFile = expandPath( "{temp-directory}cache/LDEV5899/archive/#type#-#order#.lar" ); + + directoryCreate( testPath, true, true ); + fileWrite( testPath & "test.cfm", "test" ); + + // Create mapping + admin + action="updateMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#" + physical="#testPath#" + toplevel="true" + archive="" + primary="physical" + trusted="no"; + + // Populate cache by getting the mapping + admin + action="getMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#" + returnVariable="local.mapping1"; + + // Create archive with append=true - this should update the mapping and refresh cache + admin + action="createArchive" + type="#type#" + password="#adminPassword#" + file="#archiveFile#" + virtual="#testVirtual#" + addCFMLFiles="true" + addNonCFMLFiles="false" + append="true"; + + // Get mapping again - if cache wasn't refreshed, archive will be missing + admin + action="getMapping" + type="#type#" + password="#adminPassword#" + virtual="#testVirtual#" + returnVariable="local.mapping2"; + + expect( mapping2 ).toHaveKey( "archive" ); + expect( mapping2.archive ).notToBeNull(); + } + +} diff --git a/test/tickets/LDEV5900.cfc b/test/tickets/LDEV5900.cfc new file mode 100644 index 0000000000..a18babc337 --- /dev/null +++ b/test/tickets/LDEV5900.cfc @@ -0,0 +1,170 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="admin" { + + function run( testResults, testBox ) { + describe( "LDEV-5900: createArchive should collect all errors", function() { + + it( "should fail fast on first error (default stopOnError=true)", function() { + testCreateArchive( stopOnError=true, expectedErrorCount=1 ); + }); + + it( "should collect all errors when stopOnError=false", function() { + testCreateArchive( stopOnError=false, expectedErrorCount=2 ); + }); + + it( "should collect errors in variable when errorVariable is provided", function() { + testCreateArchiveWithErrorVariable(); + }); + + }); + } + + private function testCreateArchive( required boolean stopOnError, required numeric expectedErrorCount ) { + var adminPassword = request.SERVERADMINPASSWORD; + var testVirtual = "/ldev5900-test"; + var testPath = getDirectoryFromPath( getCurrentTemplatePath() ) & "LDEV5900/"; + var archiveFile = expandPath( "{temp-directory}LDEV5900-test.lar" ); + + // Clean up any existing mapping and archive + try { + admin action="removeMapping" type="web" password="#adminPassword#" virtual="#testVirtual#"; + } catch( any e ) {} + + if ( fileExists( archiveFile ) ) { + fileDelete( archiveFile ); + } + + // Create mapping pointing to our test assets + admin + action="updateMapping" + type="web" + password="#adminPassword#" + virtual="#testVirtual#" + physical="#testPath#" + toplevel="true" + archive="" + primary="physical" + trusted="no"; + + // Try to create archive + var caughtError = false; + var errorMessage = ""; + try { + admin + action="createArchive" + type="web" + password="#adminPassword#" + file="#archiveFile#" + virtual="#testVirtual#" + addCFMLFiles="true" + addNonCFMLFiles="false" + stopOnError="#stopOnError#"; + } catch( any e ) { + caughtError = true; + errorMessage = e.message; + } + + // Should have caught an error + expect( caughtError ).toBeTrue( "Expected an error to be thrown" ); + + // Archive file should NOT have been created when there are errors + expect( fileExists( archiveFile ) ).toBeFalse( "Archive should not be created when errors occur" ); + + // Count how many bad files are mentioned in the error + var bad1Mentioned = findNoCase( "bad1.cfm", errorMessage ) > 0; + var bad2Mentioned = findNoCase( "bad2.cfm", errorMessage ) > 0; + var errorCount = ( bad1Mentioned ? 1 : 0 ) + ( bad2Mentioned ? 1 : 0 ); + + expect( errorCount ).toBe( expectedErrorCount, "stopOnError=#stopOnError#: expected #expectedErrorCount# errors" ); + } + + private function testCreateArchiveWithErrorVariable() { + var adminPassword = request.SERVERADMINPASSWORD; + var testVirtual = "/ldev5900-test-errorvar"; + var testPath = getDirectoryFromPath( getCurrentTemplatePath() ) & "LDEV5900/"; + var archiveFile = expandPath( "{temp-directory}LDEV5900-test-errorvar.lar" ); + + // Clean up any existing mapping and archive + try { + admin action="removeMapping" type="web" password="#adminPassword#" virtual="#testVirtual#"; + } catch( any e ) {} + + if ( fileExists( archiveFile ) ) { + fileDelete( archiveFile ); + } + + // Create mapping pointing to our test assets + admin + action="updateMapping" + type="web" + password="#adminPassword#" + virtual="#testVirtual#" + physical="#testPath#" + toplevel="true" + archive="" + primary="physical" + trusted="no"; + + // Try to create archive with errorVariable - should NOT throw + var caughtError = false; + var compilationErrors = {}; + try { + admin + action="createArchive" + type="web" + password="#adminPassword#" + file="#archiveFile#" + virtual="#testVirtual#" + addCFMLFiles="true" + addNonCFMLFiles="false" + errorVariable="compilationErrors"; + } catch( any e ) { + caughtError = true; + } + + // Should NOT have thrown an error + expect( caughtError ).toBeFalse( "Should not throw when errorVariable is provided" ); + + // Should have errors in the variable + expect( compilationErrors ).toBeStruct(); + expect( structCount( compilationErrors ) ).toBe( 2, "Should have 2 errors collected" ); + + for ( var filePath in compilationErrors ) { + var errorInfo = compilationErrors[ filePath ]; + + // Verify error info structure + expect( errorInfo ).toBeStruct( "Error should be a struct" ); + expect( errorInfo ).toHaveKey( "message", "Error should have message" ); + expect( errorInfo ).toHaveKey( "detail", "Error should have detail" ); + } + + // Archive file should NOT have been created when there are errors + expect( fileExists( archiveFile ) ).toBeFalse( "Archive should not be created when errors occur" ); + + // Verify both bad files are in the error struct + var bad1Found = false; + var bad2Found = false; + for ( var filePath in compilationErrors ) { + if ( findNoCase( "bad1.cfm", filePath ) ) bad1Found = true; + if ( findNoCase( "bad2.cfm", filePath ) ) bad2Found = true; + } + + expect( bad1Found ).toBeTrue( "bad1.cfm should be in errors" ); + expect( bad2Found ).toBeTrue( "bad2.cfm should be in errors" ); + + // Clean up + try { + admin action="removeMapping" type="web" password="#adminPassword#" virtual="#testVirtual#"; + } catch( any e ) {} + } + + function afterAll() { + var adminPassword = request.SERVERADMINPASSWORD; + try { + admin action="removeMapping" type="web" password="#adminPassword#" virtual="/ldev5900-test"; + } catch( any e ) {} + try { + admin action="removeMapping" type="web" password="#adminPassword#" virtual="/ldev5900-test-errorvar"; + } catch( any e ) {} + } + +} diff --git a/test/tickets/LDEV5900/bad1.cfm b/test/tickets/LDEV5900/bad1.cfm new file mode 100644 index 0000000000..34c53dfe98 --- /dev/null +++ b/test/tickets/LDEV5900/bad1.cfm @@ -0,0 +1 @@ + diff --git a/test/tickets/LDEV5900/bad2.cfm b/test/tickets/LDEV5900/bad2.cfm new file mode 100644 index 0000000000..69ee64ced2 --- /dev/null +++ b/test/tickets/LDEV5900/bad2.cfm @@ -0,0 +1 @@ +#now()# diff --git a/test/tickets/LDEV5900/good2.cfm b/test/tickets/LDEV5900/good2.cfm new file mode 100644 index 0000000000..550cc08211 --- /dev/null +++ b/test/tickets/LDEV5900/good2.cfm @@ -0,0 +1,2 @@ + +#x# diff --git a/test/tickets/LDEV5901.cfc b/test/tickets/LDEV5901.cfc new file mode 100644 index 0000000000..10e79c7fca --- /dev/null +++ b/test/tickets/LDEV5901.cfc @@ -0,0 +1,88 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="tld" { + + function run( testResults, testBox ) { + describe( "LDEV-5901: Validate attribute-groups in TLDs", function() { + + it( "attribute-groups reference valid attributes for cfloop", function() { + var tagData = getTagData( "cf", "loop" ); + var errors = validateAttributeGroups( tagData ); + + expect( errors ).toBeEmpty( + "cfloop has invalid attribute-groups: #serializeJSON( errors )#" + ); + }); + + it( "all tags with attribute-groups have valid references", function() { + var tags = getTagList().cf; + var allErrors = {}; + + for( var tagName in tags ) { + var tagData = getTagData( "cf", tagName ); + + // Skip if no attribute groups + if( !structKeyExists( tagData, "attributeGroups" ) || !arrayLen( tagData.attributeGroups ) ) { + continue; + } + + var errors = validateAttributeGroups( tagData ); + if( arrayLen( errors ) > 0 ) { + allErrors[ tagName ] = errors; + } + } + + expect( allErrors ).toBeEmpty( + "Tags have invalid attribute-groups: #serializeJSON( allErrors )#" + ); + }); + }); + } + + private array function validateAttributeGroups( required struct tagData ) { + var errors = []; + + // Get all valid attribute names for this tag + var validAttributes = {}; + for( var attr in tagData.attributes ) { + validAttributes[ attr ] = true; + } + + // Check each group + if( structKeyExists( tagData, "attributeGroups" ) ) { + for( var group in tagData.attributeGroups ) { + + // Validate group has required fields + if( !structKeyExists( group, "name" ) || !len( group.name ) ) { + arrayAppend( errors, "Group missing 'name'" ); + continue; + } + + if( !structKeyExists( group, "label" ) || !len( group.label ) ) { + arrayAppend( errors, "Group '#group.name#' missing 'label'" ); + } + + if( !structKeyExists( group, "description" ) || !len( group.description ) ) { + arrayAppend( errors, "Group '#group.name#' missing 'description'" ); + } + + if( !structKeyExists( group, "attributes" ) ) { + arrayAppend( errors, "Group '#group.name#' missing 'attributes'" ); + continue; + } + + // Check each attribute in the group exists (skip if empty string - means no action-specific attributes) + if( len( group.attributes ) ) { + var groupAttrs = listToArray( group.attributes ); + for( var attrName in groupAttrs ) { + if( !structKeyExists( validAttributes, attrName ) ) { + arrayAppend( errors, + "Group '#group.name#' references non-existent attribute '#attrName#'" + ); + } + } + } + } + } + + return errors; + } +} diff --git a/test/tickets/LDEV5908.cfc b/test/tickets/LDEV5908.cfc new file mode 100644 index 0000000000..707f914584 --- /dev/null +++ b/test/tickets/LDEV5908.cfc @@ -0,0 +1,467 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function beforeAll() { + variables.originalNS = getApplicationSettings().nullSupport; + } + + function afterAll() { + application action="update" NULLSupport=variables.originalNS; + } + + function run( testResults, testBox ) { + + describe( "ConcurrentHashMapNullSupport - Basic null operations", function() { + + it( "can put and get null values", function() { + withNullSupport( false, function() { + var sct = {}; + sct.key = nullValue(); + // Without FNS, key with null value doesn't "exist" in CFML terms + expect( structKeyExists( sct, "key" ) ).toBeFalse(); + }); + }); + + it( "can put and get null values - FNS", function() { + withNullSupport( true, function() { + var sct = {}; + sct.key = nullValue(); + expect( structKeyExists( sct, "key" ) ).toBeTrue(); + // With FNS, isNull properly detects null + expect( isNull( sct.key ) ).toBeTrue(); + }); + }); + + it( "structKeyExists returns false for keys with null values", function() { + withNullSupport( false, function() { + var sct = { a: "value", b: nullValue() }; + expect( structKeyExists( sct, "a" ) ).toBeTrue(); + // Without FNS, null value means key doesn't exist + expect( structKeyExists( sct, "b" ) ).toBeFalse(); + expect( structKeyExists( sct, "missing" ) ).toBeFalse(); + }); + }); + + it( "structKeyExists returns true for keys with null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: "value", b: nullValue() }; + expect( structKeyExists( sct, "a" ) ).toBeTrue(); + expect( structKeyExists( sct, "b" ) ).toBeTrue(); + expect( structKeyExists( sct, "missing" ) ).toBeFalse(); + }); + }); + + it( "can store and retrieve multiple null values", function() { + withNullSupport( false, function() { + var sct = {}; + sct.null1 = nullValue(); + sct.null2 = nullValue(); + sct.value = "test"; + + // Without FNS, keys with null values don't exist + expect( structKeyExists( sct, "null1" ) ).toBeFalse(); + expect( structKeyExists( sct, "null2" ) ).toBeFalse(); + expect( structKeyExists( sct, "value" ) ).toBeTrue(); + expect( sct.value ).toBe( "test" ); + }); + }); + + it( "can store and retrieve multiple null values - FNS", function() { + withNullSupport( true, function() { + var sct = {}; + sct.null1 = nullValue(); + sct.null2 = nullValue(); + sct.value = "test"; + + expect( structKeyExists( sct, "null1" ) ).toBeTrue(); + expect( structKeyExists( sct, "null2" ) ).toBeTrue(); + expect( isNull( sct.null1 ) ).toBeTrue(); + expect( isNull( sct.null2 ) ).toBeTrue(); + expect( sct.value ).toBe( "test" ); + }); + }); + + it( "can remove keys with null values", function() { + withNullSupport( false, function() { + var sct = { key: nullValue() }; + // Without FNS, key with null doesn't exist + expect( structKeyExists( sct, "key" ) ).toBeFalse(); + + // But can still delete it + structDelete( sct, "key" ); + expect( structKeyExists( sct, "key" ) ).toBeFalse(); + }); + }); + + it( "can remove keys with null values - FNS", function() { + withNullSupport( true, function() { + var sct = { key: nullValue() }; + expect( structKeyExists( sct, "key" ) ).toBeTrue(); + + structDelete( sct, "key" ); + expect( structKeyExists( sct, "key" ) ).toBeFalse(); + }); + }); + }); + + describe( "ConcurrentHashMapNullSupport - Collection views and iteration", function() { + + it( "structEach iterates over entries including null values", function() { + withNullSupport( false, function() { + var sct = { a: "value", b: nullValue(), c: "another" }; + var keys = []; + var foundNull = false; + + structEach( sct, function( key, value ) { + arrayAppend( keys, arguments.key ); + // With explicit arguments scope, isNull works correctly + if ( isNull( arguments.value ) ) { + foundNull = true; + } + }); + + expect( arrayLen( keys ) ).toBe( 3 ); + expect( foundNull ).toBeTrue(); + }); + }); + + it( "structEach iterates over entries including null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: "value", b: nullValue(), c: "another" }; + var keys = []; + var foundNull = false; + + structEach( sct, function( key, value ) { + arrayAppend( keys, arguments.key ); + if ( isNull( arguments.value ) ) { + foundNull = true; + } + }); + + expect( arrayLen( keys ) ).toBe( 3 ); + expect( foundNull ).toBeTrue(); + }); + }); + + it( "structMap handles null values", function() { + withNullSupport( false, function() { + var sct = { a: 1, b: nullValue(), c: 3 }; + var result = structMap( sct, function( key, value ) { + return isNull( arguments.value ) ? 0 : arguments.value * 2; + }); + + expect( result.a ).toBe( 2 ); + expect( result.b ).toBe( 0 ); + expect( result.c ).toBe( 6 ); + }); + }); + + it( "structMap handles null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: 1, b: nullValue(), c: 3 }; + var result = structMap( sct, function( key, value ) { + return isNull( arguments.value ) ? 0 : arguments.value * 2; + }); + + expect( result.a ).toBe( 2 ); + expect( result.b ).toBe( 0 ); + expect( result.c ).toBe( 6 ); + }); + }); + + it( "for-in loop iterates over keys with null values", function() { + withNullSupport( false, function() { + var sct = { a: "value", b: nullValue() }; + var count = 0; + var foundBKey = false; + + for ( var key in sct ) { + count++; + // Check if we find the "b" key (without accessing value) + if ( key == "b" ) { + foundBKey = true; + } + } + + expect( count ).toBe( 2 ); + // The key IS there, just structKeyExists returns false + expect( foundBKey ).toBeTrue(); + }); + }); + + it( "for-in loop iterates over keys with null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: "value", b: nullValue() }; + var count = 0; + var foundNullKey = false; + + for ( var key in sct ) { + count++; + if ( key == "b" && isNull( sct[ key ] ) ) { + foundNullKey = true; + } + } + + expect( count ).toBe( 2 ); + expect( foundNullKey ).toBeTrue(); + }); + }); + + it( "structKeyArray includes keys with null values", function() { + withNullSupport( false, function() { + var sct = { a: "value", b: nullValue(), c: "another" }; + var keys = structKeyArray( sct ); + + expect( arrayLen( keys ) ).toBe( 3 ); + // Check if "b" is in the array manually + var foundB = false; + for ( var k in keys ) { + if ( k == "b" ) { + foundB = true; + } + } + expect( foundB ).toBeTrue(); + }); + }); + + it( "structKeyArray includes keys with null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: "value", b: nullValue(), c: "another" }; + var keys = structKeyArray( sct ); + + expect( arrayLen( keys ) ).toBe( 3 ); + // Check if "b" is in the array manually + var foundB = false; + for ( var k in keys ) { + if ( k == "b" ) { + foundB = true; + } + } + expect( foundB ).toBeTrue(); + }); + }); + }); + + describe( "ConcurrentHashMapNullSupport - Functional methods", function() { + + it( "elvis operator with null value vs missing key", function() { + withNullSupport( false, function() { + var sct = { nullKey: nullValue() }; + + // Missing key uses default + var result1 = sct.missing ?: "default"; + expect( result1 ).toBe( "default" ); + + // Null value also treated as empty with elvis + var result2 = sct.nullKey ?: "default"; + expect( result2 ).toBe( "default" ); + }); + }); + + it( "elvis operator with null value vs missing key - FNS", function() { + withNullSupport( true, function() { + var sct = { nullKey: nullValue() }; + + // Missing key uses default + var result1 = sct.missing ?: "default"; + expect( result1 ).toBe( "default" ); + + // Null value also treated as empty with elvis + var result2 = sct.nullKey ?: "default"; + expect( result2 ).toBe( "default" ); + }); + }); + + it( "structAppend handles null values", function() { + withNullSupport( false, function() { + var sct1 = { a: "value1" }; + var sct2 = { b: nullValue(), c: "value2" }; + + structAppend( sct1, sct2 ); + + // Without FNS, key with null doesn't exist + expect( structKeyExists( sct1, "b" ) ).toBeFalse(); + expect( structKeyExists( sct1, "c" ) ).toBeTrue(); + expect( sct1.c ).toBe( "value2" ); + }); + }); + + it( "structAppend handles null values - FNS", function() { + withNullSupport( true, function() { + var sct1 = { a: "value1" }; + var sct2 = { b: nullValue(), c: "value2" }; + + structAppend( sct1, sct2 ); + + expect( structKeyExists( sct1, "b" ) ).toBeTrue(); + expect( isNull( sct1.b ) ).toBeTrue(); + expect( sct1.c ).toBe( "value2" ); + }); + }); + + it( "structCopy preserves null values", function() { + withNullSupport( false, function() { + var original = { a: "value", b: nullValue() }; + var copy = structCopy( original ); + + expect( structKeyExists( copy, "a" ) ).toBeTrue(); + // Without FNS, key with null doesn't exist + expect( structKeyExists( copy, "b" ) ).toBeFalse(); + expect( copy.a ).toBe( "value" ); + }); + }); + + it( "structCopy preserves null values - FNS", function() { + withNullSupport( true, function() { + var original = { a: "value", b: nullValue() }; + var copy = structCopy( original ); + + expect( structKeyExists( copy, "a" ) ).toBeTrue(); + expect( structKeyExists( copy, "b" ) ).toBeTrue(); + expect( isNull( copy.b ) ).toBeTrue(); + expect( copy.a ).toBe( "value" ); + }); + }); + + it( "structUpdate can update to null value", function() { + withNullSupport( false, function() { + var sct = { key: "value" }; + expect( sct.key ).toBe( "value" ); + + structUpdate( sct, "key", nullValue() ); + // Without FNS, after setting to null, key doesn't exist + expect( structKeyExists( sct, "key" ) ).toBeFalse(); + }); + }); + + it( "structUpdate can update to null value - FNS", function() { + withNullSupport( true, function() { + var sct = { key: "value" }; + expect( sct.key ).toBe( "value" ); + + structUpdate( sct, "key", nullValue() ); + expect( structKeyExists( sct, "key" ) ).toBeTrue(); + expect( isNull( sct.key ) ).toBeTrue(); + }); + }); + }); + + describe( "ConcurrentHashMapNullSupport - Edge cases and regressions", function() { + + it( "handles comparison with null values", function() { + withNullSupport( false, function() { + var sct = { key: nullValue() }; + + // Without FNS, key with null doesn't exist + expect( structKeyExists( sct, "key" ) ).toBeFalse(); + }); + }); + + it( "handles comparison with null values - FNS", function() { + withNullSupport( true, function() { + var sct = { key: nullValue() }; + var value = sct.key; + + expect( isNull( value ) ).toBeTrue(); + // Comparing null with == in CFML + expect( value == nullValue() ).toBeTrue(); + }); + }); + + it( "LDEV-622: can iterate over Java Map with null values", function() { + withNullSupport( false, function() { + // Create a struct that mimics the LDEV-622 scenario + var sct = { validKey: "value", nullKey: nullValue() }; + var hasError = false; + + try { + var count = 0; + for ( var key in sct ) { + count++; + } + expect( count ).toBe( 2 ); + } + catch ( any e ) { + hasError = true; + } + + expect( hasError ).toBeFalse( "Should not throw NPE when iterating" ); + }); + }); + + it( "LDEV-622: can iterate over Java Map with null values - FNS", function() { + withNullSupport( true, function() { + // Create a struct that mimics the LDEV-622 scenario + var sct = { validKey: "value", nullKey: nullValue() }; + var hasError = false; + + try { + var count = 0; + for ( var key in sct ) { + count++; + } + expect( count ).toBe( 2 ); + } + catch ( any e ) { + hasError = true; + } + + expect( hasError ).toBeFalse( "Should not throw NPE when iterating" ); + }); + }); + + it( "structCount includes entries with null values", function() { + withNullSupport( false, function() { + var sct = { a: "value", b: nullValue(), c: "another" }; + expect( structCount( sct ) ).toBe( 3 ); + }); + }); + + it( "structCount includes entries with null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: "value", b: nullValue(), c: "another" }; + expect( structCount( sct ) ).toBe( 3 ); + }); + }); + + it( "structIsEmpty returns false when struct has null values", function() { + withNullSupport( false, function() { + var sct = { key: nullValue() }; + expect( structIsEmpty( sct ) ).toBeFalse(); + }); + }); + + it( "structIsEmpty returns false when struct has null values - FNS", function() { + withNullSupport( true, function() { + var sct = { key: nullValue() }; + expect( structIsEmpty( sct ) ).toBeFalse(); + }); + }); + + it( "can clear struct containing null values", function() { + withNullSupport( false, function() { + var sct = { a: "value", b: nullValue() }; + structClear( sct ); + + expect( structIsEmpty( sct ) ).toBeTrue(); + expect( structCount( sct ) ).toBe( 0 ); + }); + }); + + it( "can clear struct containing null values - FNS", function() { + withNullSupport( true, function() { + var sct = { a: "value", b: nullValue() }; + structClear( sct ); + + expect( structIsEmpty( sct ) ).toBeTrue(); + expect( structCount( sct ) ).toBe( 0 ); + }); + }); + }); + } + + // Private helper function to toggle NullSupport and run test + private function withNullSupport( boolean enabled, required function testFn ) { + application action="update" NULLSupport=arguments.enabled; + arguments.testFn(); + } +} diff --git a/test/tickets/LDEV5913.cfc b/test/tickets/LDEV5913.cfc new file mode 100644 index 0000000000..036c190de1 --- /dev/null +++ b/test/tickets/LDEV5913.cfc @@ -0,0 +1,22 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults, testBox ){ + describe( "LDEV-5913 - super method resolution with closures", function(){ + + it( "should be able to call super methods from within nested closures", function(){ + // Use _internalRequest to isolate from TestBox's closure context + local.result = _internalRequest( + template: createURI( "LDEV5913/ldev5913.cfm" ) + ); + expect( local.result.filecontent.trim() ).toBe( "SUCCESS" ); + } ); + + } ); + } + + private string function createURI( string calledName ) { + var baseURI = "/test/#listLast( getDirectoryFromPath( getCurrenttemplatepath() ), "\/")#/"; + return baseURI & "" & calledName; + } + +} diff --git a/test/tickets/LDEV5913/ActualTest.cfc b/test/tickets/LDEV5913/ActualTest.cfc new file mode 100644 index 0000000000..7a07af99bc --- /dev/null +++ b/test/tickets/LDEV5913/ActualTest.cfc @@ -0,0 +1,13 @@ +// Exact replica of Preside's PresideObjectReaderTest structure +component extends="BaseTest" { + + function runTest() { + describe( "readObject()", function(){ + it( "should return a list of public method when component has public methods", function(){ + // This is the exact pattern from Preside - nested closures calling super.assert() + super.assert( true, "No methods key was returned" ); + return true; + } ); + } ); + } +} diff --git a/test/tickets/LDEV5913/BaseTest.cfc b/test/tickets/LDEV5913/BaseTest.cfc new file mode 100644 index 0000000000..a1f4794001 --- /dev/null +++ b/test/tickets/LDEV5913/BaseTest.cfc @@ -0,0 +1,25 @@ +// Mimics TestBox's BaseSpec +component { + + function assert( required expression, message = "" ) { + if ( !arguments.expression ) { + throw( type="AssertionFailed", message=arguments.message ); + } + return true; + } + + function describe( required string title, required function body ) { + // Execute the closure - this changes the execution context + return arguments.body(); + } + + function it( required string title, required function body ) { + // Execute the closure - this changes the execution context + return arguments.body(); + } + + function runTest() { + // This is meant to be overridden + return true; + } +} diff --git a/test/tickets/LDEV5913/ldev5913.cfm b/test/tickets/LDEV5913/ldev5913.cfm new file mode 100644 index 0000000000..5786dae90c --- /dev/null +++ b/test/tickets/LDEV5913/ldev5913.cfm @@ -0,0 +1,10 @@ + + try { + test = new ActualTest(); + result = test.runTest(); + writeOutput( "SUCCESS" ); + } + catch ( any e ) { + writeOutput( e.message & chr(10) & e.stacktrace ); + } + diff --git a/test/tickets/LDEV5927.cfc b/test/tickets/LDEV5927.cfc new file mode 100644 index 0000000000..1015f18251 --- /dev/null +++ b/test/tickets/LDEV5927.cfc @@ -0,0 +1,14 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function run( testResults, testBox ) { + describe( "LDEV-5927: javasettings JSON validation at compile time", function() { + + it( "should fail at compile time with invalid JSON", function() { + expect( function() { + new LDEV5927.invalid_json(); + }).toThrow(); + }); + + }); + } +} diff --git a/test/tickets/LDEV5927/invalid_json.cfc b/test/tickets/LDEV5927/invalid_json.cfc new file mode 100644 index 0000000000..c0fb08b9e3 --- /dev/null +++ b/test/tickets/LDEV5927/invalid_json.cfc @@ -0,0 +1,2 @@ +component javasettings='{"maven": ["invalid' { +} diff --git a/test/tickets/LDEV5932.cfc b/test/tickets/LDEV5932.cfc new file mode 100644 index 0000000000..51362ae2e7 --- /dev/null +++ b/test/tickets/LDEV5932.cfc @@ -0,0 +1,178 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" { + + function beforeAll() { + variables.httpbin = server.getTestService( "httpbin" ); + variables.updateProvider = server.getTestService( "updateProvider" ); + } + + function run( testResults, testBox ) { + describe( "Test suite for LDEV-5932 - CFHTTP GET compression", function() { + + it( title="Standard GET request should receive gzip compression from httpbin", body=function( currentSpec ) { + if ( structCount( variables.httpbin ) == 0 ) { + return; + } + + cfhttp( + url="http://#variables.httpbin.server#:#variables.httpbin.port#/gzip", + method="GET", + result="local.result", + compression=true + ); + + expect( local.result.statusCode ).toBe( "200 OK" ); + + // httpbin /gzip endpoint returns JSON with "gzipped": true when compression was used + // Note: Content-Encoding header is removed by Apache HttpClient after automatic decompression + var data = deserializeJSON( local.result.fileContent ); + expect( data ).toHaveKey( "gzipped" ); + expect( data.gzipped ).toBeTrue(); + }); + + it( title="GET request with body parameter should still work (HttpGetWithBody)", body=function( currentSpec ) { + if ( structCount( variables.httpbin ) == 0 ) { + return; + } + + cfhttp( + url="http://#variables.httpbin.server#:#variables.httpbin.port#/anything", + method="GET", + result="local.result" + ) { + cfhttpparam( type="body", value='{"test":"data"}' ); + } + + expect( local.result.statusCode ).toBe( "200 OK" ); + + var data = deserializeJSON( local.result.fileContent ); + expect( data ).toHaveKey( "method" ); + expect( data.method ).toBe( "GET" ); + }); + + it( title="HEAD request should receive gzip compression (existing behavior)", body=function( currentSpec ) { + if ( structCount( variables.httpbin ) == 0 ) { + return; + } + + cfhttp( + url="http://#variables.httpbin.server#:#variables.httpbin.port#/gzip", + method="HEAD", + result="local.result", + compression=true + ); + + expect( local.result.statusCode ).toBe( "200 OK" ); + // HEAD requests don't have body, but should have Content-Encoding header + expect( local.result.responseHeader ).toHaveKey( "Content-Encoding" ); + }); + + it( title="DELETE request without body should use standard HttpDelete", body=function( currentSpec ) { + if ( structCount( variables.httpbin ) == 0 ) { + return; + } + + cfhttp( + url="http://#variables.httpbin.server#:#variables.httpbin.port#/delete", + method="DELETE", + result="local.result" + ); + + expect( local.result.statusCode ).toBe( "200 OK" ); + + var data = deserializeJSON( local.result.fileContent ); + // Just verify we got a valid httpbin response (has headers key) + expect( data ).toHaveKey( "headers" ); + expect( data ).toHaveKey( "url" ); + }); + + it( title="DELETE request with body parameter should use HttpDeleteWithBody", body=function( currentSpec ) { + if ( structCount( variables.httpbin ) == 0 ) { + return; + } + + cfhttp( + url="http://#variables.httpbin.server#:#variables.httpbin.port#/delete", + method="DELETE", + result="local.result" + ) { + cfhttpparam( type="body", value='{"test":"data"}' ); + } + + expect( local.result.statusCode ).toBe( "200 OK" ); + + var data = deserializeJSON( local.result.fileContent ); + // Verify we got a valid httpbin response + expect( data ).toHaveKey( "headers" ); + expect( data ).toHaveKey( "url" ); + // Verify body was sent - httpbin puts it in data key + expect( data ).toHaveKey( "data" ); + expect( data.data ).toInclude( "test" ); + }); + + it( title="Standard GET request should send proper Accept-Encoding header (echoGET test)", body=function( currentSpec ) { + if ( structCount( variables.updateProvider ) == 0 ) { + return; + } + + cfhttp( + url="#variables.updateProvider.url#/rest/update/provider/echoGet", + method="GET", + result="local.result", + compression=true + ); + + expect( local.result.statusCode ).toBe( "200 OK" ); + + var data = deserializeJSON( local.result.fileContent ); + + // Verify Accept-Encoding header was sent (proves compression is being requested) + expect( data ).toHaveKey( "httpRequestData" ); + expect( data.httpRequestData ).toHaveKey( "headers" ); + expect( data.httpRequestData.headers ).toHaveKey( "accept-encoding" ); + expect( data.httpRequestData.headers["accept-encoding"] ).toInclude( "gzip" ); + }); + + xit( title="GET request with compression=false should NOT request gzip (DISABLED - Cloudflare modifies headers)", body=function( currentSpec ) { + // Note: This test is disabled because Cloudflare adds 'br' to Accept-Encoding even when we don't send gzip + // The updateProvider endpoint is behind Cloudflare which modifies the headers + if ( structCount( variables.updateProvider ) == 0 ) { + return; + } + + cfhttp( + url="#variables.updateProvider.url#/rest/update/provider/echoGet", + method="GET", + result="local.result", + compression=false + ); + + expect( local.result.statusCode ).toBe( "200 OK" ); + }); + + it( title="GET request with compression=false should NOT request gzip (httpbin test)", body=function( currentSpec ) { + if ( structCount( variables.httpbin ) == 0 ) { + return; + } + + cfhttp( + url="http://#variables.httpbin.server#:#variables.httpbin.port#/headers", + method="GET", + result="local.result", + compression=false + ); + + expect( local.result.statusCode ).toBe( "200 OK" ); + + var data = deserializeJSON( local.result.fileContent ); + + // When compression=false, Accept-Encoding should be "deflate;q=0" (not gzip) + expect( data ).toHaveKey( "headers" ); + expect( data.headers ).toHaveKey( "Accept-Encoding" ); + expect( data.headers["Accept-Encoding"] ).notToInclude( "gzip" ); + expect( data.headers["Accept-Encoding"] ).toInclude( "deflate" ); + }); + + }); + } + +} diff --git a/test/tickets/LDEV5939.cfc b/test/tickets/LDEV5939.cfc new file mode 100644 index 0000000000..7e92b7da6c --- /dev/null +++ b/test/tickets/LDEV5939.cfc @@ -0,0 +1,89 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="directory" { + + function run( testResults, testBox ) { + describe( "test case for LDEV-5939 - empty/blank directory path behaviour", function() { + + // Destructive operations - MUST throw to prevent accidental deletion of current directory + describe( "destructive operations should throw with empty/blank path", function() { + + it( title="cfdirectory action=delete with empty directory should throw", body=function( currentSpec ) { + expect( function() { + directory action="delete" directory="" recurse="yes"; + }).toThrow(); + }); + + it( title="cfdirectory action=delete with blank directory should throw", body=function( currentSpec ) { + expect( function() { + directory action="delete" directory=" " recurse="yes"; + }).toThrow(); + }); + + it( title="cfdirectory action=rename with empty directory should throw", body=function( currentSpec ) { + expect( function() { + directory action="rename" directory="" newdirectory="foo"; + }).toThrow(); + }); + + it( title="DirectoryDelete() with empty path should throw", body=function( currentSpec ) { + expect( function() { + DirectoryDelete( "" ); + }).toThrow(); + }); + + it( title="DirectoryDelete() with blank path should throw", body=function( currentSpec ) { + expect( function() { + DirectoryDelete( " " ); + }).toThrow(); + }); + + it( title="DirectoryRename() with empty source path should throw", body=function( currentSpec ) { + expect( function() { + DirectoryRename( "", "newname" ); + }).toThrow(); + }); + + it( title="DirectoryRename() with blank source path should throw", body=function( currentSpec ) { + expect( function() { + DirectoryRename( " ", "newname" ); + }).toThrow(); + }); + + }); + + // Non-destructive operations - empty path defaults to current directory (sensible behaviour) + describe( "non-destructive operations should default to current directory", function() { + + it( title="cfdirectory action=list with empty directory lists current dir", body=function( currentSpec ) { + directory action="list" directory="" name="local.qry"; + expect( local.qry ).toBeQuery(); + expect( local.qry.recordCount ).toBeGT( 0 ); + }); + + it( title="DirectoryList() with empty path lists current directory", body=function( currentSpec ) { + var result = DirectoryList( "" ); + expect( result ).toBeArray(); + expect( result.len() ).toBeGT( 0 ); + }); + + it( title="DirectoryExists() with empty path returns false", body=function( currentSpec ) { + // DirectoryExists explicitly returns false for empty/blank paths + expect( DirectoryExists( "" ) ).toBeFalse(); + }); + + }); + + // DirectoryCreate - throws because current directory already exists + describe( "DirectoryCreate with empty path throws (already exists)", function() { + + it( title="DirectoryCreate() with empty path throws (current dir already exists)", body=function( currentSpec ) { + expect( function() { + DirectoryCreate( "" ); + }).toThrow(); + }); + + }); + + }); + } + +} diff --git a/test/tickets/LDEV5942.cfc b/test/tickets/LDEV5942.cfc new file mode 100644 index 0000000000..d74de36a86 --- /dev/null +++ b/test/tickets/LDEV5942.cfc @@ -0,0 +1,81 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="session" { + + function run( testResults, testBox ) { + describe( "LDEV-5942: sessionRotate() should rotate JSESSIONID for J2EE sessions", function() { + + it( title="sessionRotate() rotates JSESSIONID for J2EE sessions", skip=isJsr223(), body=function( currentSpec ) { + var result = test( template: "/jee-session/rotate.cfm" ); + var data = deserializeJSON( result.filecontent ); + expect( data.success ).toBeTrue( data.message ?: "no message" ); + expect( data.oldSessionId ).notToBe( data.newSessionId, "JSESSIONID should change after sessionRotate()" ); + }); + + it( title="sessionRotate() preserves session data for J2EE sessions", skip=isJsr223(), body=function( currentSpec ) { + var result = test( template: "/jee-session/rotate-with-data.cfm" ); + var data = deserializeJSON( result.filecontent ); + expect( data.success ).toBeTrue( data.message ?: "no message" ); + expect( data.oldSessionId ).notToBe( data.newSessionId, "JSESSIONID should change after sessionRotate()" ); + expect( data.dataPreserved ).toBeTrue( "Session data should be preserved after rotation" ); + }); + + }); + + describe( "LDEV-3248: sessionInvalidate() should invalidate JSESSIONID for J2EE sessions", function() { + + it( title="sessionInvalidate() invalidates JSESSIONID for J2EE sessions", skip=isJsr223(), body=function( currentSpec ) { + var result = test( template: "/jee-session/invalidate.cfm" ); + var data = deserializeJSON( result.filecontent ); + expect( data.success ).toBeTrue( data.message ?: "no message" ); + expect( data.sessionInvalidated ).toBeTrue( "HttpSession should be invalidated after sessionInvalidate()" ); + }); + + }); + } + + private function isJsr223() { + // Skip when running via script-runner (JSR-223) as J2EE sessions require a real servlet container + return ( cgi.request_url eq "http://localhost/index.cfm" ); + } + + private function test( template, args={} ) { + if ( isJsr223() ) { + // Running via script-runner, use internalRequest (but J2EE tests will be skipped) + var uri = createURI( "LDEV5942" ); + var result = _InternalRequest( + template: uri & arguments.template + ); + return result; + } + else { + // Running via Tomcat, use cfhttp which supports real J2EE sessions + var hostIdx = find( cgi.script_name, cgi.request_url ); + if ( hostIdx gt 0 ) { + var host = left( cgi.request_url, hostIdx - 1 ); + var webUrl = host & "/test/tickets/LDEV5942" & arguments.template; + } + else { + throw "failed to extract host [#hostIdx#] from cgi [#cgi.script_name#], [#cgi.request_url#]"; + } + var httpResult = ""; + http method="get" url="#webUrl#" result="httpResult" { + structEach( arguments.args, function( k, v ) { + httpparam name="#k#" value="#v#" type="url"; + }); + } + + return httpResult; + } + } + + private function dumpResult( r ) { + systemOutput( "", true ); + systemOutput( "Result: " & serializeJson( r ), true ); + systemOutput( "", true ); + } + + private string function createURI( string calledName ) { + var baseURI = "/test/#listLast( getDirectoryFromPath( getCurrentTemplatePath() ), "\/" )#/"; + return baseURI & calledName; + } + +} diff --git a/test/tickets/LDEV5942/jee-session/Application.cfc b/test/tickets/LDEV5942/jee-session/Application.cfc new file mode 100644 index 0000000000..eb2572d6b3 --- /dev/null +++ b/test/tickets/LDEV5942/jee-session/Application.cfc @@ -0,0 +1,9 @@ +component { + this.name = "ldev5942_jee_session_rotate"; + this.sessionManagement = true; + this.sessionStorage = "memory"; + this.sessionTimeout = createTimeSpan( 0, 0, 0, 30 ); + this.setClientCookies = true; + this.applicationTimeout = createTimeSpan( 0, 0, 1, 0 ); + this.sessionType = "jee"; +} diff --git a/test/tickets/LDEV5942/jee-session/invalidate.cfm b/test/tickets/LDEV5942/jee-session/invalidate.cfm new file mode 100644 index 0000000000..0639c29c91 --- /dev/null +++ b/test/tickets/LDEV5942/jee-session/invalidate.cfm @@ -0,0 +1,34 @@ + + result = { + success: true, + message: "" + }; + + try { + // Get the HttpSession before invalidation + httpSession = getPageContext().getSession(); + result.oldSessionId = httpSession.getId(); + + // Invalidate the session + sessionInvalidate(); + + // Try to access the old HttpSession - it should be invalidated + try { + // This should throw an exception since the session is invalidated + httpSession.getAttribute( "test" ); + result.sessionInvalidated = false; + result.message = "HttpSession was not invalidated - getAttribute() did not throw"; + } + catch ( any e ) { + // Expected - session is invalidated + result.sessionInvalidated = true; + } + } + catch ( any e ) { + result.success = false; + result.message = e.message; + } + + content type="application/json"; + echo( serializeJSON( result ) ); + diff --git a/test/tickets/LDEV5942/jee-session/rotate-with-data.cfm b/test/tickets/LDEV5942/jee-session/rotate-with-data.cfm new file mode 100644 index 0000000000..520b4d6aa2 --- /dev/null +++ b/test/tickets/LDEV5942/jee-session/rotate-with-data.cfm @@ -0,0 +1,38 @@ + + result = { + success: true, + message: "" + }; + + try { + // Set some session data before rotation + session.testValue = "ldev5942_test_data"; + session.testNumber = 42; + + // Get the JSESSIONID before rotation + httpSession = getPageContext().getSession(); + result.oldSessionId = httpSession.getId(); + + // Rotate the session + sessionRotate(); + + // Get the JSESSIONID after rotation + httpSession = getPageContext().getSession(); + result.newSessionId = httpSession.getId(); + + // Check if session data was preserved + result.dataPreserved = ( + structKeyExists( session, "testValue" ) + && session.testValue eq "ldev5942_test_data" + && structKeyExists( session, "testNumber" ) + && session.testNumber eq 42 + ); + } + catch ( any e ) { + result.success = false; + result.message = e.message; + } + + content type="application/json"; + echo( serializeJSON( result ) ); + diff --git a/test/tickets/LDEV5942/jee-session/rotate.cfm b/test/tickets/LDEV5942/jee-session/rotate.cfm new file mode 100644 index 0000000000..0b50781c90 --- /dev/null +++ b/test/tickets/LDEV5942/jee-session/rotate.cfm @@ -0,0 +1,26 @@ + + result = { + success: true, + message: "" + }; + + try { + // Get the JSESSIONID before rotation + httpSession = getPageContext().getSession(); + result.oldSessionId = httpSession.getId(); + + // Rotate the session + sessionRotate(); + + // Get the JSESSIONID after rotation + httpSession = getPageContext().getSession(); + result.newSessionId = httpSession.getId(); + } + catch ( any e ) { + result.success = false; + result.message = e.message; + } + + content type="application/json"; + echo( serializeJSON( result ) ); + diff --git a/test/tickets/LDEV5943.cfc b/test/tickets/LDEV5943.cfc new file mode 100644 index 0000000000..8894e9ce85 --- /dev/null +++ b/test/tickets/LDEV5943.cfc @@ -0,0 +1,50 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="config" { + + function beforeAll() { + variables.admin = new org.lucee.cfml.Administrator( "server", request.ServerAdminPassword ); + } + + function run( testResults, testBox ) { + describe( "LDEV-5943 - inspectTemplate setting should persist", function() { + + it( "should read inspectTemplate from config not use hardcoded default", function() { + // Get the current setting + var settings = admin.getPerformanceSettings(); + var originalValue = settings.inspectTemplate; + + // Change it to something different + var newValue = ( originalValue == "once" ) ? "never" : "once"; + admin.updatePerformanceSettings( inspectTemplate=newValue ); + + // Verify the change took effect + var updatedSettings = admin.getPerformanceSettings(); + expect( updatedSettings.inspectTemplate ).toBe( newValue ); + + // Restore original value + admin.updatePerformanceSettings( inspectTemplate=originalValue ); + + // Verify restoration + var restoredSettings = admin.getPerformanceSettings(); + expect( restoredSettings.inspectTemplate ).toBe( originalValue ); + }); + + it( "should accept all valid inspectTemplate values", function() { + var settings = admin.getPerformanceSettings(); + var originalValue = settings.inspectTemplate; + + var validValues = [ "auto", "never", "once", "always" ]; + + for ( var value in validValues ) { + admin.updatePerformanceSettings( inspectTemplate=value ); + var updatedSettings = admin.getPerformanceSettings(); + expect( updatedSettings.inspectTemplate ).toBe( value, "Failed for value: #value#" ); + } + + // Restore original value + admin.updatePerformanceSettings( inspectTemplate=originalValue ); + }); + + }); + } + +} diff --git a/test/tickets/LDEV5954.cfc b/test/tickets/LDEV5954.cfc new file mode 100644 index 0000000000..b7d41e936d --- /dev/null +++ b/test/tickets/LDEV5954.cfc @@ -0,0 +1,127 @@ +component extends="org.lucee.cfml.test.LuceeTestCase" labels="query" { + + function beforeAll() { + variables.testData = Query( + id: [ 1, 2, 3, 4, 5 ], + value: [ 'a', 'b', 'c', 'd', 'e' ] + ); + } + + function run( testResults, testBox ) { + + describe( 'query.addParam with list=true', function() { + + describe( 'using array as value', function() { + + it( 'should work with numeric array', function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( name: 'ids', value: [ 1, 3, 5 ], sqltype: 'integer', list: true ); + + var result = q.execute( sql = " + SELECT id, value + FROM testData + WHERE id IN ( :ids ) + " ).getResult(); + + expect( result.RecordCount ).toBe( 3 ); + expect( valueList( result.id ) ).toBe( '1,3,5' ); + }); + + it( 'should work with string array', function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( name: 'vals', value: [ 'a', 'c', 'e' ], sqltype: 'varchar', list: true ); + + var result = q.execute( sql = " + SELECT id, value + FROM testData + WHERE value IN ( :vals ) + " ).getResult(); + + expect( result.RecordCount ).toBe( 3 ); + expect( valueList( result.value ) ).toBe( 'a,c,e' ); + }); + + it( 'should work with positional array param', function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( value: [ 2, 4 ], sqltype: 'integer', list: true ); + + var result = q.execute( sql = " + SELECT id, value + FROM testData + WHERE id IN ( ? ) + " ).getResult(); + + expect( result.RecordCount ).toBe( 2 ); + expect( valueList( result.id ) ).toBe( '2,4' ); + }); + + }); + + describe( 'using string list as value (existing behaviour)', function() { + + it( 'should work with comma-separated string', function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( name: 'ids', value: '1,3,5', sqltype: 'integer', list: true ); + + var result = q.execute( sql = " + SELECT id, value + FROM testData + WHERE id IN ( :ids ) + " ).getResult(); + + expect( result.RecordCount ).toBe( 3 ); + }); + + it( 'should throw on empty string value', function() { + expect( function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( name: 'ids', value: '', sqltype: 'integer', list: true ); + }).toThrow(); + }); + + it( 'should throw on whitespace-only string value', function() { + expect( function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( name: 'ids', value: ' ', sqltype: 'integer', list: true ); + }).toThrow(); + }); + + }); + + describe( 'edge cases with arrays', function() { + + it( 'should throw on empty array value', function() { + expect( function() { + var q = new Query( + dbtype = 'query', + testData = variables.testData + ); + q.addParam( name: 'ids', value: [], sqltype: 'integer', list: true ); + }).toThrow(); + }); + + }); + + }); + + } + +}