Skip to content

Commit 0627bdb

Browse files
committed
Merge branch 'development'
2 parents d142cda + 23998e6 commit 0627bdb

File tree

7 files changed

+117
-33
lines changed

7 files changed

+117
-33
lines changed

.github/copilot-instructions.md

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,41 @@
11
# CBOrm Copilot Instructions
22

3-
CBOrm is a ColdBox module that enhances Hibernate ORM for CFML engines (BoxLang, Lucee, Adobe ColdFusion). It provides service layers, Active Record patterns, fluent criteria queries, and dynamic finders.
3+
CBOrm is a ColdBox module that **enhances and abstracts Hibernate ORM** for CFML engines (BoxLang, Lucee, Adobe ColdFusion). It extends Hibernate with service layers, Active Record patterns, fluent criteria queries, dynamic finders, RESTful resources, and AOP transaction management.
44

55
## Core Architecture
66

77
**Service Layer Pattern**: CBOrm uses three main service types:
8-
- `BaseORMService` - Base service for any entity operations (`models/BaseORMService.cfc`)
9-
- `VirtualEntityService` - Auto-generated services per entity via WireBox DSL
10-
- `ActiveEntity` - Active Record pattern for entities (`models/ActiveEntity.cfc`)
11-
12-
**Key Components**:
13-
- `models/criterion/CriteriaBuilder.cfc` - Fluent query builder for Hibernate criteria
8+
- `BaseORMService` - Base service for any entity operations with CRUD, dynamic finders, criteria queries (`models/BaseORMService.cfc`)
9+
- `VirtualEntityService` - Auto-generated entity-specific services via WireBox DSL, extends BaseORMService (`models/VirtualEntityService.cfc`)
10+
- `ActiveEntity` - Active Record pattern for entities with direct CRUD methods (`models/ActiveEntity.cfc`)
11+
12+
**Criteria Query System**: Fluent API wrapping Hibernate Criteria API
13+
- `models/criterion/BaseBuilder.cfc` - Base builder with projections, restrictions, ordering, grouping
14+
- `models/criterion/CriteriaBuilder.cfc` - Main criteria query builder for regular queries
15+
- `models/criterion/DetachedCriteriaBuilder.cfc` - Detached criteria for subqueries and projections
16+
- `models/criterion/Restrictions.cfc` - Proxy to Hibernate Restrictions (eq, gt, like, between, etc.)
17+
- `models/criterion/Subqueries.cfc` - Extends Restrictions for subquery support (subEq, subGt, etc.)
18+
19+
**Utilities & Helpers**:
20+
- `models/util/ORMUtilFactory.cfc` - Factory for cross-engine ORM utilities
21+
- `models/util/support/ORMUtilSupport.cfc` - Engine-agnostic ORM operations (session, transactions, metadata)
22+
- `models/util/support/AdobeORMUtil.cfc` - Adobe ColdFusion-specific ORM utilities
23+
- `models/util/support/LuceeORMUtil.cfc` - Lucee-specific ORM utilities
24+
- `models/util/support/BoxLangORMUtil.cfc` - BoxLang-specific ORM utilities
25+
- `models/util/DynamicProcessor.cfc` - Processes dynamic finders (findByName, countByStatus, etc.)
26+
- `models/util/JavaProxyBuilder.cfc` - Creates Java proxies for Hibernate classes
27+
- `models/sql/SQLHelper.cfc` - Extracts and formats SQL from criteria queries for debugging
28+
29+
**Event Handling**:
30+
- `models/EventHandler.cfc` - ORM lifecycle event handler (preLoad, postLoad, preInsert, postInsert, etc.)
31+
- `models/ACFEventHandler.cfc` - Adobe ColdFusion-specific event handler with CFIDE interface
32+
33+
**Integration Components**:
1434
- `dsl/OrmDsl.cfc` - WireBox DSL for `entityService:{entityName}` injection
15-
- `models/EventHandler.cfc` - ORM lifecycle event handling
16-
- `models/util/ORMUtilFactory.cfc` - Cross-engine ORM utilities
35+
- `aop/HibernateTransaction.cfc` - AOP aspect for @transactional annotation support
36+
- `interceptors/CriteriaBuilder.cfc` - ColdBox interceptor for SQL logging
37+
- `models/resources/BaseHandler.cfc` - RESTful base handler for automatic CRUD REST APIs
38+
- `models/validation/UniqueValidator.cfc` - Custom validator for unique entity properties
1739

1840
## Essential Patterns
1941

