EBS OAF开发中的错误/异常处理(ErrorHandling)
(版权声明,本人原创或者翻译的文章如需转载,如转载用于个人学习,请注明出处;否则请与本人联系,违者必究)
总览
这篇文档描述了如何在你的Model和控制器代码中抛出OAF 框架异常.
内容
l 异常类型
l 异常类
l 批量异常
l 异常示例
l 对话框页和消息对话框
异常类型
OAF框架处理三种基本类型的异常:常规(general),验证(validation)和严重(severe).这三种类型将会做简单描述;相应的使用说明也会在下面介绍。
常规异常
在BC4J框架中,错误都是通过抛出一个隐式的(运行时)oracle.jbo.JBOException类型的异常。OAF 框架对这种异常有自己的专门的实现
oracle.apps.fnd.framework.OAException。
这种专门的实现提供了一种把多个异常捆绑在一起的机制,并且也可以通过Oracle E-Business Suite 消息字典来翻译异常消息,这样就可以显示有用的信息。在你的任何代码中,你可以为常规的,页面级的异常抛出一个OAException。
验证异常
可以从实体对象或者视图对象为行级别或者属性级别的失败抛出验证异常.
l oracle.apps.fnd.framework.OAAttrValException - 用于实现属性级别验证失败的OAException.
l oracle.apps.fnd.framework.OARowValException - 用于实现行级别验证失败的OAException的
OAF 框架像下面显示错误给用户:
l 属性级别异常显示在出错的Item(s)和页的上方.
l 行级别的异常显示在出错的行和也的上方.
l 页级别的异常显示在页的上方.
严重异常
严重(或者致命)异常包括意外的系统级别的错误(比如NullPointerException)和特别的JBOException,比如NoDefException.你也可以在你的代码中故意抛出一个严重异常.
如果一个严重异常发生,用户会被导向到OAErrorPage(如果严重异常发生在页面生成过程之中, 部分页面生成并包含一个用户友好的错误消息,其中包含了一个链接到详细堆栈跟踪的链接).OAErrorPage也显示了一个用户友好的错误消息,其中包含了一个链接到详细堆栈信息链接.
注意:这是一个未翻译的消息,但用户可以在site上进行修改。
Oracle Workflow通知
OAF框架也提供了一个内置的业务事件(oracle.apps.fnd.framework.OAFatalError), 不论什么时候OAF页面报告了一个严重的异常,它都可以发送通知给SYSADMIN用户。通知包含了严重异常的详细错误堆栈和关于哪个用户遇到这个异常的信息.如果你希望修改通知的默认接收人从SYSADMIN为其它人,你需要定制Item Type OAERROR的定义。
默认对这个业务事件的订阅是禁止的。要启用订阅,请参阅Oracle Workflow文档如何启用业务事件的订阅.
l 如果你使用的是OracleWorkflow Rlease 11i/2.6,Oracle Applications原先的界面,请看"To Update orDelete an Event Subscription", Oracle Workflow Developer's Guide.
l 如果你使用的是基于OAF框架的业务事件系统的界面,请参考"ToView and Maintain Event Subscriptions", Oracle Workflow Developer'sGuide.
异常类
OAF框架异常的继承层次就像下面的图1所示.基类OAException继承自JBOException.OAAttrValException和OARowValException继承自OAViewObjectException,一个继承自OAException并不再赞成使用的类。
OAException
OAException 是用来抛出常规异常时用的异常(比如,如果你在一个控制器代码中碰到了一个意外的问题,如下面的代码)
OACellFormatBean shipTermsCell = (OACellFormatBean)webBean.findIndexedChildRecursive("ShipTermsCell"); if (shipTermsCell == null) { MessageToken[] tokens = { new MessageToken("OBJECT_NAME","ShipTermsCell")}; throw new OAException("AK","FWK_TBX_OBJECT_NOT_FOUND", tokens); }
注意我们在AK应用中在Oracle E-Bussiness Suite消息字典创建了消息(FWK_TBX_OBJECT_NOT_FOUND).消息定义中包含了标识(OBJECT_NAME),我们将会用我们期望找到的UI组件的名字来替换.OAF框架将使用这个消息在页面上面自动显示一个翻译过的错误消息(如果你实例化一个OAException,并且不指明消息类型,那么它一般显示为错误).
注意:虽然它不是一个明确的编程标准,但是包装一个异常而不是创建一个新的异常是一个好的编程习惯而且有好处的.
包装一个异常来创建一个新的包含原来异常作为详细异常的OAException。通过包装一个异常,核心异常会被报告,OAF框架监测到原先的异常来决定它的严重度.如果创建了一个新的异常,你就得不到这些信息,异常堆栈就停止在你的代码,导致BUG中的信息很少.
因为OAException是一个灵活的对象,也可以用来显示其他类型的消息(信息,确认和异常),,你也可以用下面展示确认示例中的消息类型来实例化它(参看对话框也和消息对话框部分,描述如何使用这些功能来显示信息,确认和警告消息).
MessageToken[] tokens = { new MessageToken("SUPPLIER_NAME", name), new MessageToken("SUPPLIER_NUMBER", supplierId) }; OAException confirmMessage = new OAException("AK", "FWK_TBX_T_SUPPLIER_CREATE_CONF", tokens, OAException.CONFIRMATION, null);
消息类型
OAException, OAAttrValException, 和OARowValException类都包含了接受消息类型作为参数的构造器.消息类型参数告诉OAF框架要显示给用户的消息类型.有效的选项包括:
l OAException.ERROR
l OAException.WARNING
l OAException.INFORMATION
l OAException.CONFIRMATION
l OAException.SEVERE
OAAttrValException
如果在视图对象行或者实体对象中,任意属性级别的验证失败,你都可以像如下抛出一个OAAttrValException.
要实例化这个异常,你必须传递下面的信息:
l 源对象类型(OAException.TYP_ENTITY_OBJECT或者OAException.TYP_VIEW_OBJECT)
l 完整的实体定义的名称或者视图实例的名字
l 实体或者行的主键
l 正验证的属性名
l 验证失败的属性值
l 错误消息所属的应用缩写名
l 错误消息名称
实体对象示例
public void setSalary(Number value) { if (value != null) { // Verify value is > 0 if (value.compareTo(0) <= 0) { throw new OAAttrValException(OAException.TYP_ENTITY_OBJECT, // indicates EO source getEntityDef().getFullName(), // entity name getPrimaryKey(), // entity primary key "Salary", // attribute Name value, // bad attribute value "AK", // nessage application short name "FWK_TBX_T_EMP_SALARY_REQUIRED"); // message name } setAttributeInternal(SALARY, value); } } // end setSalary()
视图行示例
关于从视图行抛出这类异常的额外信息请参考下面的映射部分
setDescription(String value) { if("XXX".equals(value) { throw new OAAttrValException ( OAException.TYP_VIEW_OBJECT, // indicates VO row source getViewObject().getFullName(), //View Object full usage name getKey(), // row primary key "Description", //attribute name value, // bad attribute value "FND", //message application short name "ATTR_EXCEPTION"); // message name } setAttributeInternal("Description", value); } // end setDescription()
OARowValException
如果在视图对象行或者实体对象上的任意的行级别的验证失败,你可以像下面展示的抛出一个OARowValException
要示例化这个异常,你必须传入以下的信息:
l 完整的实体定义名称或者视图对象名称
l 实体或者行的主键
l 错误消息所属的应用缩写名
l 错误消息名
实体对象示例
protected void validateEntity() { super.validateEntity(); if(attr1!=attr2) throw new OARowValException ( getEntityDef().getFullName(), // entity full definition name getPrimaryKey(), // entity object primary key "FND", // message application short name "ATTR_EXCEPTION"); // message name }
视图行示例
关于从视图行抛出这类异常的额外信息请参考下面的映射部分
protected void validate() { super.validate(); if(attr1!=attr2) throw new OARowValException ( getViewObject().getFullName(),//View Object full usage name getKey(), // row primary key "FND", // message application short name "ATTR_EXCEPTION"); // message name }
覆盖行级别的错误前缀
当OAF框架在表中生成行或者属性错误或者警告消息时,消息由两部分组成:行前缀+错误消息.比如:
l Row 2 Error: <Some error messagerelating to the entire row 2>
l Row 2 <AttributePrompt>: <Some error message relating to the given attribute inRow 2>
你可以选择覆盖这个前缀如果默认的行引用对你的界面不合适,比如:
l Line 2-3 Error: <Some error messagerelating to this row in the table>
l Line 2-3 <Attribute Prompt>:<Some error message relating to the given attribute in this designatedrow>
要实现这个:
第一步:创建一个临时的视图对象属性,其中包含要作为行前缀的以翻译的字符串
第二步:为视图对象创建一个自定义的属性
l 设置名称(Name)为ROW_DISPLAY_PREFIX_ATTR_NAME
l 设置值(Value)为第一步中创建的属性名.
当处理和这个视图对象相关的异常时,OAF框架将检查这个自定义的属性是否设置了,如果是,将使用指定的属性值作为行前缀.
注意:为保持一致,OAF框架应用这个前缀到你产生的任意错误或者警告消息,加上它内部生成的任意行级别的消息。
映射实体对象属性到视图对象属性
当创建自定义的视图行方法,并抛出源自于实体对象的异常时,你必须在异常上调用doEntityToVOMapping来创建在实体对象属性和视图对象属性之间的映射,示例如下:
/** * Approves the purchase order associated with this row. */ public void approve() { // Whenever you write custom methods on the VO Row that call custom methods // on the Entity Object you need to do a manual mapping as shown below // to correctly handle the entity exceptions. try { getPurchaseOrderHeaderEO().approve(); } catch(OARowValException e) { OAViewObject[] vos = {(OAViewObject)getViewObject()}; e.doEntityToVOMapping(getApplicationModule(), vos); throw e; } } // end approve()
除了这种情况,OAF框架在以下的方法中为抛出异常调用这个方法:
l viewRow.setAttribute()
l viewRow.validate() (捕捉所有抛自eo.validate()的异常)
l create(AttributeList)
l viewRow.remove()
注意:如果你覆盖这些方法,这个映射是当你调用super的时候执行的.如果你的覆盖代码隐式的抛出实体对象异常,那么你需要调用doEntityToVOMapping
集束异常(BundledExceptions)
Bundled exceptions 允许你在进行验证的时候叠加同等的异常,当你完成验证的时候一块显示给用户。这些同等的异常被分组到一个叫做bundled exception 的容器异常中.
Bundled exceptions可以包含任意类型的服务器端的异常(包括系统级的异常,数据格式错误,属性验证错误,行验证错误以及实体创建错误).
同等异常列表
要创建一个bundled exception,首先必须创建一个列表,这样当你遇见异常的时候就把它加入到这个列表中去.
ArryList peerExceptions = new ArrayList(); peerExceptions.add(new OAException(....)); peerExceptions.add(new OAException(....)); ...
Bundled Exception
当你准备抛出你的bundled exception,调用OAException.getBundledOAExceptioncon根据传入的同等异常列表来创建一个bundled OAException,或者调用OAException.raiseBundledOAException来创建并立即抛出一个bundledOAException。
l 注意对OAAttrValException 和OARowValException也有相似的API。
l 参考OA*Exception类中的各种用于与bundledOAException交互的访问方法(记住bundled Exception本身只是一个简单的包含同等异常数组的容器).
在实体和行验证过程中,如果你不想自己打包,你也可以注册异常.这些异常将会在验证完成的时候或者当一个异常被明确抛出的时候抛出来,请参考下面的例子(Javadoc: oracle.apps.fnd.framework.server.OAEntityImpl 和oracle.apps.fnd.framework.server.OAViewRowImpl).
BC4J集束异常模式
当这种模式被禁用时,所有由实体属性访问方法抛出的异常都会立刻抛给调用的视图对象行,然后再把异常抛给调用者。当你启用集束异常模式,BC4J会把实体属性访问方法抛出的异常放进堆栈,并在valdiateEntity结束抛出这些异常,或者当valdiateEntity抛出一个异常的时候抛出这些异常.所有这些异常都会打包成一个单独的异常返回给调用者.
你可以使用下面的代码来启用集束异常模式:
OADBTransaction.setBundledExceptionMode(true);
默认,这种模式是被禁止的。我们建议不要使用这个功能,因为在没有启用它的时候OAF框架会代表你收集所有的异常。
异常示例
示例1
下面的代码示例展示了如何捕获异常并把它们作为一个单独的集束异常抛出来。
public void foo() { ArrayList exceptions = new ArrayList(); for(int ...; ...; ...) { if(.....) { exceptions.add(new OAException(.....)); } } OAException.raiseBundledOAException(exceptions); }
示例2
下面的代码示例展示了如何缓存validateEntity()方法中抛出的异常,然后把缓存的异常作为一个集束异常抛出来。
protected void validateEntity() { super.validateEntity(); ArrayList exceptions = new ArrayList(); //check for duplicate Filter Name if (getEntityState() == STATUS_NEW) { String value = getFilterName(); OADBTransaction tx = getOADBTransaction(); OAApplicationModule vam = getMyValidationAM(); FiltersVOImpl vo = vam.findViewObject("filtersViewUsage"); if (vo == null) { vo = vam.createViewObject("filtersViewUsage","oracle.apps.qrm.filter.server.FiltersVO"); vo.setMaxFetchSize(-1); vo.initQuery(value,"C"); Row r = vo.first(); if (r != null) { exceptions.add( new OAAttrValException ( OAException.TYP_ENTITY_OBJECT, // Entity attribute exception. getEntityDef().getFullName(), //Entity full def name getPrimaryKey(), //Row primary key "FilterName", //Attribute Name value, //Bad Value "QRM", //Message Application Short Code "QRM_UNIQUE_FILTERS_ERR")); //Message Code } } //check for empty filters(no conditions) EntityDefImpl def = EntityDefImpl.findDefObject("oracle.apps.qrm.filter.server.QrmFilterConditionsEO"); Iterator iterator = def.getAllEntityInstancesIterator(getDBTransaction()); String flag = "no"; while (iterator.hasNext()) { QrmFilterConditionsEOImpl fcEO = (QrmFilterConditionsEOImpl)iterator.next(); // only check rows in valid state if ( fcEO.getEntityState() != STATUS_DELETED && fcEO.getEntityState() != STATUS_DEAD ) { flag = "OK"; } } if (flag.equals("no")) { exceptions.add( new OARowValException ( getEntityDef().getFullName(), getPrimaryKey(), //Row primary key "QRM", //Message Application Short Code "QRM_NO_CONDITIONS_ERR")); //Message Code } OAException.raiseBundledOAException(exceptions); }
示例3
下面的代码示例展示了如何缓存一个视图对象方法中抛出的异常,然后把缓存的异常作为一个集束异常抛出来。
public void checkUsed() { String ifSelected = null; String name; ArrayList exceptions = new ArrayList(); FiltersVORowImpl row = (FiltersVORowImpl)first(); while (row != null) { ifSelected = (String)row.getAttribute("SelectFlag"); if ("Y".equals(ifSelected)) { name = (String)row.getAttribute("FilterName"); OAViewObjectImpl vo = (OAViewObjectImpl)getApplicationModule().findViewObject("IsFilterUsedVO"); vo.setWhereClause(null); vo.setWhereClauseParams(null); vo.setWhereClauseParam(0,name); vo.executeQuery(); Row r = vo.first(); //if there are analyses, then use them if (r != null) { String msg= (String)r.getAttribute("AnalysisName"); String flag ="f"; while (r != null) { //change flag if it was the first row,if not append analysis name if (flag.equals("f")) flag = "N"; else msg = msg +", "+ (String)r.getAttribute("AnalysisName"); r = vo.next(); } MessageToken[] tokens = {new MessageToken("FILTER_NAME",name), new MessageToken("ANALYSIS",msg)}; exceptions.add( new OARowValException( getViewObject().getFullName(), row.getKey(), "QRM", "QRM_FILTER_REMOVE_ERR", tokens)); } } row =(FiltersVORowImpl)next(); } OAException.raiseBundledOAException(exceptions); }
示例4
下面的代码示例展示了如何在set<Attribute>()注册一个验证异常以便在之后的实体验证中抛出这个异常。
public void setAmount(oracle.jbo.Number amnt) { // Clears any old exceptions for a fresh start. clearAttributeException("Amount"); if(amnt < 0) { OAAttrValException attrEx = new OAAttrValException( OAAttrValException.TYP_ENTITY_OBJECT, getEntityDef().getFullName(), getPrimaryKey(), "Amount", amnt, "QRM", "QRM_AMOUNT_IS_NEGATIVE"); registerAttributeException(getEntityDef().getAttributeDefImpl("Amount"),amnt, attrEx); } }
示例5
下面的代码示例展示了如何注册在validateEntity()过程中抛出的异常以便BC4J在验证结束的时候抛出这些异常。
protected void validateEntity() { super.validateEntity(); // Clears all Row and Attribute exceptions registered in validateEntity() for a fresh start. clearAttributeException("FilterNAme"); clearRowExceptions(); //check for duplicate Filter Name if (getEntityState()==STATUS_NEW) { String value = getFilterName(); OADBTransaction tx = getOADBTransaction(); OAApplicationModule vam = getMyValidationAM(); FiltersVOImpl vo = vam.findViewObject("filtersViewUsage"); if(vo == null) { vo = vam.createViewObject("filtersViewUsage", "oracle.apps.qrm.filter.server.FiltersVO"); } vo.setMaxFetchSize(-1); vo.initQuery(value,"C"); Row r = vo.first(); if (r != null) { OAAttrValException attrEx = new OAAttrValException ( OAException.TYP_ENTITY_OBJECT, // Entity attribute exception. getEntityDef().getFullName(), //Entity full def name getPrimaryKey(), //Row primary key "FilterName", //Attribute Name value, //Bad Value "QRM", //Message Application Short Code "QRM_UNIQUE_FILTERS_ERR")); //Message Code registerAttributeException(getEntityDef().getAttributeDefImpl("FilterName"), value, attrEx); } } //check for empty filters(no conditions) EntityDefImpl def = EntityDefImpl.findDefObject("oracle.apps.qrm.filter.server.QrmFilterConditionsEO"); Iterator iterator = def.getAllEntityInstancesIterator(getDBTransaction()); String flag = "no"; while (iterator.hasNext()) { QrmFilterConditionsEOImpl fcEO = (QrmFilterConditionsEOImpl)iterator.next(); // only check rows in valid state if ( fcEO.getEntityState() != STATUS_DELETED && fcEO.getEntityState() != STATUS_DEAD ) flag = "OK"; } if (flag.equals("no")) { registerRowException( new OARowValException ( getEntityDef().getFullName(), getPrimaryKey(), //Row primary key "QRM", //Message Application Short Code "QRM_NO_CONDITIONS_ERR")); //Message Code } }
对话框页面和消息对话框
关于显示模态错误,消息,警告和确认对话框页面的信息,请参考Chapter 4: Implementing Dialog Pages..
关于在页面顶部显示错误,消息,警告和确认消息对话框(但不是像抛出一个异常那样简单自动),请参考Chapter 4: Implementing Message Boxes.