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 .bidjim files

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, types
  • bjmda.native_properties() with and without complex types
  • bjmda.has_complex_types()
  • Complete Java code generation
  • Error handling

See Also


Document Version: 2.0 Created: 2026-02-08 Last Updated: 2026-02-12