@@ -32,17 +54,73 @@ component extends="cborm.models.ActiveEntity" persistent="true" {
3254
// Entity definition
3355
}
3456
35-
// Usage: var user = new User().findByEmail("test@example.com");
57+
// Usage examples:
58+
var user = new User().findByEmail("test@example.com");
59+
user.setName("New Name").save();
60+
user.delete();
3661
```
3762

3863
**Fluent Criteria Queries**: Chain methods for complex queries:
3964
```cfml
65+
// Basic criteria with restrictions
4066
userService.newCriteria()
4167
.isTrue("isActive")
68+
.eq("status", "approved")
69+
.like("name", "John%")
70+
.list();
71+
72+
// With joins and projections
73+
userService.newCriteria()
4274
.joinTo("role").eq("name", "admin")
43-
.withProjections(property="id,name")
75+
.withProjections(property="id,name,email")
4476
.asStream()
4577
.list();
78+
79+
// Subqueries with DetachedCriteriaBuilder
80+
var subQuery = roleService.createSubcriteria("Role", "r")
81+
.eq("type", "premium");
82+
userService.newCriteria()
83+
.propertyIn("roleID", subQuery)
84+
.list();
85+
```
86+
87+
**Dynamic Finders**: Auto-generated methods from BaseORMService:
88+
```cfml
89+
// findBy{Property}, findAllBy{Property}
90+
userService.findByUsername("admin");
91+
userService.findAllByStatus("active");
92+
93+
// countBy{Property}
94+
userService.countByRole("admin");
95+
96+
// Conditional finders: LessThan, GreaterThan, Like, Between, InList, etc.
97+
userService.findByAgeLessThan(18);
98+
userService.findAllByCreatedDateBetween(startDate, endDate);
99+
```
100+
101+
**RESTful Resources**: Automatic CRUD REST API handlers:
102+
```cfml
103+
component extends="cborm.models.resources.BaseHandler" {
104+
property name="ormService" inject="entityService:User";
105+
106+
variables.entity = "User";
107+
variables.sortOrder = "lastName,firstName";
108+
}
109+
// Provides: index, create, show, update, delete actions
110+
```
111+
112+
**Transaction Management**: AOP-based transaction support:
113+
```cfml
114+
// Add @transactional annotation to methods
115+
function saveUser(user) transactional {
116+
// Automatically wrapped in transaction
117+
userService.save(arguments.user);
118+
}
119+
120+
// Multi-datasource support
121+
function saveUser(user) transactional="myDatasource" {
122+
userService.save(arguments.user);
123+
}
46124
```
47125

48126
## Development Workflow

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
fail-fast: false
2020
matrix:
2121
cfengine: [ "boxlang-cfml@1", "lucee@5", "lucee@6", "adobe@2023", "adobe@2025" ]
22-
coldboxVersion: [ "^7.0.0" ]
22+
coldboxVersion: [ "^7.0.0", "^8.0.0" ]
2323
experimental: [ false ]
2424
# Experimental: ColdBox BE vs All Engines
2525
include:
@@ -42,7 +42,7 @@ jobs:
4242
cfengine: "boxlang-cfml@be"
4343
experimental: true
4444
# BoxLang No CFML compat
45-
- coldboxVersion: "be"
45+
- coldboxVersion: "^8.0.0"
4646
cfengine: "boxlang@1"
4747
experimental: true
4848
steps:

models/ActiveEntity.cfc

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,19 @@ component extends="cborm.models.VirtualEntityService" accessors="true" {
4848
){
4949
// Calculate name via metadata
5050
var md = getMetadata( this );
51-
arguments.entityName = ( md.keyExists( "entityName" ) ? md.entityName : listLast( md.name, "." ) );
51+
var annotations = md.keyExists( "annotations" ) ? md.annotations : md;
52+
arguments.entityName = (
53+
annotations.keyExists( "entityName" ) ? annotations.entityName : listLast( md.name, "." )
54+
);
5255

5356
// Store query cache region
5457
if ( isNull( arguments.queryCacheRegion ) ) {
5558
arguments.queryCacheRegion = "#arguments.entityName#.activeEntityCache";
5659
}
5760

5861
// Verify datasource just in case.
59-
if ( md.keyExists( "datasource" ) ) {
60-
arguments.datasource = md.datasource;
62+
if ( annotations.keyExists( "datasource" ) ) {
63+
arguments.datasource = annotations.datasource;
6164
}
6265

6366
// init the super class with our own arguments

models/EventHandler.cfc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,10 @@ component extends="coldbox.system.remote.ColdboxProxy" {
127127
} else {
128128
// Long Discovery
129129
var md = getMetadata( arguments.entity );
130-
args.entityName = ( md.keyExists( "entityName" ) ? md.entityName : listLast( md.name, "." ) );
130+
var annotations = md.keyExists( "annotations" ) ? md.annotations : md;
131+
args.entityName = (
132+
annotations.keyExists( "entityName" ) ? annotations.entityName : listLast( md.name, "." )
133+
);
131134
}
132135
}
133136

models/criterion/BaseBuilder.cfc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ component accessors="true" {
404404

405405
// iterate and add dynamically if the incoming argument exists, man, so much easier if we had closures.
406406
for ( var pType in arguments ) {
407-
if ( structKeyExists( arguments, pType ) AND NOT listFindNoCase( excludes, pType ) ) {
407+
if ( !isNull( arguments[ pType ] ) AND NOT listFindNoCase( excludes, pType ) ) {
408408
addProjection(
409409
arguments[ pType ],
410410
lCase( pType ),
@@ -414,23 +414,23 @@ component accessors="true" {
414414
}
415415

416416
// id
417-
if ( structKeyExists( arguments, "id" ) ) {
417+
if ( !isNull( arguments.id ) ) {
418418
projectionList.add( this.PROJECTIONS.id() );
419419
}
420420

421421
// rowCount
422-
if ( structKeyExists( arguments, "rowCount" ) ) {
422+
if ( !isNull( arguments.rowCount ) ) {
423423
projectionList.add( this.PROJECTIONS.rowCount() );
424424
}
425425

426426
// distinct
427-
if ( structKeyExists( arguments, "distinct" ) ) {
427+
if ( !isNull( arguments.distinct ) ) {
428428
addProjection( arguments.distinct, "property", projectionList );
429429
projectionList = this.PROJECTIONS.distinct( projectionList );
430430
}
431431

432432
// detachedSQLProjection
433-
if ( structKeyExists( arguments, "detachedSQLProjection" ) ) {
433+
if ( !isNull( arguments.detachedSQLProjection ) ) {
434434
// allow single or arrary of detachedSQLProjection
435435
var projectionCollection = !isArray( arguments.detachedSQLProjection ) ? [
436436
arguments.detachedSQLProjection
@@ -442,7 +442,7 @@ component accessors="true" {
442442
}
443443

444444
// sqlProjection
445-
if ( structKeyExists( arguments, "sqlProjection" ) ) {
445+
if ( !isNull( arguments.sqlProjection ) ) {
446446
// allow for either an array of sqlProjections, or a stand-alone config for one
447447
var sqlargs = !isArray( arguments.sqlProjection ) ? [ arguments.sqlProjection ] : arguments.sqlProjection;
448448
// loop over sqlProjections
@@ -460,7 +460,7 @@ component accessors="true" {
460460
}
461461

462462
// sqlGroupProjection
463-
if ( structKeyExists( arguments, "sqlGroupProjection" ) ) {
463+
if ( !isNull( arguments.sqlGroupProjection ) ) {
464464
// allow for either an array of sqlGroupProjections, or a stand-alone config for one
465465
var sqlargs = !isArray( arguments.sqlGroupProjection ) ? [ arguments.sqlGroupProjection ] : arguments.sqlGroupProjection;
466466
// loop over sqlGroupProjections

models/util/support/ORMUtilSupport.cfc

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@ component singleton {
129129
arguments.entity = entityNew( arguments.entity );
130130
}
131131

132-
var md = getMetadata( arguments.entity );
133-
if ( structKeyExists( md, "datasource" ) ) {
134-
datasource = md.datasource;
132+
var md = getMetadata( arguments.entity );
133+
var annotations = md.keyExists( "annotations" ) ? md.annotations : md;
134+
if ( structKeyExists( annotations, "datasource" ) ) {
135+
datasource = annotations.datasource;
135136
}
136137

137138
return datasource;
@@ -207,16 +208,15 @@ component singleton {
207208

208209
// Hibernate Discovery
209210
try {
210-
return getSession( getEntityDatasource( arguments.entity ) ).getEntityName(
211-
arguments.entity
212-
);
211+
return getSession( getEntityDatasource( arguments.entity ) ).getEntityName( arguments.entity );
213212
} catch ( org.hibernate.TransientObjectException e ) {
214213
// ignore it, it is not in session, go for long-discovery
215214
}
216215

217216
// Long Discovery
218-
var md = getMetadata( arguments.entity );
219-
return ( md.keyExists( "entityName" ) ? md.entityName : listLast( md.name, "." ) );
217+
var md = getMetadata( arguments.entity );
218+
var annotations = md.keyExists( "annotations" ) ? md.annotations : md;
219+
return ( annotations.keyExists( "entityName" ) ? annotations.entityName : listLast( md.name, "." ) );
220220
}
221221

222222
/**

server-boxlang-cfml@be.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"BOXLANG_DEBUG":true
3232
},
3333
"scripts":{
34-
"onServerInitialInstall":"install bx-mysql,bx-derby,bx-compat-cfml@be,bx-orm@be --noSave"
34+
"onServerInitialInstall":"install bx-mysql,bx-derby,bx-compat-cfml,bx-orm --noSave"
3535
},
3636
"-trace":true
3737
}

0 commit comments

Comments
 (0)