Skip to content

Commit 59c5b54

Browse files
authored
Merge pull request #2703 from zspitzer/LDEV-3335-fix
LDEV-3335 store component properties per class
2 parents 80b1f5a + 6e26061 commit 59c5b54

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1746
-425
lines changed

core/src/main/java/lucee/runtime/ComponentImpl.java

Lines changed: 86 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ public final class ComponentImpl extends StructSupport implements Externalizable
145145
ComponentImpl top = this;
146146
ComponentImpl base;
147147
private PageSource pageSource;
148-
private ComponentPageRef cpRef;
148+
private ComponentPageImpl cp;
149149
private ComponentScope scope;
150150

151151
// for all the same
@@ -179,7 +179,8 @@ public final class ComponentImpl extends StructSupport implements Externalizable
179179
/**
180180
* Constructor of the Component, USED ONLY FOR DESERIALIZE
181181
*/
182-
public ComponentImpl() {}
182+
public ComponentImpl() {
183+
}
183184

184185
/**
185186
* constructor of the class
@@ -207,7 +208,7 @@ public ComponentImpl(ComponentPageImpl componentPage, Boolean output, boolean _s
207208
this.properties = new ComponentProperties(componentPage.getComponentName(), dspName, extend.trim(), implement, hint, output, callPath + appendix, realPath,
208209
componentPage.getSubname(), _synchronized, null, persistent, accessors, modifier, meta);
209210

210-
this.cpRef = new ComponentPageRef(componentPage);
211+
this.cp = componentPage;
211212
this.pageSource = componentPage.getPageSource();
212213
this.importDefintions = componentPage.getImportDefintions();
213214
// if(modifier!=0)
@@ -217,17 +218,11 @@ public ComponentImpl(ComponentPageImpl componentPage, Boolean output, boolean _s
217218
}
218219

219220
public JavaSettings getJavaSettings(PageContext pc) throws IOException {
220-
ComponentPageImpl cp;
221-
try {
222-
cp = this.cpRef.get(pc);
223-
}
224-
catch (PageException e) {
225-
throw ExceptionUtil.toIOException(e);
226-
}
227-
boolean is = cp.isJavaSettingsInitialized();
221+
222+
boolean is = this.cp.isJavaSettingsInitialized();
228223
if (!is) {
229224
synchronized (cp) {
230-
is = cp.isJavaSettingsInitialized();
225+
is = this.cp.isJavaSettingsInitialized();
231226
if (!is) {
232227
boolean mergeConfig = false;
233228
JavaSettings js = null;
@@ -270,18 +265,12 @@ public JavaSettings getJavaSettings(PageContext pc) throws IOException {
270265
// current
271266
js = JavaSettingsImpl.merge(pc.getConfig(), js, JavaSettingsImpl.readJavaSettings(pc, properties.meta));
272267
if (mergeConfig) js = JavaSettingsImpl.merge(pc.getConfig(), ((ConfigPro) pc.getConfig()).getJavaSettings(), js);
273-
;
274268

275-
return cp.setJavaSettings(js);
269+
return this.cp.setJavaSettings(js);
276270
}
277271
}
278272
}
279-
return cp.getJavaSettings();
280-
}
281-
282-
@Override
283-
public final int hashCode() {
284-
return java.util.Objects.hash(base, _data, pageSource);
273+
return this.cp.getJavaSettings();
285274
}
286275

287276
public boolean hasJavaSettings(PageContext pc) {
@@ -311,7 +300,7 @@ public ComponentImpl _duplicate(boolean deepCopy, boolean isTop) {
311300
try {
312301
// attributes
313302
trg.pageSource = pageSource;
314-
trg.cpRef = cpRef;
303+
trg.cp = cp;
315304
// trg._triggerDataMember=_triggerDataMember;
316305
trg.useShadow = useShadow;
317306
trg._static = _static;
@@ -485,7 +474,7 @@ public void init(PageContext pageContext, ComponentPageImpl componentPage, boole
485474

486475
if (base != null) {
487476
this.dataMemberDefaultAccess = base.dataMemberDefaultAccess;
488-
this._static = new StaticScope(base._static, this, cpRef, dataMemberDefaultAccess);
477+
this._static = new StaticScope(base._static, this, new ComponentPageRef(componentPage), dataMemberDefaultAccess);
489478
this.absFin = base.absFin;
490479
_data = base._data;
491480
_udfs = isRestEnabled ? new LinkedHashMap<Key, UDF>(base._udfs) : new HashMap<Key, UDF>(base._udfs);
@@ -496,7 +485,7 @@ public void init(PageContext pageContext, ComponentPageImpl componentPage, boole
496485
}
497486
else {
498487
this.dataMemberDefaultAccess = pageContext.getConfig().getComponentDataMemberDefaultAccess();
499-
this._static = new StaticScope(null, this, cpRef, dataMemberDefaultAccess);
488+
this._static = new StaticScope(null, this, new ComponentPageRef(componentPage), dataMemberDefaultAccess);
500489
// TODO get per CFC setting
501490
// this._triggerDataMember=pageContext.getConfig().getTriggerComponentDataMember();
502491
_udfs = isRestEnabled ? new LinkedHashMap<Key, UDF>() : new HashMap<Key, UDF>();
@@ -511,7 +500,7 @@ public void init(PageContext pageContext, ComponentPageImpl componentPage, boole
511500

512501
long indexBase = 0;
513502
if (base != null) {
514-
indexBase = base.cpRef.get(pageContext).getStaticStruct().index();
503+
indexBase = base.cp.getStaticStruct().index();
515504
}
516505

517506
// scope
@@ -759,8 +748,7 @@ else if (_namedArgs != null) {
759748
return Reflector.componentToClass(pc, this).getClass();
760749
}
761750

762-
// When calling via super, use public access for error message since super calls should access
763-
// inherited methods
751+
// When calling via super, use public access for error message since super calls should access inherited methods
764752
int errorAccess = superAccess ? ACCESS_PUBLIC : access;
765753
if (member == null) throw ComponentUtil.notFunction(this, KeyImpl.init(name), null, errorAccess);
766754
throw ComponentUtil.notFunction(this, KeyImpl.init(name), member.getValue(), errorAccess);
@@ -990,21 +978,21 @@ private Collection.Key[] keysPreservingOrder(int access) {
990978
if (_udfs.isEmpty() && _data.isEmpty()) {
991979
return new Collection.Key[0];
992980
}
993-
981+
994982
List<Key> orderedKeys = new ArrayList<Key>(_udfs.size() + _data.size());
995-
996-
for (Entry<Key, UDF> entry: _udfs.entrySet()) {
983+
984+
for (Entry<Key, UDF> entry : _udfs.entrySet()) {
997985
if (entry.getValue().getAccess() <= access) {
998986
orderedKeys.add(entry.getKey());
999987
}
1000988
}
1001-
for (Entry<Key, Member> entry: _data.entrySet()) {
989+
for (Entry<Key, Member> entry : _data.entrySet()) {
1002990
Member member = entry.getValue();
1003991
if (member.getAccess() <= access && !(member instanceof UDF)) {
1004992
orderedKeys.add(entry.getKey());
1005993
}
1006994
}
1007-
995+
1008996
return orderedKeys.toArray(new Collection.Key[orderedKeys.size()]);
1009997
}
1010998

@@ -1194,7 +1182,8 @@ public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties
11941182
try {
11951183
return DumpUtil.toDumpData(_call(pageContext, KeyConstants.__toDumpData, udf, null, new Object[0]), pageContext, maxlevel, dp);
11961184
}
1197-
catch (PageException e) {}
1185+
catch (PageException e) {
1186+
}
11981187
}
11991188
}
12001189
}
@@ -1392,8 +1381,8 @@ public PageSource _getPageSource() {
13921381
return pageSource;
13931382
}
13941383

1395-
public ComponentPageImpl _getComponentPageImpl(PageContext pc) throws PageException {
1396-
return cpRef.get(pc);
1384+
public ComponentPageImpl _getComponentPageImpl() {
1385+
return cp;
13971386
}
13981387

13991388
public ImportDefintion[] _getImportDefintions() {
@@ -1728,7 +1717,7 @@ protected static Struct getMetaData(int access, PageContext pc, ComponentImpl co
17281717
if (!StringUtil.isEmpty(displayname)) sct.set(KeyConstants._displayname, displayname);
17291718

17301719
sct.set(KeyConstants._persistent, comp.properties.persistent);
1731-
// sct.set(KeyConstants._hashCode, comp.hashCode());
1720+
sct.set(KeyConstants._hashCode, comp.hashCode());
17321721
sct.set(KeyConstants._accessors, comp.properties.accessors);
17331722
sct.set(KeyConstants._synchronized, comp.properties._synchronized);
17341723
sct.set(KeyConstants._inline, comp.properties.inline);
@@ -1988,14 +1977,9 @@ private Object _set(PageContext pc, Collection.Key key, Object value, int access
19881977
"enable [trigger data member] in administrator to also invoke getters and setters");
19891978
if (existing != null) {
19901979
if (existing.getModifier() == Member.MODIFIER_FINAL) {
1991-
ComponentPageImpl tmp = cpRef.get(pc, null);
1992-
String componentName = tmp != null ? tmp.getComponentName() : "";
1993-
1994-
tmp = base.cpRef.get(pc, null);
1995-
String baseComponentName = tmp != null ? tmp.getComponentName() : "";
1996-
1997-
throw new ExpressionException("Attempt to modify a 'final' member [" + key + "] within the 'this' scope of the component [" + componentName
1998-
+ "]. This member is declared as 'final' in the base component [" + baseComponentName + "] or a component extended by it, and cannot be overridden.");
1980+
throw new ExpressionException("Attempt to modify a 'final' member [" + key + "] within the 'this' scope of the component [" + cp.getComponentName()
1981+
+ "]. This member is declared as 'final' in the base component [" + base.cp.getComponentName()
1982+
+ "] or a component extended by it, and cannot be overridden.");
19991983
}
20001984

20011985
}
@@ -2404,16 +2388,65 @@ public boolean isAccessors() {
24042388

24052389
@Override
24062390
public void setProperty(Property property) throws PageException {
2407-
top.properties.properties.put(StringUtil.toLowerCase(property.getName()), property);
2408-
// FUTURE getDefaultAsObject was added in Beta pahse of Lucee 7, so we keep the checkcast in place
2409-
if (((PropertyImpl) property).getDefaultAsObject() != null) scope.setEL(KeyImpl.init(property.getName()), ((PropertyImpl) property).getDefaultAsObject());
2410-
if (top.properties.persistent || top.properties.accessors) {
2411-
PropertyFactory.createPropertyUDFs(this, property);
2391+
// LDEV-3335: Handle property inheritance and overrides
2392+
PropertyImpl propImpl = (PropertyImpl) property;
2393+
PageSource propOwnerPS = propImpl.getOwnerPageSource();
2394+
2395+
// Check if this property is overriding an existing property (same name already registered)
2396+
String propNameLower = StringUtil.toLowerCase(propImpl.getName());
2397+
PropertyImpl existing = (PropertyImpl) top.properties.properties.get(propNameLower);
2398+
boolean isOverride = existing != null && propOwnerPS == null;
2399+
2400+
boolean isInherited = propOwnerPS != null && !propOwnerPS.equals(getPageSource());
2401+
2402+
if (isInherited && !isOverride) {
2403+
// Property is from a parent component - duplicate it to avoid sharing/mutation
2404+
propImpl = (PropertyImpl) propImpl.duplicate(false);
2405+
}
2406+
else if (propOwnerPS == null) {
2407+
// LDEV-3335: Property doesn't have owner set yet - set it to this component
2408+
// This happens for properties from __staticProperties that haven't been initialized
2409+
propImpl.setOwnerName(getAbsName(), getPageSource());
2410+
}
2411+
2412+
top.properties.properties.put(propNameLower, propImpl);
2413+
if (propImpl.getDefaultAsObject() != null) {
2414+
scope.setEL(propImpl.getNameAsKey(), propImpl.getDefaultAsObject());
2415+
}
2416+
// Create accessor UDFs if:
2417+
// 1. Component has accessors enabled, OR
2418+
// 2. Component is persistent, OR
2419+
// 3. Property is inherited and has accessors (need to create new UDFs with duplicated property)
2420+
// 4. Property is an override with accessors (child re-declaring parent property)
2421+
if (top.properties.persistent || top.properties.accessors || (isInherited && (propImpl.getGetter() || propImpl.getSetter()))
2422+
|| (isOverride && (propImpl.getGetter() || propImpl.getSetter()))) {
2423+
PropertyFactory.createPropertyUDFs(this, propImpl);
24122424
}
24132425
}
24142426

24152427
private void initProperties() throws PageException {
24162428
top.properties.properties = new LinkedHashMap<String, Property>();
2429+
// Call generated stub to initialize properties from static registry (zero overhead!)
2430+
if (top.cp != null) {
2431+
top.cp.initPropertiesStub(this);
2432+
}
2433+
2434+
// LDEV-3335: Add static flyweight accessor UDFs to _data and scope
2435+
Map<Key, UDF> staticAccessorUDFs = top.cp != null ? top.cp.getStaticAccessorUDFs() : null;
2436+
if (staticAccessorUDFs != null && !staticAccessorUDFs.isEmpty()) {
2437+
Iterator<Map.Entry<Key, UDF>> it = staticAccessorUDFs.entrySet().iterator();
2438+
while (it.hasNext()) {
2439+
Map.Entry<Key, UDF> entry = it.next();
2440+
Key key = entry.getKey();
2441+
UDF udf = entry.getValue();
2442+
2443+
// Only add if not manually overridden
2444+
if (!_data.containsKey(key)) {
2445+
_data.put(key, udf);
2446+
scope.put(key, udf);
2447+
}
2448+
}
2449+
}
24172450

24182451
// MappedSuperClass
24192452
if (isPersistent() && !isBasePeristent() && top.base != null && top.base.properties.properties != null && top.base.properties.meta != null) {
@@ -2424,8 +2457,11 @@ private void initProperties() throws PageException {
24242457
while (it.hasNext()) {
24252458
p = it.next().getValue();
24262459
if (p.isPeristent()) {
2427-
2428-
setProperty(p);
2460+
// LDEV-87: Don't override properties that child component has already declared
2461+
String propNameLower = StringUtil.toLowerCase(p.getName());
2462+
if (!top.properties.properties.containsKey(propNameLower)) {
2463+
setProperty(p);
2464+
}
24292465
}
24302466
}
24312467
}

core/src/main/java/lucee/runtime/ComponentPageImpl.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@
2424
import java.nio.charset.Charset;
2525
import java.util.ArrayList;
2626
import java.util.Arrays;
27+
import java.util.Collections;
2728
import java.util.Iterator;
29+
import java.util.LinkedHashMap;
2830
import java.util.List;
31+
import java.util.Map;
2932
import java.util.Map.Entry;
3033

3134
import jakarta.servlet.http.HttpServletRequest;
@@ -103,6 +106,10 @@ public abstract class ComponentPageImpl extends ComponentPage {
103106

104107
public static final lucee.runtime.type.Collection.Key REMOTE_PERSISTENT_ID = KeyConstants._Id16hohohh;
105108

109+
// Note: Static property registry is now generated per-class in bytecode, not in base class
110+
// Each generated component class has its own __staticProperties field and __getStaticProperties() method
111+
// See PageImpl.writeOutStatic() for bytecode generation
112+
106113
private long lastCheck = -1;
107114

108115
private StaticScope staticScope;
@@ -1148,6 +1155,40 @@ public StaticStruct getStaticStruct() {
11481155
return new StaticStruct();
11491156
}
11501157

1158+
/**
1159+
* Returns the static properties map for this component class.
1160+
* Components with properties will override this method to return their static property registry.
1161+
* Default implementation returns null for components without properties.
1162+
*
1163+
* @return Map of property names to PropertyImpl instances, or null if no properties
1164+
*/
1165+
public Map<String, lucee.runtime.component.PropertyImpl> getStaticProperties() {
1166+
return null;
1167+
}
1168+
1169+
/**
1170+
* LDEV-3335: Returns the static flyweight accessor UDF map for this component class.
1171+
* Components with accessors will override this to return their static UDF registry.
1172+
* Default implementation returns null for components without accessor UDFs.
1173+
*
1174+
* @return Map of accessor names to UDF instances, or null if no accessor UDFs
1175+
*/
1176+
public Map<Key, UDF> getStaticAccessorUDFs() {
1177+
return null;
1178+
}
1179+
1180+
/**
1181+
* Initializes component properties from the static property registry.
1182+
* Components with properties will override this method to provide optimized property initialization.
1183+
* Default implementation does nothing (no-op for components without properties).
1184+
*
1185+
* @param impl The ComponentImpl instance to initialize properties for
1186+
* @throws PageException if property initialization fails
1187+
*/
1188+
public void initPropertiesStub(ComponentImpl impl) throws PageException {
1189+
// No-op for components without properties
1190+
}
1191+
11511192
public abstract void initComponent(PageContext pc, ComponentImpl c, boolean executeDefaultConstructor) throws PageException;
11521193

