欄位護體心法:以 Java 黑魔法施展三明治夾擊,拒絕核心清空詛咒 🥪🛡️
iDempiere

欄位護體心法:以 Java 黑魔法施展三明治夾擊,拒絕核心清空詛咒 🥪🛡️

2026-04-16 最後更新:2026-04-17) · 8 分鐘 · Ray Lee (System Analyst)

前言:江湖恩怨的起源

話說 iDempiere 武林之中,有一物件名曰 Request(工單),乃本門 Kanban 看板的命脈所繫。

此物件內建一套 Request Event 機制——每逢工單有任何異動,無論狀態流轉、欄位修改,一律觸發事件,忠實記錄。本是好事,奈何核心門派有個陋習:每次觸發,皆順手將 EndTime 欄位清空。

清空!不留情面,不問用途,一律清空。

偏偏本公司為了打造 Gantt 時程甘特圖,早已將 StartTimeEndTime 視為「預計工期起訖」的聖物,Kanban 看板上的每一條任務橫條,皆仰賴此二欄定錨。結果工單一更新,EndTime 應聲歸零,甘特條目憑空消失,掌管進度的同仁苦不堪言。

江湖人人皆知:核心源碼裡,只消刪掉一行程式碼,此患即除。

然老掌門有令,鐵口直斷:「不得動核心!日後升級,誰負責?必須以 Plugin 之道解決!」

於是,一場鬥智鬥勇、三戰三折的突圍之旅,就此展開。


第一式:OSGi 排名戰術——初入江湖,遭遇迎頭痛擊 ❌

心法口訣:「以序取勝,先下手為強。」

吾等初出茅廬,自以為尋得破敵之道:建立三層「夾擊陣型」,以 Ranking 高下控制出手順序——

  • Capturer(先行者) Ranking 10000:搶在核心之前,扣押 EndTime 資料
  • Core(核心) Ranking 0:照常發動清空指,無情清除
  • Restorer(善後者) Ranking -100:待核心收手,悄然還原

此計甚妙。然而…… Core 根本無視 Ranking,它走的是「江湖資歷制」——按 OSGi 服務的註冊順序出手,不看你段位高低。吾等精心佈陣,在它眼中不過是牆上貼紙。

一劍砍下,打在空氣上。❌


第二式:ModelValidator 快速通關——走後門,踩到暗門 ❌

心法口訣:「走捷徑者,易入陷阱。」

吾等遂改弦易轍,打算走 VIP 後門——在事件發送前就攔截,令清空指出不了手。

然系統有規矩:它要核查 Client ID。問題是吾等的攔截器,不知使用者此刻身在哪個 Client,導致系統冷冷一句「不符資格」,直接 Skip 掉吾等的攔截器。

滿懷信心走進 VIP 通道,被保安無情攔在門口。❌


終極殺招:Java Reflection 強行注入——撬開禁室,持全域通行證出手 🪄✨

心法口訣:「鎖不住的門,用反射撬開。」

正門進不去、後門被擋——吾等祭出武林密技:Java Reflection(反射機制)

此乃程式界的「無形手」——不需授權,不打招呼,直接伸手進 m_globalValidators 這個 private 禁室,把自己的監聽器強行塞入。自此,吾等持有免簽的全域通行證,無論哪個 Client、哪個角落,皆能先行感知,搶佔先機。

// 取得 ModelValidationEngine 的 private 欄位
Field f = ModelValidationEngine.class.getDeclaredField("m_globalValidators");
f.setAccessible(true);
List<ModelValidator> globalValidators = (List<ModelValidator>) f.get(engine);
globalValidators.add(new EndTimeCapturer());

三層「三明治夾擊陣型」,就此成型:

層次角色技術實作任務
🥪 上層麵包Capturer(先行者)Reflection + ModelValidator清空指出手前,扣押 EndTime
🥩 夾心餡料Core(核心)OSGi PO_BEFORE_CHANGE照常出招,無情清空
🥪 下層麵包Restorer(善後者)OSGi EventHandler後手之利,完美還原

三明治夾擊成立,EndTime 在「被清空」與「還原」之間的瞬間,完好如初。Gantt 看板,重歸秩序。


雷公李曰

余嘗觀 OSGi 武林,正道千條,皆有門神把守。唯反射之術,如鬼魅穿牆,不拘形式,不受門禁。然此術非萬能解——用之前,先問自己:能否與掌門商量,改個架構?若答案仍是「不許動核心」,那麼,就施展這記黑魔法吧。

軟體之道,從無絕境,只有尚未找到的缺口。EndTime 雖被清空,然只要三明治夾到位,欄位護體,永不失守。

