The pluggable
overlay
mediates and relays requests to a wrapped space.
The support for pre-processing of requests,
post-processing of responses and handling or exceptions provides developers with
a simple framework for building sophisticated customized
overlays.
The pluggable overlay is transparent - all of the endpoints in the wrapped
space are surfaced in the host space.
The processing steps are illustrated in this diagram:
- 1 A request is received by the pluggable overlay
- 1a If a preProcess request is specified, it is issued.
- 1b The preProcess request returns a new INKFRequest in the response.
- 2 Pluggable overlay issues either the original or the new INKRequest into space
- 3 if Pluggable overlay received response
- 3a If a postProcess request is specified, it is issued.
- 3b The postProcess request returns a response
- 4 or Pluggable overlay receives exception
- 4a If an exceptionProcess request is specified, it is issued.
- 4b The exceptionProcess request returns a response
- 5 Pluggable overlay returns the response.
The pluggable overlay wraps a space and supports a pre-process, post-process and an exception-process request
that follows the
standard declarative request syntax:
<pluggable-overlay>
<preProcess>
<!---->
</preProcess>
<space />
<postProcess>
<!---->
</postProcess>
<exceptionProcess>
<!---->
</exceptionProcess>
</pluggable-overlay>
preProcess
The preProcess service can either act as a filter stopping further
processing of the request if a criteria is not satisfied,
or as a request adapter, changing the
incoming request before it is passed into the wrapped space.
The preProcess request must return an instance of
INKFRequest
as the representation.
When the pluggable overlay receives a request (1) it checks to see
if a preProcess request is specified.
If present, it issues this request (1a) and uses the
INKFRequest
returned as the representation in response (1b) as the request
issued into the wrapped space (2).
If preProcess throws an exception then the sub-request (2) is not
issued to the wrapped space, post-processing is not executed
and the exception is returned as the final response (4).
The inbound request (1) that the pluggable overlay receives
should be passed in the preProcess request;
it is referred to as arg:request,
as illustrated in this example:
<pluggable-overlay>
<preProcess>
<identifier>active:audit</identifier>
<argument name="operand">arg:request</argument>
</preProcess>
<space>
...
</space>
</pluggable-overlay>
Example
The following pre-processing accessor implements a simple auditing
function by sending the request identifier to the
System.out console stream.
Note that it creates a clone of the initial request (1)
and returns this as its representation (1b);
the pluggable overlay will use this for the inner request (2).
public class AuditAccessor extends StandardAccessorImpl
{
public void onSource(INKFRequestContext context) throws Exception
{
INKFRequestReadOnly initialRequest = context.source("arg:operand", INKFRequestReadOnly.class);
System.out.println(initialRequest.getIdentifier());
INKFRequest innerRequest = initialRequest.getIssuableClone();
context.createResponseFrom(innerRequest);
}
}Example
The following pre-processing accessor provides a template for accessors
that need to adapt the initial request (1) before it is issued into the wrapped space (2).
This template creates a faithful copy of the initial request; accessors that need to
adapt the request can modify this template to create a modified request.
public class PreProcessExplicitRelayAccessor extends StandardAccessorImpl
{
public void onSource(INKFRequestContext context) throws Exception
{
INKFRequestReadOnly initialRequest = context.source("arg:operand", INKFRequestReadOnly.class);
INKFRequest innerRequest = context.createRequest(initialRequest.getIdentifier());
innerRequest.setVerb(initialRequest.getVerb());
innerRequest.setRepresentationClass(initialRequest.getRepresentationClass());
INKFResponseReadOnly primaryResponse = initialRequest.getPrimaryAsResponse();
if (primaryResponse!=null)
{
innerRequest.addPrimaryArgumentFromResponse(primaryResponse);
}
for (String key: initialRequest.getHeaderKeys())
{
for (Object value : initialRequest.getHeaderValues(key))
{
innerRequest.setHeader(key, value);
}
}
context.createResponseFrom(innerRequest);
}
}postProcess
The postProcess service provides an opportunity to implement a wide range
of architectural patterns.
Essentially the postProcess service can modify the response (3) by
changing either the representation or the
configuration of the response.
This modification can be based on just the response (3) or from
information
contained in the initial request
(1), such as
the identifier or the resolved logical endpoint identifier.
The postProcess request must return an instance of
INKFResponseReadOnly
as the representation.
When the pluggable overlay receives a response (3) to the request (2),
it checks to see if a postProcess request is specified.
If present, it issues this request (3a) and uses the
INKFResponseReadOnly
returned as the representation in response (3b)
as the final response (4).
If the postProcess request needs to provide the
INKFRequestReadOnly
issued into the wrapped space (2) or the
INKFResponseReadOnly
returned from the wrapped space (3) as arguments,
this example illustrates how do specify this:
<pluggable-overlay>
<postProcess>
<identifier>active:postprocess</identifier>
<argument name="request">arg:request</argument>
<argument name="response">arg:response</argument>
</postProcess>
<space>
...
</space>
</pluggable-overlay>
Example
The following post-processing accessor sets the HTTP expiry to be one day
if the initial request specified a GIF resource.
public class ExpiryAccessor extends StandardAccessorImpl
{
private static final long DAY=1000*60*60*24;
public void onSource(INKFRequestContext context) throws Exception
{
INKFRequestReadOnly initialRequest = context.source("arg:request", INKFRequestReadOnly.class);
String identifier = initialRequest.getIdentifier();
INKFResponseReadOnly innerResponse=context.sourceForResponse("arg:response");
INKFResponse response=context.createResponseFrom(innerResponse);
if (identifier.endsWith(".gif"))
{
response.setMetaData("httpResponse:/header/Expires", System.currentTimeMillis()+DAY);
}
}
}Example
The following post-processing accessor performs an XSLT transformation
of the returned result to transform the returned XML to XHTML.
The following post-processing accessor sets the expiry for a response to be one day.
public class TransformOutputAccessor extends StandardAccessorImpl
{
public void onSource(INKFRequestContext context) throws Exception
{
Object representation = context.source("arg:response");
INKFRequest request = context.createRequest("active:xslt");
request.addArgument("operator", "res:/resources/convertToXHTML.xsl");
request.addArgumentByValue("operand", representation);
INKFResponseReadOnly innerResponse = context.issueRequestForResponse(request);
context.createResponseFrom(innerResponse);
}
}exceptionProcess
The exceptionProcess service provides an opportunity to implement a wide range
of exception handling patterns.
Essentially the exceptionProcess service can modify the default behaviour or propagating the unhandled exception (4) by
either wrapping the exception or performing some remedial action.
When the pluggable overlay receives an exception (4) from the request (2),
it checks to see if a exceptionProcess request is specified.
If present, it issues this request (4a) and uses the response
returned (3b) as the final response (4).
If the exceptionProcess request needs to provide the
INKFRequestReadOnly
issued into the wrapped space (2) or the
NKFException
thrown from the wrapped space (4) as arguments,
this example illustrates how do specify this:
<pluggable-overlay>
<exceptionProcess>
<identifier>active:exceptionprocess</identifier>
<argument name="request">arg:request</argument>
<argument name="exception">arg:exception</argument>
</exceptionProcess>
<space>
...
</space>
</pluggable-overlay>
Example
The following exception-processing accessor wraps the exception with another exception to show it was handled
public class ExceptionHandlerAccessor extends StandardAccessorImpl
{
public void onSource(INKFRequestContext context) throws Exception
{
INKFRequestReadOnly initialRequest = context.source("arg:request", INKFRequestReadOnly.class);
String identifier = initialRequest.getIdentifier();
NKFException ex=(NKFException)aContext.source("arg:exception",WrappedThrowable.class).getThrowable();
NKFException ex2 = new NKFException("Handled","Exception thrown by "+identifier, ex);
INKFResponse response=context.createResponseFrom(ex2);
}
}Unhandled Exceptions
If an exception is thrown while any of the process-requests are being executed
that exception is percolated back to the initial requestor (4).
It is important to note that the pluggable overlay is not transactional.
This means that if the request (2) results in a state change and the processing
of request (3a) causes an exception then that exception is percolated
back (4) and the state change remains.