bjmda - Bidji MDA Namespace
Version: 0.8+
Namespace: bjmda
Type: FreeMarker Custom Directive
Table of Contents
Overview
The bjmda namespace (Bidji Model Driven Architecture) provides methods to access Bidji entity and property metadata in FreeMarker templates.
Benefits:
- ✅ Clear and explicit syntax
- ✅ Native FreeMarker validation
- ✅ Descriptive error messages
- ✅ Automatically available for
.bidjimfiles
Syntax
${bjmda.name(entity)}
[#foreach p in bjmda.properties(entity)]
private ${p.type} _${p.name};
[/#foreach]
Available Methods
Entity Metadata
bjmda.name(entity)
Returns the entity name.
Parameters:
entity: Bidji Entity object
Returns: String - Entity name
Example:
public class ${bjmda.name(entity)} {
bjmda.package(entity)
Returns the package name (parent directory of the .bidjim file).
Parameters:
entity: Bidji Entity object
Returns: String - Package name
Example:
package ${bjmda.module(entity)}.${bjmda.package(entity)};
bjmda.module(entity)
Returns the module name (grandparent directory of the .bidjim file).
Parameters:
entity: Bidji Entity object
Returns: String - Module name
Example:
package ${bjmda.module(entity)}.${bjmda.package(entity)};
bjmda.default_sort(entity), bjmda.icon(entity), bjmda.delete(entity), bjmda.taggable(entity)
Return entity configuration attributes.
Entity Properties
bjmda.properties(entity)
Returns all entity properties.
Parameters:
entity: Bidji Entity object
Returns: List<Map<String, Object>> - List of properties
Example:
[#foreach p in bjmda.properties(entity)]
private ${p.type!""} _${p.name};
[/#foreach]
bjmda.native_properties(entity)
Returns only native type properties (excluding complex types/linked entities).
Parameters:
entity: Bidji Entity object
Returns: List<Map<String, Object>> - List of native properties
Example:
[#foreach p in bjmda.native_properties(entity)]
${p.name}: ${p.type}
[/#foreach]
bjmda.dataform_properties(entity), bjmda.datatable_properties(entity), bjmda.data_properties(entity)
Return properties filtered for UI forms and data tables.
Nested Entity
bjmda.entity(name)
Returns an entity by name (model lookup).
Parameters:
name: String - Entity name to search for
Returns: Entity object
Example:
[#assign nested_entity = bjmda.entity(p.type)]
${bjmda.name(nested_entity)}
Type Queries
bjmda.type_or_string(property)
Returns the property type, or 'string' if not defined.
Parameters:
property: Property map (with or without@prefix)
Returns: String - FreeMarker/PHP type
Example:
[#foreach p in bjmda.properties(entity)]
${bjmda.type_or_string(p)} $${p.name}
[/#foreach]
bjmda.nullable_type(property)
Returns the nullable type of the property.
bjmda.dbtype(property)
Returns the SQL type of the property.
bjmda.default_value(property)
Returns the default value of the property.
bjmda.is_property(column)
Checks if a column has a corresponding property in the model.
UI Helpers
bjmda.detail_tabs(entity), bjmda.has_detail_tabs(entity), bjmda.first_datatable_tab(entity)
Return tab information for detail views.
bjmda.has_filter_by_month(entity), bjmda.is_monthly_table(entity), bjmda.has_table_footer(entity)
Helpers for table display options.
Relations
bjmda.one_to_many(entity), bjmda.many_to_many(entity)
Return relationship properties.
bjmda.is_created_in(), bjmda.creates()
Check creation relationships between entities.
Model
bjmda.has_complex_types()
Checks if the current model uses complex types (linked entities).
Parameters: none
Returns: Boolean
Example:
[#if bjmda.has_complex_types()]
[#-- Imports for linked entities --]
[/#if]
Debug
bjmda.dump_entity(entity), bjmda.dump_property(property)
Display entity or property structure (for debugging).
Examples
Example 1: Laravel Model Generation
From tmp_bidji/templates/php-laravel/Modules/{Package}/app/Models/{Model}.ftl.php:
[#assign entity = bidji.entity]
<?php
namespace Modules\${Package}\Models;
use Illuminate\Database\Eloquent\Model;
[#if bjmda.delete(entity)?? && bjmda.delete(entity) == 'soft']
use Illuminate\Database\Eloquent\SoftDeletes;
[/#if]
[#if bjmda.taggable(entity)]
use Modules\Common\Models\Tag;
[/#if]
class ${Model} extends Model
{
[#if bjmda.delete(entity)?? && bjmda.delete(entity) == 'soft']
use SoftDeletes;
[/#if]
protected $table = '${package}_${model}s';
protected $primaryKey = '${model}_id';
protected $attributes = [
[#foreach property in bjmda.properties(entity)]
[#if property.name == 'id']
// '${model}_id' is primary key
[#elseif property.defaultvalue??]
'${property.dbname}' => ${property.defaultvalue},
[#elseif property.is_relation]
[#if property.has_one]
'${property.dbname}' => null,
[/#if]
[#else]
'${property.dbname}' => null,
[/#if]
[/#foreach]
];
}
Example 2: Laravel Migration with Database Types
From tmp_bidji/templates/php-laravel/Modules/{Package}/database/migrations/*.ftl.php:
[#assign entity = bidji.entity]
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('${package}_${model}s', function (Blueprint $table) {
[#foreach property in bjmda.properties(entity)]
[#if property.name == 'id']
$table->bigIncrements('${model}_id')->primary();
[#elseif property.has_values]
$table->enum('${property.dbname}',['${property.values?replace("|","','")}']);
[#elseif property.is_native]
$table->${bjmda.dbtype(property)?lower_case}('${property.dbname}')[#if property.mandatory == false]->nullable()[/#if][#if property.unique]->unique()[/#if][#if property.default??]->default(${property.default})[/#if];
[#elseif property.has_one]
$table->foreignId('${property.dbname}')[#if property.mandatory == false]->nullable()[/#if]->constrained(
table: '${package}_${property.type?lower_case}s', column: '${property.type?lower_case}_id', indexName: '${property.dbname}_fk'
);
[/#if]
[/#foreach]
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->nullable();
[#if bjmda.delete(entity)?? && bjmda.delete(entity) == 'soft']
$table->softDeletes();
[/#if]
});
[#-- many-to-many relationships --]
[#foreach property in bjmda.many_to_many(entity)]
[#if property.type??]
Schema::create('${package}_${model}s_${property.name}', function (Blueprint $table) {
$table->unsignedBigInteger('${model}_id');
$table->unsignedBigInteger('${property.name?remove_ending("s")}_id');
$table->timestamps();
$table->foreign('${model}_id')->references('${model}_id')->on('${package}_${model}s');
$table->foreign('${property.name?remove_ending("s")}_id')->references('id')->on('users');
$table->primary(['${model}_id', '${property.name?remove_ending("s")}_id']);
});
[/#if]
[/#foreach]
}
};
Example 3: Laravel Route Generation
From tmp_bidji/templates/php-laravel/Modules/{Package}/routes/web.ftl.php:
<?php
use Illuminate\Support\Facades\Route;
[#foreach entity in bjmda.entities()]
[#if bjmda.package(entity) == package]
use Modules\${bjmda.package(entity)?cap_first}\Http\Controllers\${bjmda.name(entity)}Controller;
[#foreach property in bjmda.properties(entity)]
[#if property.name == 'id']
[#elseif property.has_many]
use Modules\${bjmda.package(entity)?cap_first}\Http\Controllers\${bjmda.name(entity)?cap_first}${property.type}Controller;
[/#if]
[/#foreach]
[/#if]
[/#foreach]
[#foreach entity in bjmda.entities()]
[#if bjmda.package(entity) == package]
Route::group(['middleware' => 'auth'], function () {
Route::resource('${bjmda.name(entity)?lower_case}s', ${bjmda.name(entity)}Controller::class)->names('${bjmda.name(entity)?lower_case}s');
[#foreach property in bjmda.properties(entity)]
[#if property.name == 'id']
[#elseif property.has_many]
Route::resource('${bjmda.name(entity)?lower_case}s.${property.name?lower_case}', ${bjmda.name(entity)?cap_first}${property.type}Controller::class)->names('${bjmda.name(entity)?lower_case}s.${property.name?lower_case}');
[/#if]
[/#foreach]
[#if bjmda.taggable(entity)]
Route::post('${bjmda.name(entity)?lower_case}s/tag', [\Modules\${bjmda.package(entity)?cap_first}\Http\Controllers\${bjmda.name(entity)}TagController::class, 'store'])->name('${bjmda.name(entity)?lower_case}s.tag');
[/#if]
});
[/#if]
[/#foreach]
Example 4: API Routes
From tmp_bidji/templates/php-laravel/Modules/{Package}/routes/api.ftl.php:
<?php
use Illuminate\Support\Facades\Route;
[#foreach entity in bidjilist.entity]
use Modules\${Package}\Http\Controllers\${bjmda.name(entity)}Controller;
[/#foreach]
Route::middleware(['auth:sanctum'])->prefix('v1')->group(function () {
[#foreach entity in bidjilist.entity]
Route::apiResource('${bjmda.name(entity)?lower_case}s', ${bjmda.name(entity)}Controller::class)->names('${bjmda.name(entity)?lower_case}s');
[/#foreach]
});
Example 5: Nested Entity Access
[#assign entity = bidjim.entity]
[#if bjmda.has_complex_types()]
[#foreach p in bjmda.properties(entity)]
[#if !p.type?starts_with("Integer") && !p.type?starts_with("String")]
[#assign nested = bjmda.entity(p.type)]
import ${bjmda.module(entity)}.${bjmda.package(entity)}.${bjmda.name(nested)};
[/#if]
[/#foreach]
[/#if]
Migration from Legacy Syntax
Complete Mapping Table
| Old Syntax | New bjmda Syntax |
|---|---|
| Entity Metadata | |
${entity.@name} |
${bjmda.name(entity)} |
${entity.@package} |
${bjmda.package(entity)} |
${entity.@module} |
${bjmda.module(entity)} |
${entity.@default_sort} |
${bjmda.default_sort(entity)} |
${entity.@icon} |
${bjmda.icon(entity)} |
${entity.@delete} |
${bjmda.delete(entity)} |
${entity.@taggable} |
${bjmda.taggable(entity)} |
${entity2.@name} |
${bjmda.name(entity2)} |
${entity2.@package} |
${bjmda.package(entity2)} |
| Properties | |
entity?properties |
bjmda.properties(entity) |
entity?native_properties |
bjmda.native_properties(entity) |
entity?dataform_properties |
bjmda.dataform_properties(entity) |
entity?datatable_properties |
bjmda.datatable_properties(entity) |
entity?data_properties |
bjmda.data_properties(entity) |
entity?one_to_many_properties |
bjmda.one_to_many(entity) |
entity?many_to_many_properties |
bjmda.many_to_many(entity) |
${property.@name} |
${property.name} |
${property.@type} |
${property.type} |
| Nested Entity | |
nested?entity |
bjmda.entity(nested.type) |
nested?properties |
bjmda.properties(nested.type) |
nested?native_properties |
bjmda.native_properties(nested.type) |
| Property Type | |
property?type_or_string |
bjmda.type_or_string(property) |
column?type_or_string |
bjmda.type_or_string(column) |
property?dbtype |
bjmda.dbtype(property) |
property?nullable_type_or_string |
bjmda.nullable_type(property) |
property?default_value |
bjmda.default_value(property) |
column?default_value |
bjmda.default_value(column) |
column?is_property |
bjmda.is_property(column) |
| UI Helpers | |
entity?detail_tabs |
bjmda.detail_tabs(entity) |
entity?has_detail_tabs |
bjmda.has_detail_tabs(entity) |
entity?first_datatable_tab |
bjmda.first_datatable_tab(entity) |
entity?has_filter_by_month |
bjmda.has_filter_by_month(entity) |
entity?is_monthly_table |
bjmda.is_monthly_table(entity) |
entity?has_table_footer |
bjmda.has_table_footer(entity) |
| Relations | |
entity?is_created_in |
bjmda.is_created_in() |
entity?creates |
bjmda.creates() |
| Model | |
model?has_complex_types |
bjmda.has_complex_types() |
| Debug | |
entity?dump |
bjmda.dump_entity(entity) |
property?dump |
bjmda.dump_property(property) |
Automatic Migration Script
./migrate_api_to_bjmda.sh --dry-run /path/to/templates
./migrate_api_to_bjmda.sh /path/to/templates
./migrate_api_to_bjmda.sh --backup /path/to/templates
The script automatically migrates all .ftl files in the target directory.
Important Notes
1. Required entity Variable
bjmda methods take the entity as an explicit parameter. In bidjic templates, the entity is available via bidjim.entity:
[#assign entity = bidjim.entity]
${bjmda.name(entity)}
2. Keys Without @ Prefix
Properties returned by bjmda.properties() have keys without the @ prefix:
[#-- ✅ Correct with bjmda --]
[#foreach p in bjmda.properties(entity)]
${p.name} ${p.type}
[/#foreach]
[#-- ❌ Incorrect with bjmda (@ not needed) --]
[#foreach p in bjmda.properties(entity)]
${p.@name} ${p.@type}
[/#foreach]
3. Backward Compatibility
Both syntaxes work simultaneously. Migration can be progressive:
- Legacy syntax continues to work via
BidjicCompiler - bjmda syntax is available directly
4. FreeMarker Validation
FreeMarker automatically validates:
- Number of arguments
- Method existence
Unknown method:
Unknown bjmda method: 'invalid_method'.
Missing argument:
bjmda.name() requires 1 argument
Tests
Java Unit Tests
mvn test -Dtest=BjmdaNamespaceTest
Coverage:
- 20 tests
- Metadata, properties, types, errors
- 100% success
AntUnit Tests
cd test
ant -f build-builtin-bjmda.xml testAll
Included tests:
bjmda.name(),bjmda.package(),bjmda.module()bjmda.properties()size, names, typesbjmda.native_properties()with and without complex typesbjmda.has_complex_types()- Complete Java code generation
- Error handling
See Also
- bjio.md - I/O Namespace (files)
- BidjicDirectives.md - Complete Bidji Directives
- migrate_api_to_bjmda.sh - Migration script
- BjmdaNamespaceTest.java - Java tests
Document Version: 2.0 Created: 2026-02-08 Last Updated: 2026-02-12