English Version

Prologue: A Grudge Born in the ERP Dojo

Deep in the iDempiere martial arts world lives an object known as Request — the lifeblood of our Kanban board.

Request comes with a built-in event mechanism: every time a ticket is touched — a status change, a field update, anything — it fires a Request Event, faithfully logging the movement. Noble in intent. Disastrous in side effect.

Because somewhere in the core, a rogue disciple runs a routine that wipes EndTime clean on every single event trigger. No questions asked. No exceptions made. Just gone.

Our dojo had been using StartTime and EndTime as the sacred anchors for Gantt chart planning — every task bar on the Kanban board depended on these two fields to define its span. One ticket update, and poof: EndTime zeroes out, the Gantt bar vanishes into the void, and the project managers are left staring at a board full of floating fragments.

Everyone in the dojo knows the fix: delete one line in the core source. One line. Done.

But the Grand Master spoke, and the word was final: “Do not touch the core. Who takes responsibility when we upgrade? This must be solved with a Plugin.”

And so the quest began.


Move 1: The OSGi Ranking Gambit — Confident Entry, Crushing Defeat ❌

Mantra: “Strike first, strike high.”

Our first plan seemed elegant: a three-layer formation using OSGi service rankings to control the order of execution.

  • Capturer (Rank 10,000): intercepts before Core, captures EndTime
  • Core (Rank 0): fires as usual, clears EndTime
  • Restorer (Rank -100): swoops in last, restores EndTime

Brilliant in theory. Irrelevant in practice.

Core ignores rankings entirely. It follows registration order — whoever signed up first goes first. Our carefully calibrated formation was invisible to it. We swung hard and hit nothing but air.

Defeated. ❌


Move 2: The ModelValidator Shortcut — The Back Door Was a Trap ❌

Mantra: “The quick path often leads to a pit.”

We pivoted. Instead of racing Core in the queue, we’d intercept before the event is even dispatched — a VIP lane that bypasses the whole ordering problem.

There was one catch: the system checks Client ID before admitting our interceptor. Since we couldn’t predict which Client the user would be in at runtime, the system delivered a clean “ineligible” verdict and skipped our handler entirely.

We walked up to the VIP entrance with full confidence. The bouncer turned us away. ❌


The Ultimate Move: Java Reflection Infiltration — Breaking Into the Private Vault 🪄✨

Mantra: “If the door won’t open, reflection will walk through the wall.”

Both the front gate and the back door were closed. Time for black magic.

Java Reflection is the “invisible hand” of the programming world. It doesn’t need permission. It doesn’t knock. It reaches directly into the private field m_globalValidators inside ModelValidationEngine and installs our listener without asking anyone.

// Access the private field via reflection
Field f = ModelValidationEngine.class.getDeclaredField("m_globalValidators");
f.setAccessible(true);
List<ModelValidator> globalValidators = (List<ModelValidator>) f.get(engine);
globalValidators.add(new EndTimeCapturer());

Our listener is now registered globally — no Client ID check, no registration order problem. We have a universal pass.

The three-layer Sandwich Strike snaps into place:

LayerRoleImplementationMission
🥪 Top breadCapturerReflection + ModelValidatorCapture EndTime before Core strikes
🥩 FillingCoreOSGi PO_BEFORE_CHANGEFires normally, clears EndTime
🥪 Bottom breadRestorerOSGi EventHandlerRestores EndTime after Core exits

EndTime survives the clearing. The Gantt board holds. The Kanban looks exactly as it should.


Master Lei Speaks

I have observed the OSGi martial world for many years. A thousand orthodox paths — each one guarded. Only Reflection moves like a ghost through walls, bound by neither gate nor rank. But know this: Reflection is not the first resort. Before you reach for the black magic, ask yourself — can you negotiate with the Grand Master? Can the architecture change?

If the answer remains “do not touch the core,” then so be it. Draw the spell.

In software, there are no dead ends. Only passages not yet found. EndTime may be cleared a hundred times — but with the Sandwich in place, it always comes back.

日本語版

序章:因縁の発端

iDempiere 武林の奥深くに、Request(ワークチケット)という名の物件が存在する。本道場のKanbanボードを支える、まさに命脈だ。

このRequestには、コアに組み込まれたRequest Eventの仕組みがある。チケットに何らかの変更——ステータス変更であれ、フィールド更新であれ——があるたびにイベントが発火し、忠実に記録する。本来は優れた設計だ。しかしコアの奥深くに、ある厄介な習慣が潜んでいた。

