Skip to content

Commit f72dae4

Browse files
committed
LDEV-6340 stop logging "failed to resolve parent entity" for mappedSuperClass parents
1 parent 401488f commit f72dae4

28 files changed

Lines changed: 501 additions & 10 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 5.6.15.17
4+
5+
- [LDEV-6340](https://luceeserver.atlassian.net/browse/LDEV-6340) — Entities extending a `mappedSuperClass="true"` parent no longer log `failed to resolve parent entity` warnings on every SessionFactory build. Mapped superclasses aren't entities, so "parent not registered" is the expected state, not an error
6+
37
## 5.6.15.16
48

59
- [LDEV-1697](https://luceeserver.atlassian.net/browse/LDEV-1697) — Overlapping `ormSettings.cfclocation` entries (parent + child directory) registered the same CFC twice and triggered an ambiguity error. Now deduped by canonical file path, matching ACF behaviour

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ Install via Lucee Admin, or pin in your environment:
88

99
```bash
1010
# Lucee 7.0+ (Maven coordinates, auto-updates to latest snapshot)
11-
LUCEE_EXTENSIONS=org.lucee:hibernate-extension:5.6.15.16-SNAPSHOT
11+
LUCEE_EXTENSIONS=org.lucee:hibernate-extension:5.6.15.17-SNAPSHOT
1212

1313
# Lucee 6.2 (extension GUID, pinned version)
14-
LUCEE_EXTENSIONS=FAD1E8CB-4F45-4184-86359145767C29DE;version=5.6.15.16-SNAPSHOT
14+
LUCEE_EXTENSIONS=FAD1E8CB-4F45-4184-86359145767C29DE;version=5.6.15.17-SNAPSHOT
1515
```
1616

1717
## History

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<modelVersion>4.0.0</modelVersion>
55
<groupId>org.lucee</groupId>
66
<artifactId>hibernate-extension</artifactId>
7-
<version>5.6.15.16-SNAPSHOT</version>
7+
<version>5.6.15.17-SNAPSHOT</version>
88
<packaging>jar</packaging>
99
<name>Hibernate Extension</name>
1010

source/java/src/org/lucee/extension/orm/hibernate/HibernateSessionFactory.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,15 +318,14 @@ private static String assembleMappingForCFC(String key, CFCInfo value, Set<Strin
318318
StringBuilder mappings = new StringBuilder();
319319
String ext = value.getCFC().getExtends();
320320
if (!Util.isEmpty(ext)) {
321-
try {
322-
Component parent = data.getEntityByCFCName(ext, false);
321+
// LDEV-6340: mappedSuperClass / persistent="false" / non-CFC parents
322+
// legitimately aren't registered as entities — their properties get
323+
// inlined by HBMCreator.loadForeignCFC during HBM generation, so
324+
// there's no parent <class> to prepend here. Don't throw, don't log.
325+
Component parent = data.getEntityByCFCName(ext, false, null);
326+
if (parent != null) {
323327
ext = HibernateCaster.getEntityName(parent);
324328
}
325-
catch (Exception e) {
326-
Log log = CommonUtil.getORMLog();
327-
if ( log != null ) log.log( Log.LEVEL_WARN, "hibernate",
328-
"failed to resolve parent entity [" + ext + "] for entity [" + HibernateCaster.getEntityName( value.getCFC() ) + "]", e );
329-
}
330329

331330
ext = HibernateUtil.id(CommonUtil.last(ext, ".").trim());
332331
if (!done.contains(ext)) {

source/java/src/org/lucee/extension/orm/hibernate/SessionFactoryData.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,53 @@ public Component getEntityByEntityName(String entityName, boolean unique) throws
161161
throw ExceptionUtil.createException((ORMSession) null, null, "Entity [" + entityName + "] does not exist", "");
162162
}
163163

164+
/**
165+
* Non-throwing overload — returns {@code defaultValue} when the CFC isn't
166+
* registered as an entity (e.g. {@code mappedSuperClass="true"} or
167+
* {@code persistent="false"} parents). Use this when "parent isn't an
168+
* entity" is a legitimate state, not an error (LDEV-6340).
169+
*/
170+
public Component getEntityByCFCName(String cfcName, boolean unique, Component defaultValue) {
171+
String name = cfcName;
172+
int pointIndex = cfcName.lastIndexOf('.');
173+
if (pointIndex != -1) {
174+
name = cfcName.substring(pointIndex + 1);
175+
}
176+
else cfcName = null;
177+
178+
Component cfc;
179+
180+
if (hasTempCFCs()) {
181+
Iterator<Component> it2 = tmpList.iterator();
182+
while (it2.hasNext()) {
183+
cfc = it2.next();
184+
if (HibernateUtil.isEntity(ormConf, cfc, cfcName, name))
185+
return unique ? (Component) cfc.duplicate(false) : cfc;
186+
}
187+
}
188+
else {
189+
Iterator<Map<String, CFCInfo>> it = cfcs.values().iterator();
190+
Map<String, CFCInfo> _cfcs;
191+
while (it.hasNext()) {
192+
_cfcs = it.next();
193+
Iterator<CFCInfo> _it = _cfcs.values().iterator();
194+
while (_it.hasNext()) {
195+
cfc = _it.next().getCFC();
196+
if (HibernateUtil.isEntity(ormConf, cfc, cfcName, name))
197+
return unique ? (Component) cfc.duplicate(false) : cfc;
198+
}
199+
}
200+
}
201+
202+
CFCInfo info = getCFC(name, null);
203+
if (info != null) {
204+
cfc = info.getCFC();
205+
return unique ? (Component) cfc.duplicate(false) : cfc;
206+
}
207+
208+
return defaultValue;
209+
}
210+
164211
public Component getEntityByCFCName(String cfcName, boolean unique) throws PageException {
165212
String name = cfcName;
166213
int pointIndex = cfcName.lastIndexOf('.');

tests/mapping/mappedSuperClass.cfc

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
component extends="org.lucee.cfml.test.LuceeTestCase" labels="orm" {
2+
3+
function beforeAll() {
4+
// Need WARN-or-lower for the LDEV-6340 bug message to land in orm.log;
5+
// default server level may be ERROR. logging.cfc uses the same pattern.
6+
configImport( type: "server", password: request.SERVERADMINPASSWORD, data: {
7+
loggers: {
8+
orm: {
9+
appender : "resource",
10+
appenderArguments: { path: "{lucee-config}/logs/orm.log" },
11+
level : "trace",
12+
layout : "classic"
13+
}
14+
}
15+
});
16+
}
17+
18+
function run( testResults, testBox ) {
19+
20+
describe( "mappedSuperClass — LDEV-6340 log noise", function() {
21+
22+
it( "User extends mappedSuperClass BaseEntity — entity ops work and log stays clean", function() {
23+
var appId = createUUID();
24+
var offsetBefore = getLogLength();
25+
26+
var result = _InternalRequest(
27+
template: "#uri( "basic" )#/index.cfm",
28+
url : { appId: appId }
29+
);
30+
expect( trim( result.filecontent ) ).toBe( "ok" );
31+
32+
var logContent = getLogContentSince( offsetBefore );
33+
systemOutput( "LDEV-6340 LOG: [#logContent#]", true );
34+
35+
expect( logContent ).notToInclude( "failed to resolve parent entity" );
36+
expect( logContent ).notToInclude( "Entity [BaseEntity] does not exist" );
37+
});
38+
39+
});
40+
41+
describe( "mappedSuperClass — property inheritance shapes", function() {
42+
43+
it( "parent declares @Id — child inherits the id field", function() {
44+
var result = _InternalRequest(
45+
template: "#uri( "parentId" )#/index.cfm",
46+
url : { appId: createUUID() }
47+
);
48+
expect( trim( result.filecontent ) ).toBe( "ok" );
49+
});
50+
51+
it( "multi-level chain — child inherits properties from grand-mappedSuperClass", function() {
52+
var result = _InternalRequest(
53+
template: "#uri( "multiLevel" )#/index.cfm",
54+
url : { appId: createUUID() }
55+
);
56+
expect( trim( result.filecontent ) ).toBe( "ok" );
57+
});
58+
59+
it( "mappedSuperClass + real entity TPH inheritance — Car gets mappedSuperClass props two levels up", function() {
60+
var result = _InternalRequest(
61+
template: "#uri( "mixedInheritance" )#/index.cfm",
62+
url : { appId: createUUID() }
63+
);
64+
expect( trim( result.filecontent ) ).toBe( "ok" );
65+
});
66+
67+
it( "child redeclares parent property with different column + length — child wins", function() {
68+
var result = _InternalRequest(
69+
template: "#uri( "attrOverride" )#/index.cfm",
70+
url : { appId: createUUID() }
71+
);
72+
expect( trim( result.filecontent ) ).toBe( "ok" );
73+
});
74+
75+
});
76+
77+
}
78+
79+
private string function uri( required string scenario ) {
80+
return getDirectoryFromPath( contractPath( getCurrentTemplatePath() ) ) & "mappedSuperClass/" & arguments.scenario;
81+
}
82+
83+
private numeric function getLogLength() {
84+
var logFile = expandPath( "{lucee-config}/logs/orm.log" );
85+
if ( !fileExists( logFile ) ) return 0;
86+
return len( fileRead( logFile ) );
87+
}
88+
89+
private string function getLogContentSince( required numeric offset ) {
90+
var logFile = expandPath( "{lucee-config}/logs/orm.log" );
91+
if ( !fileExists( logFile ) ) return "";
92+
var content = fileRead( logFile );
93+
if ( len( content ) <= arguments.offset ) return "";
94+
return content.mid( arguments.offset + 1, len( content ) );
95+
}
96+
97+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
component {
2+
3+
this.name = "orm-msuper-attrOverride-#url.appId ?: hash( getCurrentTemplatePath() )#";
4+
this.datasource = server.getDatasource( "h2", server._getTempDir( "orm-msuper-attrOverride" ) );
5+
this.ormEnabled = true;
6+
this.ormSettings = {
7+
dbcreate: "dropcreate",
8+
cfclocation: [ getDirectoryFromPath( getCurrentTemplatePath() ) ]
9+
};
10+
11+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Child entity redeclares `title` with different `column` + `length`.
2+
// JPA's @AttributeOverride equivalent in CFML — same property name, new attrs.
3+
component
4+
persistent="true"
5+
extends ="Named"
6+
accessors ="true"
7+
table ="msuper_customer"
8+
{
9+
10+
property name="id" fieldtype="id" ormtype="string";
11+
property
12+
name ="title"
13+
ormtype="string"
14+
length ="200"
15+
column ="customer_title";
16+
17+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// mappedSuperClass with a default column name and short length.
2+
// Child overrides both via redeclaration.
3+
component
4+
mappedSuperClass="true"
5+
accessors ="true"
6+
{
7+
8+
property
9+
name ="title"
10+
ormtype="string"
11+
length ="50"
12+
column ="default_title";
13+
14+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<cfscript>
2+
// Child redeclared `title` with column="customer_title" and length=200.
3+
// Parent's was column="default_title" length=50. Child's wins.
4+
5+
// Verify the schema honoured the override: column must be `customer_title`, not `default_title`.
6+
import org.hibernate.Session;
7+
8+
ormFlush();
9+
10+
// Probe the H2 schema. column names are uppercased by default.
11+
schema = queryExecute(
12+
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE UPPER(TABLE_NAME) = 'MSUPER_CUSTOMER' ORDER BY COLUMN_NAME"
13+
);
14+
cols = [];
15+
for ( row in schema ) {
16+
cols.append( lCase( row.COLUMN_NAME ) );
17+
}
18+
if ( !cols.find( "customer_title" ) )
19+
throw( message="schema missing override column [customer_title], got #serializeJSON( cols )#" );
20+
if ( cols.find( "default_title" ) )
21+
throw( message="schema still has parent's column [default_title] — override didn't take" );
22+
23+
// Round-trip a value longer than the parent's 50-char limit (proves length override too).
24+
longTitle = repeatString( "mango ", 20 ); // 120 chars
25+
c = entityNew( "Customer" );
26+
c.setId( createUUID() );
27+
c.setTitle( longTitle );
28+
entitySave( c );
29+
ormFlush();
30+
ormClearSession();
31+
32+
loaded = entityLoadByPK( "Customer", c.getId() );
33+
if ( isNull( loaded ) )
34+
throw( message="Customer not loaded after save" );
35+
if ( loaded.getTitle() != longTitle )
36+
throw( message="title round-trip mismatch — expected [#longTitle#], got [#loaded.getTitle()#]" );
37+
38+
echo( "ok" );
39+
</cfscript>

0 commit comments

Comments
 (0)