Jednou ze součástí
Oracle SOA Suite je tzv.
Fault Management Framework, který se mmj. stará o zpracování výjimek v
BPELu. Pokud během
invoke aktivity nastane výjimka, framework ji odchytí a předá ji ke zpracování akci, která je definovaná ve
fault policy.
Tyto politiky jsou zajímavou alternativou k odchytávání výjimek v samotném BPELu pomocí
fault handleru <catch>. Dá se na ně pohlížet jako na aspekt (ve smyslu
AOP), který je deklarativně navěšený na procesu (nebo i celé kompozitní aplikaci).
No, pokud jsem řekl aspekt, pravděpodobně jsem vzbudil velká očekávání. Tak to bohužel není - sám jsem musel pohřbít některé své designové představy - má to spoustu omezení, resp. možnosti nejsou rozsáhlé. Takže co to vlastně umí?
V rámci politiky se dají definovat následující zotavné akce:
- Retry. Nepovedený invoke se dá zopakovat. Dá se nastavit počet opakování, prodleva, exponenciální prodleva a následující zřetězená akce, pokud retry nezafunguje.
- Human Intervention. Proces se zastaví a je možné ho manuálně obnovit z management konzole.
- Terminate Process. Proces je ukončen. Totéž jako aktivita.exit.
- Rethrow Fault. Chyba je vyhozena zpátky do BPEL procesu.
- Replay Scope. Vyhodí reply fault, což způsobí znovu vykonání aktivit ve scope.
- Java Code. Zavolá externí Java třídu. To je to, na co se budeme dále soustředit.
Problém
Mám pro klienty vystavenou službu
SOAPFault, která má ve svém kontraktu definovaný
SOAP Fault:
<xsdelement name="fault">
<xsdcomplexType>
<xsdsequence>
<xsdelement name="resultCode"
type="xsd:string"/>
<xsdelement name="error">
<xsdcomplexType>
<xsdsequence>
<xsdelement name="errorCode"
type="xsd:string"/>
<xsdelement name="errorDescription"
type="xsd:string"/>
</xsdsequence>
</xsdcomplexType>
</xsdelement>
</xsdsequence>
</xsdcomplexType>
</xsdelement>
Pro graficky orientované to vypadá takhle:
Tento fault je pak použit ve WSDL:
<wsdlmessage name="faultMessage">
<wsdlpart name="payload" element="inp1:fault"/>
</wsdlmessage>
<wsdlportType name="SOAPFaultPort">
<wsdloperation name="test">
<wsdlinput message="tns:requestMessage"/>
<wsdloutput message="tns:replyMessage"/>
<wsdlfault name="testfault"
message="tns:faultMessage"/>
</wsdloperation>
</wsdlportType>
Služba je implementovaná pomocí BPEL procesu, který v rámci orchestrace volá další službu, nazvanou
BPELFault:
Tak, to byla expozice. Teď přijde kolize. V BPELu mám definovanou
invoke aktivitu, která volá externí službu
BPELFault. Chtěl bych použít politiku tak, aby když externí služba vrátí chybu, aby mi politika nastavila můj definovaný SOAP fault a proces ho vrátil klientovi.
Než se pustíme do implementace politiky, musíme v BPEL procesu ještě splnit dvě podmínky. Jednak v procesu definovat
proměnnou, která bude mít nastavený typ zprávy jako daný fault:
<variable name="soapFault"
messageType="ns1:faultMessage"/>
A druhak, proces musí náš fault vyhodit ven pomocí aktivity
throw:
<catchAll>
<throw name="ThrowFault"
faultName="ns1:testfault"
faultVariable="soapFault"/>
</catchAll>
Jak to celé funguje?
Obrázek je za tisíc slov, takže tady je BPMN diagram. Swimliny jsou trochu nečitelný :-/ takže odshora:
BPEL, External WS, Fault Management Framework a
Java Class.
Java
Java třída, která bude z politiky volaná musí implementovat rohraní
IFaultRecoveryJavaClass. To má dvě metody, nás bude zajímat pouze
handleFault, která má jako parametr
IFaultRecoveryContext. Pomocí tohoto kontextu lze přistupovat k objektům v BPEL procesu, odkud vyletěla výjimka.
Z kontextu si tak vytáhneme výše uvedenou BPEL proměnnou
soapFault a pomocí
XPath, nebo
DOMu ji naplníme. Na závěr vrátíme z metody string
RETHROW, aby politika vrátila řízení zpátky do BPEL procesu, odkud je pak už vrácena SOAP fault klientovi.
(Omlouvám se, že ten kód uvádím celý. Ale kdyby náhodou to někdo (v Česku) řešil, tak ať to má trochu jednodušší :-)
package cz.swsamuraj.soa.fault;
import com.collaxa.cube.engine.fp.BPELFaultRecoveryContextImpl;
import
oracle.integration.platform.faultpolicy.IFaultRecoveryContext;
import
oracle.integration.platform.faultpolicy.IFaultRecoveryJavaClass;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
This class is called by fault policy.
@author
public class SOAPFaultHandler implements IFaultRecoveryJavaClass {
private static final String VARIABLE = "soapFault";
private static final String PART = "payload";
private static final String RESULT_CODE_NAME = "resultCode";
private static final String RESULT_CODE_VALUE = "255";
private static final String ERROR_NAME = "error";
private static final String ERROR_CODE_NAME = "errorCode";
private static final String ERROR_CODE_VALUE =
"EXTERNAL_SERVICE_ERROR";
private static final String ERROR_DESCRIPTION_NAME =
"errorDescription";
private static final String RESULT = "RETHROW";
@Override
public void handleRetrySuccess(
IFaultRecoveryContext iFaultRecoveryContext) {
}
Method for set up a BPEL variable for SOAP fault.
@param recoveryContext
@return
@Override
public String handleFault(IFaultRecoveryContext recoveryContext) {
if (recoveryContext instanceof BPELFaultRecoveryContextImpl) {
BPELFaultRecoveryContextImpl bpelContext =
(BPELFaultRecoveryContextImpl) recoveryContext;
Element root =(Element)
bpelContext.getVariableData(VARIABLE,
PART, "/");
Node soapFault = root.getFirstChild();
String namespaceURI = soapFault.getNamespaceURI();
bpelContext.addAuditTrailEntry(String.format(
"Obtained a root element: {%s}%s.", namespaceURI,
soapFault.getLocalName()));
if (soapFault.hasChildNodes()) {
int count = soapFault.getChildNodes().getLength();
for (int i = 0; i < count; i++) {
Node child = soapFault.getFirstChild();
soapFault.removeChild(child);
}
}
Document document = soapFault.getOwnerDocument();
Node resultCode = createNode(document, namespaceURI,
RESULT_CODE_NAME, RESULT_CODE_VALUE);
soapFault.appendChild(resultCode);
Node error = createNode(document, namespaceURI,
ERROR_NAME, null);
soapFault.appendChild(error);
Node errorCode = createNode(document, namespaceURI,
ERROR_CODE_NAME,
ERROR_CODE_VALUE);
error.appendChild(errorCode);
Node errorDescription = createNode(document, namespaceURI,
ERROR_DESCRIPTION_NAME,
bpelContext.getFault()
.toString());
error.appendChild(errorDescription);
bpelContext.addAuditTrailEntry(String.format(
"SOAP fault response has been set"
+ " by fault policy '%s'.",
bpelContext.getPolicyId()));
}
return RESULT;
}
Method creates a new {@link Node} with given parameters.
@param document{@link Document}
@param qualifiedName
@param namespaceURI
@return{@link Node}
private Node createNode(Document document, String namespaceURI,
String qualifiedName, String textContent) {
Node node = document.createElementNS(namespaceURI,
qualifiedName);
if (textContent != null) {
node.setTextContent(textContent);
}
return node;
}
}
Zajímavá je zde asi pouze metoda kontextu
addAuditTrailEntry, která propisuje informace do auditního záznamu procesu, což je pak vidět v konzoli
Enterprise Managera:
Definice politiky
Už máme ruce, teď mozek. Aby politika byla funkční, je potřeba vytvořit dva soubory. Jednak samotnou definici, soubor
fault-policies.xml a potom navázání politiky na konkrétní kompozitiní aplikaci (nebo komponent), soubor
fault-bindings.xml. Toto jsou defaultní názvy souborů. Pokud chceme jiné názvy, např. protože chceme politiky verzovat, je potřeba tuto odlišnost uvést v definici kompozitní aplikace.
<property name="oracle.composite.faultPolicyFile">
fault-policies-v1.0.xml</property>
<property name="oracle.composite.faultBindingFile">
fault-bindings-v1.0.xml</property>
Nebo graficky:
V definičním souboru
fault-policies-v1.0.xml říkáme, že chceme odchytávat výjimku
remoteFault (není to standardní chyba definovaná BPELem, ale Oracle BPEL Extension) a chceme, aby byla zpracovaná naší Java třídou:
xml version='1.0' encoding='UTF-8'
<faultPolicies xmlns="http://schemas.oracle.com/bpel/faultpolicy">
<faultPolicy version="2.0.1" id="BpelInvokeActivityFaults-v1.0">
<Conditions>
<faultName
xmlnsbpelx="http://schemas.oracle.com/bpel/extension"
name="bpelx:remoteFault">
<condition>
<action ref="ora-java"/>
</condition>
</faultName>
</Conditions>
<Actions>
<Action id="ora-java">
<javaAction
className="cz.swsamuraj.soa.fault.SOAPFaultHandler"
defaultAction="ora-rethrow">
<returnValue value="RETRHOW" ref="ora-rethrow"/>
</javaAction>
</Action>
<Action id="ora-rethrow">
<rethrowFault/>
</Action>
</Actions>
</faultPolicy>
</faultPolicies>
Ve vazebním souboru
fault-bindings-v1.0.xml jenom říkáme, že naše politika
BpelInvokeActivityFaults-v1.0 je svázaná s celou kompozitní aplikací:
xml version='1.0' encoding='UTF-8'
<faultPolicyBindings
version="2.0.1"
xmlns="http://schemas.oracle.com/bpel/faultpolicy">
<composite faultPolicy="BpelInvokeActivityFaults-v1.0"/>
</faultPolicyBindings>
Deployment do runtime
Pokud máme třídu i politické :-) soubory přímo v kompozitní aplikaci, nemusíme deployment nijak řešit - prostě kompozitní aplikaci standardně nasadíme. Pokud však chceme politiky externalizovat (protože je chceme přepoužít pro více kompozitek), je vhodné umístit XML soubory do
MDS (Metadata Services repository) a Java třídu nasadit do runtimu SOA Suity:
- Zkompilovanou třídu zabalíme do JAR souboru.
- JAR soubor nakopírujeme do adresáře <ORACLE_HOME>/Oracle_SOA1/soa/modules/oracle.soa.ext_11.1.1
- V tomto adresáři spustíme příkaz ant.
- Restartujeme WebLogic.
Související články
Odkazy