イベントが発火するたびに、EndTimeフィールドを問答無用で消去してしまうのだ。

理由を問わず。用途を確かめず。ただ、消す。

本道場ではStartTimeEndTimeを、Ganttチャートの「予定開始・終了日時」を記録する聖なる二柱として活用していた。Kanbanボードの各タスクバーは、この二つのフィールドで時間軸を定義している。ところがチケットが更新されるたびにEndTimeが消え、Ganttバーは宙に浮き、進捗を管理する担当者は頭を抱えることとなった。

道場の誰もが知っている。コアのソースコードを開けば、たった一行を削除するだけでこの問題は解決する。

しかし大師範の言葉は厳然としていた:「コアには手を触れるな。アップグレード時に誰が責任を取るのか?Pluginで解決せよ。」

こうして、三つの試練を経る突破の旅が始まった。


第一の型:OSGiランキング戦術——自信満々の入門、完膚なきまでの敗北 ❌

心法の一節:「先手必勝、序列で制す。」

最初の策は単純明快に見えた。OSGiサービスのランキングを使い、実行順序を制御する三層夾撃陣を組む。

  • Capturer(先手) Ranking 10,000:Coreより先にEndTimeを保存
  • Core(核心) Ranking 0:通常通り発動し、EndTimeを消去
  • Restorer(後手) Ranking -100:Coreの後にEndTimeを復元

鮮やかな布陣だった。しかし現実は非情だった。

Coreはランキングを一切無視する。動く順序は登録された順番——段位でも優先度でもなく、ただの受付番号だ。綿密に組んだ陣は、Coreの目には存在しないも同然だった。

渾身の一撃は、空を切った。❌


第二の型:ModelValidatorの近道——裏口に踏み込んだら、落とし穴だった ❌

心法の一節:「近道を歩む者は、罠に落ちやすい。」

次の策は、イベントが配信される前に割り込む——VIPルートでCoreの行動を封じる作戦だ。

だが、システムには一つの掟があった:Client IDの照合。我々の割り込みハンドラは、実行時にユーザーがどのClientにいるかを判断できない。結果、システムは冷たく「資格なし」と判定し、ハンドラをスキップした。

VIP入口に自信満々で向かったが、警備員に容赦なく門前払いを食らった。❌


奥義:Javaリフレクション強制注入——禁断の私有フィールドを撃ち破り、全域通行証を手に入れよ 🪄✨

心法の一節:「鍵が開かぬなら、リフレクションで壁を抜けよ。」

正門も裏口も封じられた。ならば黒魔術を使うしかない。

Javaのリフレクション機構は、プログラムの世界における「無形の手」だ。許可を必要とせず、ノックもしない。ModelValidationEngineprivateフィールドm_globalValidatorsに直接侵入し、我々のリスナーを強制的に登録する。

// リフレクションでprivateフィールドにアクセス
Field f = ModelValidationEngine.class.getDeclaredField("m_globalValidators");
f.setAccessible(true);
List<ModelValidator> globalValidators = (List<ModelValidator>) f.get(engine);
globalValidators.add(new EndTimeCapturer());

これにより我々のリスナーはグローバルに登録された。Client IDの照合なし。登録順序の問題なし。全域通行証の獲得だ。

三層「サンドイッチ夾撃陣」が完成する:

役割技術実装任務
🥪 上のパンCapturer(先手)Reflection + ModelValidatorCoreの一撃前にEndTimeを確保
🥩 具材Core(核心)OSGi PO_BEFORE_CHANGE通常通り発動、EndTimeを消去
🥪 下のパンRestorer(後手)OSGi EventHandler後手の優位を活かし完全復元

EndTimeは消去される。しかしサンドイッチが挟み込む。気づけば元通り。Ganttボードは秩序を取り戻す。


雷公李曰く

余、長年にわたりOSGiの武林を観察してきた。正道は数あれど、いずれも門番が守っている。ただリフレクションだけが、壁を幽霊のように抜ける。形式に縛られず、門禁にも屈しない。

しかし忘れてはならない——これは最後の手段だ。まず問え:大師範と交渉できないか?アーキテクチャを変えられないか?それでも答えが「コアに触れるな」であれば、迷わず黒魔術を解き放て。

ソフトウェアに行き止まりはない。ただ、まだ見つかっていない突破口があるだけだ。EndTimeは何度消されても、サンドイッチが守る限り、必ず帰ってくる。

Ray Lee (System Analyst)
作者 Ray Lee (System Analyst)

iDempeire ERP Contributor, 經濟部中小企業處財務管理顧問 李寶瑞