1편에서 JDeveloper를 띄우고 Hello World까지 확인했다면, 이제 진짜 일이 들어옵니다. “공급업체 화면에 관리항목 하나 추가해 주세요”라는 요청을 받았을 때, 이걸 클릭 몇 번으로 끝낼 수 있는지 자바 클래스를 새로 짜야 하는지 판단하는 것이 OAF 개발자의 첫 번째 실력입니다. 판단을 잘못하면 30분이면 될 일이 3일이 되거나, 반대로 패치 한 번에 날아갈 코드를 만들게 됩니다.
목차
01 — Decision세 가지 길: 개인화 · 확장 · 신규 개발
OAF에서 화면을 바꾸는 방법은 크게 세 가지입니다. 그리고 세 가지는 난이도, 소요 시간, 패치 내구성이 전부 다릅니다. 요구사항을 받으면 코드를 열기 전에 이 분류부터 하세요.
표준 OAF 페이지의 XML이나 오라클이 제공한 .class 파일을 직접 수정하거나 덮어쓰는 방식은 오라클 미지원(unsupported)입니다. 패치가 올라오면 파일이 원본으로 교체되면서 작업이 통째로 사라지고, SR을 열어도 지원받지 못합니다. 반드시 위 세 가지 중 하나로 처리하세요.
Personalization — 코딩 없이 화면 바꾸기
OAF로 만들어진 페이지는 기본적으로 전부 개인화가 가능합니다. 개발자가 특정 속성을 꺼두거나 프로그램으로 동적 생성한 항목만 예외입니다. 실무 요구사항의 절반 이상이 이 단계에서 끝납니다.
1-1. 준비: 프로파일 옵션
1편에서 켰던 Personalize Self-Service Defn = Yes가 되어 있어야 페이지 상단에 Personalize Page 링크가 나타납니다.
1-2. 개인화 레벨 이해하기
가장 헷갈리는 부분입니다. 개인화는 어느 레벨에 저장하느냐에 따라 적용 범위가 달라집니다.
| 레벨 | 적용 대상 | 우선순위 | 실무 용도 |
|---|---|---|---|
| Site | 인스턴스 전체 사용자 | 가장 낮음 | 전사 공통 화면 정비 |
| Organization | 특정 조직 | ↓ | 법인별 항목 차이 |
| Responsibility | 특정 책임 | ↓ | 가장 많이 씀. 부서별 화면 분리 |
| User | 개인 | 가장 높음 | 사용자 본인 설정. 타인에게 안 보임 |
충돌하면 더 좁은 범위가 이깁니다. Site에서 숨긴 항목을 특정 Responsibility에서 다시 보이게 할 수 있습니다.
1-3. 실습: 필드 하나 숨기기
- 대상 페이지에서 Personalize Page 클릭
- 상단에서 개인화 레벨(보통 Responsibility) 선택 후 Expand All
- 트리에서 대상 Item 찾기 — 이름이 비슷한 게 많아서 헷갈리면, 화면으로 돌아가 About this Page > Page Definition에서 실제 Item ID를 먼저 확인
- 해당 행의 연필 아이콘(Personalize) 클릭
- Rendered 속성을
False로 변경 후 Apply
페이지로 돌아가면 즉시 반영돼 있습니다. 서버 재기동도, 캐시 삭제도 필요 없습니다.
Personalization으로 새 버튼이나 링크를 추가하는 것도 됩니다. Region에 Create Item으로 버튼을 만들고 Destination URI에 다른 OAF 페이지나 함수를 연결하면, 자바 코드 한 줄 없이 화면 간 이동을 붙일 수 있습니다. DFF 세그먼트 노출/숨김도 여기서 처리합니다.
1-4. 개인화 이관과 관리
개인화 결과는 파일이 아니라 DB의 JDR 저장소에 XML 문서로 저장됩니다. 그래서 개발기에서 운영기로 옮기려면 XML로 뽑아서 넣어야 합니다. Functional Administrator 책임의 Personalization > Import/Export 화면을 쓰거나, 서버에서 XMLExporter/XMLImporter를 직접 실행합니다.
어떤 개인화가 걸려 있는지 확인할 때는 JDR_UTILS 패키지가 유용합니다.
-- 특정 패키지 경로에 걸린 커스터마이징 목록 조회
SET SERVEROUTPUT ON SIZE 1000000;
EXEC jdr_utils.listCustomizations('/oracle/apps/pos/supplier/webui/SuppSummPG');
-- 해당 문서 내용을 XML로 출력
EXEC jdr_utils.printDocument('/oracle/apps/pos/.../customizations/site/0/SuppSummPG');
-- 잘못 만든 개인화 삭제 (운영 반영 전 반드시 백업)
EXEC jdr_utils.deleteDocument('...위 경로...');
COMMIT;
커스텀 페이지 개발 — 만드는 순서가 정해져 있다
신규 페이지를 만들 때는 Model 계층을 먼저, View를 나중에 만듭니다. 화면부터 그리면 나중에 데이터를 붙일 때 다 갈아엎게 됩니다. 순서는 아래와 같습니다.
최소 골격 코드
AM에 조회 메서드를 하나 만들고, CO에서 호출하는 가장 기본적인 형태입니다.
// ── AM: XXInvoiceAMImpl.java ─────────────────────────
public void initInvoiceQuery(String vendorId) {
XXInvoiceVOImpl vo = getXXInvoiceVO1(); // AM에 붙인 VO 인스턴스
vo.setWhereClauseParams(null); // 이전 파라미터 초기화 (필수)
vo.setWhereClauseParam(0, vendorId);
vo.executeQuery();
}
// ── CO: XXInvoiceCO.java ─────────────────────────────
public void processRequest(OAPageContext pageContext, OAWebBean webBean) {
super.processRequest(pageContext, webBean);
OAApplicationModule am = pageContext.getApplicationModule(webBean);
String vendorId = pageContext.getParameter("VendorId");
am.invokeMethod("initInvoiceQuery", new Serializable[]{ vendorId });
}
public void processFormRequest(OAPageContext pageContext, OAWebBean webBean) {
super.processFormRequest(pageContext, webBean);
if (pageContext.getParameter("ApplyBtn") != null) {
OAApplicationModule am = pageContext.getApplicationModule(webBean);
am.invokeMethod("saveInvoice");
pageContext.forwardImmediatelyToCurrentPage(null, true, ADD_BREAD_CRUMB_NO);
}
}
setWhereClauseParams(null)을 빼먹으면 파라미터가 계속 누적돼서 두 번째 조회부터 JBO-27122 같은 오류가 납니다. VO에 조건을 넣기 전에는 항상 초기화 먼저입니다.
네이밍 규칙 — 여기서 어긋나면 배포가 안 됩니다
OAF에서 커스텀 오브젝트의 패키지 경로는 취향의 문제가 아니라 규칙입니다. 규칙을 어기면 EBS 패치가 내 파일을 덮어쓰거나, R12.2 온라인 패치 환경에서 배포 자체가 실패합니다.
커스텀 클래스의 패키지를 oracle.apps.xx...처럼 oracle로 시작하게 만들면 안 됩니다. 오라클 소유 네임스페이스를 침범하는 것이라 온라인 패치 과정에서 문제가 생깁니다. 반드시 xxkal.oracle.apps.... 형태로 커스텀 접두어를 맨 앞에 두세요. 관련 내용은 MOS Doc ID 1609939.1에 정리돼 있습니다.
Extension — 표준 화면의 동작 바꾸기
Personalization으로는 안 되고, 그렇다고 화면을 새로 만들 수도 없을 때 쓰는 방법입니다. 원리는 단순합니다. 오라클 표준 클래스를 상속받아 내 클래스를 만들고, 런타임에 표준 대신 내 것이 호출되게 바꿔치기합니다.
CO Extension 예시
package xxkal.oracle.apps.pos.supplier.webui;
import oracle.apps.pos.supplier.webui.SuppSummCO;
public class XXSuppSummCO extends SuppSummCO {
public void processRequest(OAPageContext pageContext, OAWebBean webBean) {
super.processRequest(pageContext, webBean); // ← 절대 빼먹지 말 것
// 표준 화면이 다 그려진 뒤, 내 로직 추가
OAMessageCheckBoxBean cb =
(OAMessageCheckBoxBean) webBean.findChildRecursive("TaxYnFlag");
if (cb != null) {
cb.setValue(pageContext, "Y"); // 기본값 체크 상태로
}
}
public void processFormRequest(OAPageContext pageContext, OAWebBean webBean) {
// 표준 로직 실행 전에 검증을 넣고 싶다면 super 앞에 배치
if (pageContext.getParameter("Apply") != null) {
String v = pageContext.getParameter("XXCustomField");
if (v == null || v.trim().length() == 0) {
throw new OAException("필수 항목을 입력하세요.", OAException.ERROR);
}
}
super.processFormRequest(pageContext, webBean);
}
}
super 호출을 앞에 두면 표준 동작이 끝난 뒤 내 코드가 실행되고(후처리), 뒤에 두면 표준 동작 전에 내 코드가 먼저 실행됩니다(전처리·검증). 검증은 대개 super 앞, 값 세팅은 super 뒤입니다.
배포 — R12.1과 R12.2는 절차가 다릅니다
OAF 산출물은 성격이 두 가지입니다. 자바 클래스(.class)는 파일시스템에, 페이지 정의(.xml)는 DB의 JDR 저장소에 들어갑니다. 그래서 배포도 두 갈래로 진행됩니다.
XML 임포트 명령
# 환경변수 로드 후 실행
$ import XXInvoicePG.xml \
-username apps -password <apps_pwd> \
-rootdir /home/appluser/deploy \
-dbconnection "(description=(address_list=(address=(protocol=tcp)
(host=ebsdb.example.com)(port=1521))) (connect_data=(sid=EBSPRD)))"
VO Substitution 등록 (JPX)
$ java oracle.jrad.tools.xml.importer.JPXImporter XXPOSExt.jpx \
-username apps -password <apps_pwd> \
-dbconnection "(description=(address_list=(address=(protocol=tcp)
(host=ebsdb.example.com)(port=1521))) (connect_data=(sid=EBSPRD)))"
# 임포트 후 반드시 미들티어 캐시 초기화 또는 재기동
배포했는데 화면이 그대로라면 대부분 캐시 문제입니다. Functional Administrator 책임 > Core Services > Caching Framework > Global Configuration에서 해당 캐시를 Clear 하거나, 개발 환경이라면 adopmnctl.sh로 OACore를 바운스하세요. JPX 임포트 후에는 재기동이 사실상 필수입니다.
02 — Debugging디버깅 실전
OAF는 서버에서 무슨 일이 일어나는지 보기가 까다롭습니다. 세 가지 무기를 씁니다.
1. 로그 남기기
if (pageContext.isLoggingEnabled(OAFwkConstants.STATEMENT)) {
pageContext.writeDiagnostics(this,
"[XXInvoiceCO] vendorId = " + vendorId, OAFwkConstants.STATEMENT);
}
프로파일 FND: Debug Log Enabled = Yes, FND: Debug Log Level = Statement를 켜고, FND_LOG_MESSAGES 테이블을 조회하면 됩니다.
2. About This Page 활용
페이지 하단 링크에서 Page Definition 탭을 열면 실행 중인 페이지의 Region/Item ID, 연결된 VO, 적용된 Controller 클래스가 전부 보입니다. “이 필드 이름이 뭐지?”를 추측하지 말고 여기서 확인하세요.
3. VO 쿼리 확인
ViewObject vo = am.findViewObject("XXInvoiceVO1");
pageContext.writeDiagnostics(this,
"SQL = " + vo.getQuery(), OAFwkConstants.STATEMENT);
pageContext.writeDiagnostics(this,
"ROWS = " + vo.getRowCount(), OAFwkConstants.STATEMENT);
바인드 변수가 실제로 어떻게 들어갔는지, 행이 0건인지 아닌지 여기서 바로 갈립니다.
03 — Checklist운영 반영 전 점검표
- 커스텀 패키지가
xx접두어로 시작하는가 (표준 네임스페이스 침범 없음) - 표준 오브젝트를 직접 수정한 곳이 없는가
- CO Extension에서
super호출을 빠뜨린 메서드가 없는가 - VO Extension에서 표준 컬럼의 순서나 이름을 바꾸지 않았는가
- 하드코딩된 조직 ID, 사용자 ID, 책임 ID가 없는가
- 메시지를 코드에 직접 쓰지 않고
FND_NEW_MESSAGES에 등록했는가 (다국어 대응) - 개인화를 개발기에서 운영기로 이관할 XML을 확보했는가
- JPX 임포트 후 미들티어 재기동 계획이 잡혀 있는가
- 롤백 시나리오 —
jdr_utils.deleteDocument대상 경로를 미리 기록했는가
04 — Reference참고 문서
| 문서 | 번호 | 내용 |
|---|---|---|
| OAF Developer’s Guide | 버전별 PDF | 8장 코딩 표준 필독. Oracle Help Center에 공개 |
| OAF Personalization Guide | R12.2 문서 | 개인화로 가능한 범위 전체 목록 |
| JDeveloper 패치 대응표 | Doc ID 416708.1 | EBS 버전별 JDev 패치 번호 |
| R12.2 커스터마이징 개발·배포 | Doc ID 1577661.1 | 1.6.3.5절이 OAF 확장 배포 |
| 커스텀 패키지 네이밍 | Doc ID 1609939.1 | 12.2에서의 패키지 접두어 규칙 |
| R12.2 OAF 업그레이드 고려사항 | Doc ID 1927975.1 | 기존 커스터마이징의 12.2 전환 |
정리
OAF 개발의 실력 차이는 화려한 화면을 만드는 데서 나오지 않습니다. 요구사항을 받았을 때 개인화로 끝낼지 확장으로 갈지 정확히 판단하고, 다음 패치에도 살아남는 방식으로 구현하는 것 — 이게 전부입니다. FIG.05의 결정 트리와 FIG.07의 네이밍 규칙 두 장만 몸에 붙어 있어도 대부분의 프로젝트는 무난히 굴러갑니다.
