EndpointReference Improvements
An EndpointReference is a mechanism included in the JBI specification to support sending callback information as part of a MessageExchange. The receiver can use the EndpointReference to determine the underlying Endpoint needed to address the callback MessageExchange. EPR's are created and resolved by components. The NMR just facilitates the processes.
Background
EPR's come in two flavors: External or Internal. The processing model is the same.
Internal EPR
A Internal EPR is used within the JBI environment to allow local callbacks.
External EPR
A External EPR is used outside of the JBI environment to refer to an external endpoint supported by a binding component. This type of EPR can be given to external systems that wish to
callback to a service referenced by the EPR.
Creation of an EPR
The creation of a EndpointReference(EPR) results in a DOM DocumentFragment.
private DocumentFragment getEpr(QName interfaceName, QName operationName) {
DocumentFragment result = null;
ServiceEndpoint[] endpoints = context_.getExternalEndpoints(interfaceName);
if (endpoints.length > 0) {
ServiceEndpoint endpoint = endpoints[0]; // simple choice
result = endpoint.getAsReference(operationName);
}
return result;
}
Resolution of an EPR
The resolution process involves having the NMR look for a component that understands the EPR and have the component return a ServiceEndpoint.
MessageExchange createExchangeForEPR(DocumentFragment epr, QName operationName)
throws MessagingException, Exception
{
MessageExchange result;
ServiceEndpoint endpoint = context_.resolveEndpointReference(epr);
if (endpoint != null) {
result = channel_.createExchangeFactory(endpoint).
createExchange(endpoint.getServiceName(), operationName);
}
else {
throw new Exception("Cannot resolve EPR");
}
return result;
}
ServiceEndpoint creation
The ServiceEndpoint returned by the component that understands the EPR needs to implement a minimal interface so that the NMR can handle the rest of the details.
class MyServiceEndpoint implements ServiceEndpoint {
private DocumentFragment epr_;
private QName serviceName_;
private String endpointName_;
private QName interfaceName_;
MyServiceEndpoint(DocumentFragment epr) {
epr_ = epr;
// a real binding component would pick apart the EPR, and
// determine appropriate service description items. This
// binding just makes up a fake description for the
// dynamic endpoint.
serviceName_ = new QName("http://www.example.com/services",
"service1");
endpointName_ = "endpoint1";
interfaceName_ = new QName("http://www/example.com/interfaces",
"interface1");
}
public QName getServiceName() {
return serviceName_;
}
public String getEndpointName() {
return endpointName_;
}
public DocumentFragment getAsReference(QName operation) {
return epr_;
}
public QName[] getInterfaces() {
return new QName[] { interfaceName_ };
}
}
NMR Details
The NMR takes the component returned ServiceEndpoint and wraps this in an internal class called DynamicEndpoint, which is also implements ServiceEndpoint, that allows the NMR to identify the kind of Endpoint and enforce any specific processing rules. The owner of the DynamicEndpoint is the component that created the ServiceEndpoint. This means that the NMR will route any message addressed with the DynamicEndpoint to the component that created the ServiceEndpoint.
The Issue
So everything should seem straight forward to this point.
- Create an EPR.
- Give EPR to internal/external component that wishes to perform a callback.
- Resolve an EPR.
- Use the resulting ServiceEndpoint to address a MessageExchange.
Now the issue is: What the owner of the EPR-based ServiceEndpoint does when it receives a MessageExchange?
There seems to be an unstated assumption that the Service and Endpoint names used in the creation of the EPR would be a unique name. Since an EPR is in a component based name space, the component is free to use anything it wants as a Service and Endpoint name. Typically, there is some additional context needed in handling this type of request. So using the unique name as a mapping key for the context would be straight forward.
But, what happens if the component decides, for other good reasons, to just use the same name as some InternalEndpoint that it created (most likely the one related to the ExternalEndpoint.) Nothing obviously wrong with this, since the component is in control of the name space. Component endpoint management hasn't been complicated by the addition. And the component created ServiceEndpoint can contain any extra context needed by the request. A different model, but should yield a similar result.
Unfortunately, the ServiceEndpoint created by the component is not returned to the component when MessageExhange.getEndpoint() is called. Remember from the previous section, the NMR wrapped the ServiceEndpoint in a DyanmicEndpoint to allow the NMR to attach context it needs to the process a request. Thus the component doesn't get its ServiceEndpoint back and the associated Service and Endpoint are not unique so no mapping can be provided to get to the components context information.
This issue, of contrasting assumption, came up while fixing an EPR related issue in the HTTPBC component specifically, but its likely to be a generic problem among components.
The Solution
After some quick discussions, we settled on the following:
Both interpretations seem reasonable. So we should try an support both so that component writers have a little more implementation freedom.
So the new rule is as follows:
- MessageExchange.getEndpoint() with return the NMR DynamicEndpoint if the caller is in the CONSUMER Role of the exchange.
- MessageExchange.getEndpoint() will return the component ServiceEndpoint if the caller is in the PROVIDER Role of the exchange. By asking the DynamicEndpoint for the underlying ServiceEndpoint.
This Role specific handling is similar to how Service Connections are handled. Service Connections create an alternate name linked to an existing Endpoint. The NMR uses a LinkedEndpoint object to capture the state. The alternate name is used by a specific component instead of the existing name. This allows better observability about the source of requests. The CONSUMER Role uses the alternate name (LinkedEndpoint) to address the exchange and will have the same value returned. The PROVIDER Role will see the underlying existing Endpoint.
We sacrificed a tiny bit of observability that having unique ServiceEndpoint names would have afforded. But we still know the the request came as a result of a EPR because of the DynamicEndpoint.