11531194
public void ckecked() {

core/src/main/java/lucee/runtime/ComponentProperties.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,22 +29,28 @@
2929
public final class ComponentProperties implements Serializable {
3030

3131
private static final Collection.Key WSDL_FILE = KeyConstants._wsdlfile;
32+
33+
// Reference fields (8 bytes each) - group together to minimize padding
3234
final String dspName;
3335
final String extend;
3436
final String hint;
35-
final Boolean output;
3637
final String callPath;
37-
final boolean realPath;
38-
final boolean _synchronized;
38+
final String implement;
39+
final String subName;
40+
final String name;
41+
final Boolean output;
3942
Class javaAccessClass;
4043
Map<String, Property> properties;
4144
Struct meta;
42-
final String implement;
45+
46+
// int field (4 bytes)
47+
final int modifier;
48+
49+
// Boolean fields (1 byte each) - group at end to minimize padding
50+
final boolean realPath;
51+
final boolean _synchronized;
4352
final boolean persistent;
4453
final boolean accessors;
45-
final int modifier;
46-
final String subName;
47-
final String name;
4854
public boolean inline;
4955

5056
public ComponentProperties(String name, String dspName, String extend, String implement, String hint, Boolean output, String callPath, boolean realPath, String subName,

core/src/main/java/lucee/runtime/component/ComponentLoader.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,8 @@ public static StaticScope getStaticScope(PageContext pc, PageSource loadingLocat
125125
Component c = ss.getComponent();
126126
while ((bc = (ComponentImpl) c.getBaseComponent()) != null) {
127127
ComponentPageImpl bcp = (ComponentPageImpl) ((PageSourceImpl) bc._getPageSource()).loadPage(pc, false, null);
128-
if (bcp.getStaticStruct() != null) {
128+
// bcp can be null during concurrent class initialization (race condition)
129+
if (bcp != null && bcp.getStaticStruct() != null) {
129130
long idx = bcp.getStaticStruct().index();
130131
if (idx == 0 || idx > index) {
131132
reload = true;

core/src/main/java/lucee/runtime/component/MetadataUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
public final class MetadataUtil {
3232

3333
public static Page getPageWhenMetaDataStillValid(PageContext pc, ComponentImpl comp, boolean ignoreCache) throws PageException {
34-
Page page = comp._getComponentPageImpl(pc);
34+
Page page = comp._getComponentPageImpl();
3535
if (page == null) page = getPage(pc, comp._getPageSource());
3636
if (ignoreCache) return page;
3737

0 commit comments

Comments
 (0)