Difference between revisions of "Refining the implementation"
(→A Faultful Interface) |
(→Logging it!) |
||
(20 intermediate revisions by the same user not shown) | |||
Line 27: | Line 27: | ||
* <code>GCUBEUnrecoverableException</code> ~ GCUBEUnrecoverableFault | * <code>GCUBEUnrecoverableException</code> ~ GCUBEUnrecoverableFault | ||
− | gCube exceptions and gCube faults are freely convertible. Typically, a gCube exception raised within the service should be converted into a gCube fault before it escapes the service. Similary, a gCube fault received from interacting with another service ought to be converted into a gCube exception before it | + | gCube exceptions and gCube faults are freely convertible. Typically, a gCube exception raised within the service should be converted into a gCube fault before it escapes the service. Similary, a gCube fault received from interacting with another service ought to be converted into a gCube exception before it percolates up the call stack. |
− | + | [[Image:blue.jpg]] gCube exceptions complement but do not replace all the other Java exceptions, of course. The code which observes a failure may or may not be aware of its implications in the broader context of the request process. If it does, then it should raise a gCube exception. If it does not (e.g. is a generic routine), then it should raise whatever Java exception seems most appropriate. | |
− | + | [[Image:blue.jpg]] All gCube faults share a common root <code>GCUBEFault</code>, both as interface elements and implementation objects. Similary, all gCube exceptions derive <code>GCUBEException</code>. As we shall see, these roots are mostly useful in the implementation, where they simplify exception handling. | |
=== A Faultful Interface === | === A Faultful Interface === | ||
Line 66: | Line 66: | ||
<input message="tns:aboutInputMessage"/> | <input message="tns:aboutInputMessage"/> | ||
<output message="tns:aboutOutputMessage"/> | <output message="tns:aboutOutputMessage"/> | ||
− | <fault name=" | + | <fault name="fault" message="corefaults:GCUBEFaultMessage"/> |
− | + | ||
− | + | ||
</operation> | </operation> | ||
Line 87: | Line 85: | ||
<input message="tns:aboutInputMessage"/> | <input message="tns:aboutInputMessage"/> | ||
<output message="tns:aboutOutputMessage"/> | <output message="tns:aboutOutputMessage"/> | ||
− | <fault name=" | + | <fault name="fault" message="corefaults:GCUBEFaultMessage"/> |
− | + | ||
− | + | ||
</operation> | </operation> | ||
</pre> | </pre> | ||
− | + | [[Image:blue.jpg]] The <code>location</code> makes sense with respect to the temporary <code>build</code> [[#Building & Deploying Stubs|used by our buildfile]] and will succeed because the buildfile will copy all the the pre-defined gCore WSDL in that folder at the point of building. You do not need to worry about it, just make sure you are copying this correctly. | |
That's all for the interface of <code>Stateless</code>. This is the right time to [[#Building & Deploying Stubs|rebuild the stubs]] though. | That's all for the interface of <code>Stateless</code>. This is the right time to [[#Building & Deploying Stubs|rebuild the stubs]] though. | ||
Line 119: | Line 115: | ||
}}} | }}} | ||
− | public String about(String name) throws GCUBEFault | + | public String about(String name) throws GCUBEFault { |
try { | try { | ||
Line 139: | Line 135: | ||
* <code>getDefaultException()</code> is a method which <code>ServiceContext</code> inherits from <code>GCUBEServiceContext</code>. If not overridden, the method will wrap any exception into a <code>GCUBERetryEquivalentException</code>, but it may be overridden to reflect more optimistic or less optimistic assumptions. | * <code>getDefaultException()</code> is a method which <code>ServiceContext</code> inherits from <code>GCUBEServiceContext</code>. If not overridden, the method will wrap any exception into a <code>GCUBERetryEquivalentException</code>, but it may be overridden to reflect more optimistic or less optimistic assumptions. | ||
− | |||
− | |||
After having rebuilt and redeployed <code>SampleService</code>, adapt our <code>StatelessClient</code> to catch faults of the different types and then test it. | After having rebuilt and redeployed <code>SampleService</code>, adapt our <code>StatelessClient</code> to catch faults of the different types and then test it. | ||
+ | |||
+ | == Logging it! == | ||
+ | |||
+ | We gave [[The_Development_Cycle#Starting_the_gHN#Starting the gHN|earlier]] a first look at the gHN's log, observing the logs which gCF produces at startup on behalf of <code>SampleService</code>. Surely enough you would like to log the activities of your code, not only the gCF code from which yours inherits. In fact, you would probably want to log both from within code which runs within the gHN, as well as from within client code which runs ''outside'' a gHN. After all, configurable, multi-level log entries are much more informative and flexible than hand-made <code>println()</code> statements could possibly be. | ||
+ | |||
+ | Logging in gCF is discussed in somewhat more detail [[Logging|here]]. In brief:: | ||
+ | |||
+ | * logging messages are produced by invoking the methods of a ''logger'' object. | ||
+ | * logging messages can be assigned any one of 6 different ''levels''. In order of increasing 'severity': <code>TRACE</code>,<code>DEBUG</code>,<code>INFO</code>,<code>WARN</code>,<code>ERROR</code>,<code>FATAL</code>. | ||
+ | * logging messages can be complemented with stacktrace information. | ||
+ | * logging messages at any one level appear in the log only if the logger which emits them is configured to display messages at that level. | ||
+ | |||
+ | This is an illustration of the logger methods more commonly used: | ||
+ | |||
+ | <pre> | ||
+ | logger.fatal(Object message); | ||
+ | logger.fatal(Object message, Throwable t); | ||
+ | logger.error(Object message); | ||
+ | logger.error(Object message, Throwable t); | ||
+ | logger.warn(Object message); | ||
+ | logger.warn(Object message, Throwable t); | ||
+ | logger.info(Object message); | ||
+ | logger.info(Object message, Throwable t); | ||
+ | logger.debug(Object message); | ||
+ | logger.debug(Object message, Throwable t); | ||
+ | logger.trace(Object message); | ||
+ | logger.trace(Object message, Throwable t); | ||
+ | </pre> | ||
+ | |||
+ | The following methods are instead useful to guard pointlessly expensive invocations of the previous ones: | ||
+ | |||
+ | <pre> | ||
+ | logger.isFatalEnabled(); | ||
+ | logger.isErrorEnabled(); | ||
+ | logger.isWarnEnabled(); | ||
+ | logger.isInfoEnabled(); | ||
+ | logger.isDebugEnabled(); | ||
+ | logger.isTraceEnabled(); | ||
+ | </pre> | ||
+ | |||
+ | Now, gCF offers two types of loggers. One for use within the gHN and one for use outside the gHN. Both loggers are [http://logging.apache.org/log4j log4j] implementations of the simple [http://commons.apache.org/logging/ JCL] interface illustrated above. Aslog4j implementations, the two loggers can be configured to log only at or above a certain level of security, towards some outputs rather than others, and using some templates rather than others. Were you to wish to refine the default configuration which ships with gCore (which we believe to be adequate in the majority of case) please refer to the log4j [http://logging.apache.org/log4j/1.2/manual.html manual]. | ||
+ | |||
+ | Now, let us have closer look at the two loggers. Within the service implementation, you can use instantiate a <code>GCUBELog</code> with the class on behalf of which the logger ought to log. For this, you can pass either an object of that class, or else the class object itself. For example, if <code>myObject</code> denotes an object of class <code>MyClass</code>, then the following idioms are equivalent: | ||
+ | |||
+ | <pre> | ||
+ | GCUBELog logger = new GCUBELog(myObject); | ||
+ | GCUBELog logger = new GCUBELog(myObject.getClass()); | ||
+ | GCUBELog logger = new GCUBELog(MyClass.class); | ||
+ | </pre> | ||
+ | |||
+ | Typically, loggers are defined as static or instance members of implementation components. Static loggers are more memory-efficient but also less flexible in the presence of inheritance. For example, the following instance-level logger: | ||
+ | |||
+ | <pre> | ||
+ | public class MyClass { | ||
+ | .... | ||
+ | GCUBELog logger = new GCUBELog(this); | ||
+ | ... | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | logs on behalf of the class of the object denoted by <code>this</code>, which is not necessarily <code>MyClass</code> but potentially any one of its subclasses. gCF uses pretty much always instance-level loggers to log on behalf of your classes. | ||
+ | |||
+ | A common strategy to easily recognise service-specific entries in the container's log is to log on behalf of the service context, and in fact any other type of context (the [[#More Contexts|next section]] shows you what other common contexts are available in gCF). You can do this either at the point of creation of the logger or at any point of its lifetime, e.g.: | ||
+ | |||
+ | GCUBELog logger = new GCUBELog(ServiceContext.getContext()); | ||
+ | |||
+ | or: | ||
+ | |||
+ | logger.setContext(ServiceContext.getContext()); | ||
+ | |||
+ | In fact, any log prefix can be dynamically associated with a logger: | ||
+ | |||
+ | <pre> | ||
+ | logger.setPrefix("someprefix") | ||
+ | </pre> | ||
+ | |||
+ | As [[The Development Cycle#Starting the gHN|previously noticed]], the configuration of <code>GCUBELog</code>s is in <code>$GLOBUS_LOCATION/container-log4j.properties</code>. Most commonly, you will want to modify it to change the logging level of your loggers. As an example, the default gCore configuration: | ||
+ | |||
+ | <pre> | ||
+ | log4j.category.org.globus=WARN | ||
+ | log4j.category.org.gcube=DEBUG | ||
+ | </pre> | ||
+ | |||
+ | implies that loggers associated with classes 'under' <code>org.globus</code> will actually emit only warnings and errors, whereas loggers associated with classes 'under' <code>org.gcube</code> will emit all messages and excludes only traces. | ||
+ | |||
+ | For a (rather artificial) example of logging in context, here's a version of our port-type implementation with leaves a trace of some of its actions: | ||
+ | |||
+ | <pre> | ||
+ | package org.acme.sample.stateless; | ||
+ | import ... | ||
+ | |||
+ | public class Stateless extends GCUBEStartupPortType { | ||
+ | |||
+ | private GCUBELog logger = new GCUBELog(this); | ||
+ | |||
+ | /** {@inheritDoc} */ | ||
+ | protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();} | ||
+ | |||
+ | protected static void simulateRequestProcessing() throws Exception { | ||
+ | if (Math.random()<.60) { //simulating an error | ||
+ | switch ((int) (Math.random()*4+1)) {//randomly choosing error type | ||
+ | case 1 : throw new GCUBEUnrecoverableException("just give up"); | ||
+ | case 2 : throw new GCUBERetryEquivalentException("maybe someone else?"); | ||
+ | case 3: throw new GCUBERetrySameException("maybe in a bit?"); | ||
+ | case 4: throw new Exception("some problem with unclear semantics"); | ||
+ | }}} | ||
+ | |||
+ | public String about(String name) throws GCUBEFault,GCUBERetryEquivalentFault,GCUBERetrySameFault,GCUBEUnrecoverableFault { | ||
+ | |||
+ | logger.trace("Received a call from "+name); | ||
+ | try { | ||
+ | simulateRequestProcessing(); | ||
+ | return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() + | ||
+ | " ("+this.getServiceContext().getServiceClass() + ")"; | ||
+ | } | ||
+ | catch(GCUBEException e) { | ||
+ | logger.error("Problem in about():",e); | ||
+ | throw e.toFault();} | ||
+ | catch(Exception e) { | ||
+ | logger.error("Problem in about()",e); | ||
+ | throw sctx.getDefaultException("Problem in about()", e).toFault(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | |||
+ | Now, the second type of gCF logger. <code>GCUBECLientLog</code> is the type of logger to use in client code or, more correctly, in code which is not hosted within a gHN (services do act as clients of other services!). There is one small difference between <code>GCUBELog</code>s and <code>GCUBEClientLog</code>s, and this is that <code>GCUBEClientLog</code>s are configured in their own separate file, <code>$GLOBUS_LOCATION/client-log4j.properties</code>. Furthermore, <code>GCUBEClientLog</code>s log by default to the console rather than a file. Besides this, <code>GCUBEClientLog</code>s can be used exactly like <code>GCUBELog</code>s, as this new version of the our test client shows: | ||
+ | |||
+ | <pre> | ||
+ | public class StatelessTest { | ||
+ | |||
+ | static GCUBEClientLog logger = new GCUBEClientLog(StatelessTest.class); | ||
+ | |||
+ | public static void main(String[] args) throws Exception { | ||
+ | |||
+ | logger.trace("Running a stateless test..."); | ||
+ | |||
+ | EndpointReferenceType endpoint = new EndpointReferenceType(); | ||
+ | endpoint.setAddress(new AttributedURI(args[0])); | ||
+ | |||
+ | StatelessPortType stub = new StatelessServiceAddressingLocator().getStatelessPortTypePort(endpoint); | ||
+ | stub=GCUBERemotePortTypeContext.getProxy(stub,GCUBEScope.getScope(args[1])); | ||
+ | try { | ||
+ | logger.info(stub.about(args[2])); | ||
+ | } | ||
+ | catch (Exception e) { | ||
+ | logger.error("Caught exception of type "+e.getClass().getSimpleName()); | ||
+ | } | ||
+ | |||
+ | } | ||
+ | |||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Of course, to see these logs in the Eclipse or shell console, we need to abilitate <code>DEBUG</code>-level logs in <code>$GLOBUS_LOCATION/client-log4j.properties</code>: | ||
+ | |||
+ | <pre> | ||
+ | log4j.org.acme.sample=DEBUG | ||
+ | </pre> | ||
+ | |||
+ | == Node & Port-Type Contexts == | ||
+ | |||
+ | With gCF, a gCube service interacts with [[Contexts|other contexts]] besides its own service context. We have already encountered one in our [[#A Minimal Client|test client]], where we have used a <code>RemotePortTypeContext</code> to obtain a dynamic proxy for the stubs of some remote port-type. We briefly consider now two other types of context which play a significant role in most service implementations, namely node contexts and local port-type contexts. | ||
+ | |||
+ | The ''node context'' operates at a coarser level of granularity than the service context. It provides information and services which relate to the gHN itself. There are few components in the gCF which do not interact with the node context: Lifetime management, gCube calls management, and some crucial aspects of security management rely heavily on it. In fact, our ''SampleService'' already engages with the node context in order to activate itself, though this interaction takes place within the code of the <code>GCUBEServiceContext</code> from which the service context of <code>SampleService</code> inherits. However, you will also have to interact ''directly'' with the node context whenever you wish to introspect about the environment in which your service is deployed and operates (e.g. to discover information about co-deployed services or to get concrete implementation of gCF interfaces, as you will se later on). | ||
+ | |||
+ | The following refinement of the <code>about()</code> method of our <code>Stateless</code> port-type is a (very) simple illustration of how a service implementation may obtain and interrogate the node context: | ||
+ | |||
+ | <pre> | ||
+ | public String about(String name) throws GCUBERetryEquivalentFault, GCUBERetrySameFault, GCUBEUnrecoverableFault,GCUBEFault { | ||
+ | |||
+ | GHNContext nctx = GHNContext.getContext(); | ||
+ | ServiceContext sctx = ServiceContext.getContext(); | ||
+ | try { | ||
+ | simulateRequestProcessing(); | ||
+ | return "Hello " + name + ", you have invoked service "+this.getServiceContext().getName() + | ||
+ | " ("+this.getServiceContext().getServiceClass() + ") on the GHN "+nctx.getGHNID()+" in the gCube infrastructure " | ||
+ | +nctx.getGHN().getInfrastructure(); | ||
+ | } | ||
+ | catch(GCUBEException e) { | ||
+ | logger.error("Problem in about():",e); | ||
+ | throw e.toFault();} | ||
+ | catch(Exception e) { | ||
+ | logger.error("Problem in about()",e); | ||
+ | throw sctx.getDefaultException("Problem in about()", e).toFault(); | ||
+ | } | ||
+ | |||
+ | </pre> | ||
+ | |||
+ | As you can see, the node context is implemented as an object of class <code>GHNContext</code>, and <code>GHNContext</code> follows the singleton pattern [[The Development Cycle#A Simple Implementation|previously]] suggested for your own service context. | ||
+ | |||
+ | [[Image:blue.jpg]] More information on the capabiities of the gHN context will be found in the rest of the primer. Alternatively, you can refer to the [[Contexts#The gHN Context|developer guide]] or even the code documentation. | ||
+ | |||
+ | A ''port-type context'' operates at a finer level of granularity than the service context. As you would expect by now, it provides information and services which relate to individual port-types, most noticeably configuration which is specific to their behaviour. These are the contexts with which your code will interact most often with. The steps required to define contexts for your port-types are very similar to those already followed for the service context, and we illustrate them next. | ||
+ | |||
+ | First, you will want to dedicate a section of the JNDI file to the configuration of the port-type: | ||
+ | |||
+ | <pre> | ||
+ | <service name="acme/sample/stateless"> | ||
+ | |||
+ | ....will place your configuration here... | ||
+ | |||
+ | </service> | ||
+ | </pre> | ||
+ | |||
+ | We will offer an example of port-type configuration below. For now, notice that the <code>name</code> of the <code>service</code> section is the relative endpoint of the port-type, exactly as specified in the [[The Development Cycle#My First Profile|service profile]] and [[The Development Cycle#The Deployment Descriptor|deployment descriptor]]. | ||
+ | |||
+ | Next, you define the port-type context implementation by derivation, either from <code>GCUBEPortTypeContext</code> or <code>GCUBEStatefulPortTypeContext</code>. the choice depends on whether the port-type is stateless or does manage state. Since our <code>Stateless</code> port-type is...well, stateless...the choice is obvious here. We will consider [[Adding State|later]] the (much more interesting) case of a stateful port-type and its context. As to the derivation, it follows pretty much the same template pattern already encountered for service contexts: | ||
+ | |||
+ | <pre> | ||
+ | package org.acme.sample.stateless; | ||
+ | import... | ||
+ | |||
+ | public class StatelessContext extends GCUBEPortTypeContext { | ||
+ | |||
+ | |||
+ | /** Single context instance, created eagerly */ | ||
+ | private static StatelessContext cache = new StatelessContext(); | ||
+ | |||
+ | private StatelessContext(){} | ||
+ | |||
+ | /** Returns cached instance */ | ||
+ | public static StatelessContext getContext() {return cache;} | ||
+ | |||
+ | /**{@inheritDoc}*/ | ||
+ | public String getJNDIName() {return "acme/sample/stateless";} | ||
+ | |||
+ | /** {@inheritDoc}*/ | ||
+ | public String getNamespace() {return "http://acme.org/sample";} | ||
+ | |||
+ | /** {@inheritDoc}*/ | ||
+ | public GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();} | ||
+ | |||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Notice here that <code>StatelessContext</code>: | ||
+ | |||
+ | * follows the singleton pattern by returning always a single, eagerly created instance. This should be familiar ground by now. | ||
+ | * follows the template pattern which requires it to indicate its relative endpoint (method <code>getJNDIName()</code>), namespace (method <code>getNamespace()</code>), and the context of the associated service (<code>getServiceContext()</code>). As was the case for the service context, this is information and linkage required by gCF to act transparently on behalf of the port-type. | ||
+ | |||
+ | This further refinement of the <code>about()</code> method shows some of the information you can obtain from a port-type context (name, endpoint, deployment descriptor data): | ||
+ | |||
+ | <pre> | ||
+ | public String about(String name) throws GCUBERetryEquivalentFault, GCUBERetrySameFault, GCUBEUnrecoverableFault,GCUBEFault { | ||
+ | |||
+ | StringBuilder output = new StringBuilder(); | ||
+ | GHNContext nctx = GHNContext.getContext(); | ||
+ | ServiceContext sctx = ServiceContext.getContext(); | ||
+ | GCUBEPortTypeContext pctx = StatelessContext.getContext(); | ||
+ | try { | ||
+ | simulateRequestProcessing(); | ||
+ | output.append("Hello "+name).append(", you have invoked porttype "). | ||
+ | append(pctx.getName()+" of service "+sctx.getName()).append(", which you found "). | ||
+ | append (" on the GHN "+nctx.getGHNID()). | ||
+ | append(" at "+pctx.getEPR()+" in the gCube infrastructure "+nctx.getGHN().getInfrastructure()). | ||
+ | append("\n").append(". The port-type is implemented by class "+pctx.getDeploymentDescriptor().getParameter("className")+"."); | ||
+ | |||
+ | } | ||
+ | catch(GCUBEException e) { | ||
+ | logger.error("Problem in about():",e); | ||
+ | throw e.toFault();} | ||
+ | catch(Exception e) { | ||
+ | logger.error("Problem in about()",e); | ||
+ | throw sctx.getDefaultException("Problem in about()", e).toFault(); | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | After having rebuilt and redeployed <code>SampleService</code>, an invocation of <code>StatelessTest</code> should produce the following ouput: | ||
+ | |||
+ | <pre> | ||
+ | Hello John Doe, you have invoked porttype stateless of service SampleService, which you found on the GHN 6a9123f0-00cf-11dd-b91c-dcf83e3c74c8 at | ||
+ | Address: http://localhost:8080/wsrf/services/acme/sample/stateless in the gCube infrastructure development. | ||
+ | The port-type is implemented by class org.acme.sample.stateless.Stateless. | ||
+ | </pre> | ||
+ | |||
+ | To conclude, we show an example of custom port-type configuration. Here, <code>showImplementationClass</code>indicates whether the output of the <code>about()</code> method ought to include information about the implementation of the <code>Stateless</code> port-type: | ||
+ | |||
+ | <pre> | ||
+ | <service name="acme/sample/stateless"> | ||
+ | |||
+ | <environment | ||
+ | name="showImplementationClass" | ||
+ | value="true" | ||
+ | type="java.lang.Boolean" | ||
+ | override="false" /> | ||
+ | |||
+ | </service> | ||
+ | </pre> | ||
+ | |||
+ | And here is yet another self-explanatory refinement of the <code>about()</code> method which shows how the port-type context may be used to access port-type configuration: | ||
+ | |||
+ | <pre> | ||
+ | public String about(String name) throws GCUBERetryEquivalentFault, GCUBERetrySameFault, GCUBEUnrecoverableFault,GCUBEFault { | ||
+ | |||
+ | StringBuilder output = new StringBuilder(); | ||
+ | GHNContext nctx = GHNContext.getContext(); | ||
+ | ServiceContext sctx = ServiceContext.getContext(); | ||
+ | GCUBEPortTypeContext pctx = StatelessContext.getContext(); | ||
+ | try { | ||
+ | simulateRequestProcessing(); | ||
+ | output.append("Hello "+name).append(", you have invoked porttype "). | ||
+ | append(pctx.getName()+" of service "+sctx.getName()).append(", which you found "). | ||
+ | append (" on the GHN "+nctx.getGHNID()). | ||
+ | append(" at "+pctx.getEPR()+" in the gCube infrastructure "+nctx.getGHN().getInfrastructure()). | ||
+ | append("\n"); | ||
+ | |||
+ | if ((Boolean) pctx.getProperty("showImplementationClass")) | ||
+ | output.append(". The port-type is implemented by class "+ | ||
+ | pctx.getDeploymentDescriptor().getParameter("className")+"."); | ||
+ | |||
+ | } | ||
+ | catch(GCUBEException e) { | ||
+ | logger.error("Problem in about():",e); | ||
+ | throw e.toFault();} | ||
+ | catch(Exception e) { | ||
+ | logger.error("Problem in about()",e); | ||
+ | throw sctx.getDefaultException("Problem in about()", e).toFault();} | ||
+ | return output.toString(); | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | [[Image:blue.jpg]] More information on the capabiities of port-type contexts will be found in the rest of the primer. Alternatively, you can refer to the [[Contexts#PortType_Contexts|developer guide]] or even the code documentation. | ||
+ | |||
+ | |||
+ | With addition of <code>StatelessContext</code> our implementation ought now to look as follows: | ||
+ | |||
+ | <pre> | ||
+ | |-SampleService | ||
+ | |--etc | ||
+ | |---profile.xml | ||
+ | |---deploy-jndi-config.xml | ||
+ | |---deploy-server.wsdd | ||
+ | |---build.properties | ||
+ | | | ||
+ | |--src | ||
+ | |---org | ||
+ | |----acme | ||
+ | |-----sample | ||
+ | |------ServiceContext.java | ||
+ | |------stateless | ||
+ | |-------Stateless.java | ||
+ | |-------StatelessContext.java [new] | ||
+ | |------tests | ||
+ | |-------StatelessTest.java | ||
+ | | | ||
+ | |--schema | ||
+ | |---Stateless.wsdl | ||
+ | | | ||
+ | | | ||
+ | |--build.xml | ||
+ | | | ||
+ | |-Dependencies | ||
+ | |--SampleService | ||
+ | |||
+ | </pre> |
Latest revision as of 16:25, 21 April 2009
Now that we have exemplified the tasks which comprise the development cycle of a gCube service, we can progressively inject more structure and functionality into the implementation of SampleService
. We do so incrementally, of course, tackling first a number of issues which are common to all service implementations and yet do not imply big changes to the internal structure of our SampleService
.
Contents
A Faultful PortType
Things not always go according to plans. Unpredictable circumstances at runtime may cause deviations form the control flow we expect within our service implementation. Some of these deviations are the result of genuine programming errors which we hope to capture as early as possible during testing. Others are more exceptional and we cannot blame our code for them, rather a malformed client request or simply a local or remote environment which does not satisfy normal expectations. When this happens and there is a client waiting for a response, all we can do is return a fault which explains the problem and may induce a client to react in some useful way.
Faults & Exceptions
Simply put, a fault is an exception which escapes the service implementation. More precisely, it wraps such an exception in a way which is suitable for it to travel over the network and back to clients. By default, the gHN wraps exceptions which escape the service boundary in a generic SOAP fault. There is no much a client can do with it, beside desisting gracefully from doing whatever it was trying to do. However, we can define our own fault types as specialisations of the generic one.
gCF offer three pre-defined fault specialisations. They model prototypical fault causes, still generic but informative enough for clients to react more usefully than by giving up:
-
GCUBERetrySameFault
, which basically means: couldn't do it now, but try again later I might make it then. -
GCUBERetryEquivalentFault
which basically means: couldn't do it myself, but other Running Instances they might. -
GCUBEUnrecoverableFault
, which basically means: couldn't do it now, won't do it later, and there is no point asking someone else. Just forget about it.
A client presented with either one of the first two fault types can try to recover accordingly, while a client presented with the last knows that there is little point in insisting, neither later nor elsewhere. Of course, when or which of these faults should be returned depends on the precise semantics of our service.
So, how about our SampleService
? Well, at the moment our Stateless
port-type can hardly go wrong, because if its single about()
method completes successfully once, it is very likely that it will complete successfully all the time. At the very best, we could play safe and declare that we might return a GCUBEUnrecoverableFault
, in the assumption that if something goes wrong then there aren't many chances it will ever go right. In fact, a modicum of testing could immediately eliminate this possibility: either the service does not activate properly within the gHN or, if it does, it will behave correctly.
So, to illustrate fully the case of 'faultful' port-types, we are going to introduce artificially the possibility of a wider range of faults into the interface and implementation of the about()
method.
Before we do that, however, a final observation. Faults are 'heavyweight' exceptions and are best raised and caught at the ‘edge’ of a service implementations, i.e. in the code directly invoked by clients and in the code which directly call the methods of other services. Accordingly, gCF mirrors gCube faults with equivalent but 'lightweight' exception for use inside the service implementation:
-
GCUBERetrySameException
~ GCUBERetrySameFault -
GCUBERetryEquivalentException
~ GCUBERetryEquivalentFault -
GCUBEUnrecoverableException
~ GCUBEUnrecoverableFault
gCube exceptions and gCube faults are freely convertible. Typically, a gCube exception raised within the service should be converted into a gCube fault before it escapes the service. Similary, a gCube fault received from interacting with another service ought to be converted into a gCube exception before it percolates up the call stack.
gCube exceptions complement but do not replace all the other Java exceptions, of course. The code which observes a failure may or may not be aware of its implications in the broader context of the request process. If it does, then it should raise a gCube exception. If it does not (e.g. is a generic routine), then it should raise whatever Java exception seems most appropriate.
All gCube faults share a common root GCUBEFault
, both as interface elements and implementation objects. Similary, all gCube exceptions derive GCUBEException
. As we shall see, these roots are mostly useful in the implementation, where they simplify exception handling.
A Faultful Interface
First of all, let us declare the possibility of failures in the port-type interface:
<?xml version="1.0" encoding="UTF-8"?> <definitions name="Stateless" targetNamespace="http://acme.org/sample" xmlns:tns="http://acme.org/sample" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults"> <import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/> <types> <xsd:schema targetNamespace="http://acme.org/sample"> <xsd:element name="about" type="xsd:string" /> <xsd:element name="aboutResponse" type="xsd:string" /> </xsd:schema> </types> <message name="aboutInputMessage"> <part name="request" element="tns:about"/> </message> <message name="aboutOutputMessage"> <part name="response" element="tns:aboutResponse"/> </message> <portType name="StatelessPortType"> <operation name="about"> <input message="tns:aboutInputMessage"/> <output message="tns:aboutOutputMessage"/> <fault name="fault" message="corefaults:GCUBEFaultMessage"/> </operation> </portType> </definitions>
The thing to notice here is that we import pre-defined fault messages from a standard gCore WSDL:
<import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
and then use them in the declartion of the about()
operation:
<operation name="about"> <input message="tns:aboutInputMessage"/> <output message="tns:aboutOutputMessage"/> <fault name="fault" message="corefaults:GCUBEFaultMessage"/> </operation>
The location
makes sense with respect to the temporary build
used by our buildfile and will succeed because the buildfile will copy all the the pre-defined gCore WSDL in that folder at the point of building. You do not need to worry about it, just make sure you are copying this correctly.
That's all for the interface of Stateless
. This is the right time to rebuild the stubs though.
A Faultful Implementation
Now the implementation of the port-type and the simulation of failure we promised:
package org.acme.sample.stateless; import ... public class Stateless extends GCUBEStartupPortType { /** {@inheritDoc} */ protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();} protected static void simulateRequestProcessing() throws Exception { if (Math.random()<.60) { //simulating an error switch ((int) (Math.random()*4+1)) {//randomly choosing error type case 1 : throw new GCUBEUnrecoverableException("just give up"); case 2 : throw new GCUBERetryEquivalentException("maybe someone else?"); case 3: throw new GCUBERetrySameException("maybe in a bit?"); case 4: throw new Exception("some problem with unclear semantics"); }}} public String about(String name) throws GCUBEFault { try { simulateRequestProcessing(); return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() + " ("+this.getServiceContext().getServiceClass() + ")"; } catch(GCUBEException e) {throw e.toFault();} catch(Exception e) {throw sctx.getDefaultException("Problem of unknown semantics", e).toFault();} } }
A few things to comment upon here:
- the method
simulatRequestProcessing()
emulates an arbitrarily complex process which may fail in more than one different way. In a real scenario, the process would be likely distributed among many implementation components, and it would probably reach very 'deeply' within the call stack. Here we use a 'flat' method in which we throw a die and simulate a failure in 60% of the outcomes. When we do so, we throw another 4-faced die to simulate the different types of failures which may occur inside a gCube Service: the three types of gCube exceptions gCube plus a fourth type of generic failure.
- the method
about()
invokessimulatRequestProcessing()
and catches potential exceptions. All gCube exceptions are converted to the corresponding gCube fault. Any other Java exception is embedded into the type of gCube exception set as default before being also converted to a gCube fault.
-
getDefaultException()
is a method whichServiceContext
inherits fromGCUBEServiceContext
. If not overridden, the method will wrap any exception into aGCUBERetryEquivalentException
, but it may be overridden to reflect more optimistic or less optimistic assumptions.
After having rebuilt and redeployed SampleService
, adapt our StatelessClient
to catch faults of the different types and then test it.
Logging it!
We gave earlier a first look at the gHN's log, observing the logs which gCF produces at startup on behalf of SampleService
. Surely enough you would like to log the activities of your code, not only the gCF code from which yours inherits. In fact, you would probably want to log both from within code which runs within the gHN, as well as from within client code which runs outside a gHN. After all, configurable, multi-level log entries are much more informative and flexible than hand-made println()
statements could possibly be.
Logging in gCF is discussed in somewhat more detail here. In brief::
- logging messages are produced by invoking the methods of a logger object.
- logging messages can be assigned any one of 6 different levels. In order of increasing 'severity':
TRACE
,DEBUG
,INFO
,WARN
,ERROR
,FATAL
. - logging messages can be complemented with stacktrace information.
- logging messages at any one level appear in the log only if the logger which emits them is configured to display messages at that level.
This is an illustration of the logger methods more commonly used:
logger.fatal(Object message); logger.fatal(Object message, Throwable t); logger.error(Object message); logger.error(Object message, Throwable t); logger.warn(Object message); logger.warn(Object message, Throwable t); logger.info(Object message); logger.info(Object message, Throwable t); logger.debug(Object message); logger.debug(Object message, Throwable t); logger.trace(Object message); logger.trace(Object message, Throwable t);
The following methods are instead useful to guard pointlessly expensive invocations of the previous ones:
logger.isFatalEnabled(); logger.isErrorEnabled(); logger.isWarnEnabled(); logger.isInfoEnabled(); logger.isDebugEnabled(); logger.isTraceEnabled();
Now, gCF offers two types of loggers. One for use within the gHN and one for use outside the gHN. Both loggers are log4j implementations of the simple JCL interface illustrated above. Aslog4j implementations, the two loggers can be configured to log only at or above a certain level of security, towards some outputs rather than others, and using some templates rather than others. Were you to wish to refine the default configuration which ships with gCore (which we believe to be adequate in the majority of case) please refer to the log4j manual.
Now, let us have closer look at the two loggers. Within the service implementation, you can use instantiate a GCUBELog
with the class on behalf of which the logger ought to log. For this, you can pass either an object of that class, or else the class object itself. For example, if myObject
denotes an object of class MyClass
, then the following idioms are equivalent:
GCUBELog logger = new GCUBELog(myObject); GCUBELog logger = new GCUBELog(myObject.getClass()); GCUBELog logger = new GCUBELog(MyClass.class);
Typically, loggers are defined as static or instance members of implementation components. Static loggers are more memory-efficient but also less flexible in the presence of inheritance. For example, the following instance-level logger:
public class MyClass { .... GCUBELog logger = new GCUBELog(this); ... }
logs on behalf of the class of the object denoted by this
, which is not necessarily MyClass
but potentially any one of its subclasses. gCF uses pretty much always instance-level loggers to log on behalf of your classes.
A common strategy to easily recognise service-specific entries in the container's log is to log on behalf of the service context, and in fact any other type of context (the next section shows you what other common contexts are available in gCF). You can do this either at the point of creation of the logger or at any point of its lifetime, e.g.:
GCUBELog logger = new GCUBELog(ServiceContext.getContext());
or:
logger.setContext(ServiceContext.getContext());
In fact, any log prefix can be dynamically associated with a logger:
logger.setPrefix("someprefix")
As previously noticed, the configuration of GCUBELog
s is in $GLOBUS_LOCATION/container-log4j.properties
. Most commonly, you will want to modify it to change the logging level of your loggers. As an example, the default gCore configuration:
log4j.category.org.globus=WARN log4j.category.org.gcube=DEBUG
implies that loggers associated with classes 'under' org.globus
will actually emit only warnings and errors, whereas loggers associated with classes 'under' org.gcube
will emit all messages and excludes only traces.
For a (rather artificial) example of logging in context, here's a version of our port-type implementation with leaves a trace of some of its actions:
package org.acme.sample.stateless; import ... public class Stateless extends GCUBEStartupPortType { private GCUBELog logger = new GCUBELog(this); /** {@inheritDoc} */ protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();} protected static void simulateRequestProcessing() throws Exception { if (Math.random()<.60) { //simulating an error switch ((int) (Math.random()*4+1)) {//randomly choosing error type case 1 : throw new GCUBEUnrecoverableException("just give up"); case 2 : throw new GCUBERetryEquivalentException("maybe someone else?"); case 3: throw new GCUBERetrySameException("maybe in a bit?"); case 4: throw new Exception("some problem with unclear semantics"); }}} public String about(String name) throws GCUBEFault,GCUBERetryEquivalentFault,GCUBERetrySameFault,GCUBEUnrecoverableFault { logger.trace("Received a call from "+name); try { simulateRequestProcessing(); return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() + " ("+this.getServiceContext().getServiceClass() + ")"; } catch(GCUBEException e) { logger.error("Problem in about():",e); throw e.toFault();} catch(Exception e) { logger.error("Problem in about()",e); throw sctx.getDefaultException("Problem in about()", e).toFault(); } } }
Now, the second type of gCF logger. GCUBECLientLog
is the type of logger to use in client code or, more correctly, in code which is not hosted within a gHN (services do act as clients of other services!). There is one small difference between GCUBELog
s and GCUBEClientLog
s, and this is that GCUBEClientLog
s are configured in their own separate file, $GLOBUS_LOCATION/client-log4j.properties
. Furthermore, GCUBEClientLog
s log by default to the console rather than a file. Besides this, GCUBEClientLog
s can be used exactly like GCUBELog
s, as this new version of the our test client shows:
public class StatelessTest { static GCUBEClientLog logger = new GCUBEClientLog(StatelessTest.class); public static void main(String[] args) throws Exception { logger.trace("Running a stateless test..."); EndpointReferenceType endpoint = new EndpointReferenceType(); endpoint.setAddress(new AttributedURI(args[0])); StatelessPortType stub = new StatelessServiceAddressingLocator().getStatelessPortTypePort(endpoint); stub=GCUBERemotePortTypeContext.getProxy(stub,GCUBEScope.getScope(args[1])); try { logger.info(stub.about(args[2])); } catch (Exception e) { logger.error("Caught exception of type "+e.getClass().getSimpleName()); } } }
Of course, to see these logs in the Eclipse or shell console, we need to abilitate DEBUG
-level logs in $GLOBUS_LOCATION/client-log4j.properties
:
log4j.org.acme.sample=DEBUG
Node & Port-Type Contexts
With gCF, a gCube service interacts with other contexts besides its own service context. We have already encountered one in our test client, where we have used a RemotePortTypeContext
to obtain a dynamic proxy for the stubs of some remote port-type. We briefly consider now two other types of context which play a significant role in most service implementations, namely node contexts and local port-type contexts.
The node context operates at a coarser level of granularity than the service context. It provides information and services which relate to the gHN itself. There are few components in the gCF which do not interact with the node context: Lifetime management, gCube calls management, and some crucial aspects of security management rely heavily on it. In fact, our SampleService already engages with the node context in order to activate itself, though this interaction takes place within the code of the GCUBEServiceContext
from which the service context of SampleService
inherits. However, you will also have to interact directly with the node context whenever you wish to introspect about the environment in which your service is deployed and operates (e.g. to discover information about co-deployed services or to get concrete implementation of gCF interfaces, as you will se later on).
The following refinement of the about()
method of our Stateless
port-type is a (very) simple illustration of how a service implementation may obtain and interrogate the node context:
public String about(String name) throws GCUBERetryEquivalentFault, GCUBERetrySameFault, GCUBEUnrecoverableFault,GCUBEFault { GHNContext nctx = GHNContext.getContext(); ServiceContext sctx = ServiceContext.getContext(); try { simulateRequestProcessing(); return "Hello " + name + ", you have invoked service "+this.getServiceContext().getName() + " ("+this.getServiceContext().getServiceClass() + ") on the GHN "+nctx.getGHNID()+" in the gCube infrastructure " +nctx.getGHN().getInfrastructure(); } catch(GCUBEException e) { logger.error("Problem in about():",e); throw e.toFault();} catch(Exception e) { logger.error("Problem in about()",e); throw sctx.getDefaultException("Problem in about()", e).toFault(); }
As you can see, the node context is implemented as an object of class GHNContext
, and GHNContext
follows the singleton pattern previously suggested for your own service context.
More information on the capabiities of the gHN context will be found in the rest of the primer. Alternatively, you can refer to the developer guide or even the code documentation.
A port-type context operates at a finer level of granularity than the service context. As you would expect by now, it provides information and services which relate to individual port-types, most noticeably configuration which is specific to their behaviour. These are the contexts with which your code will interact most often with. The steps required to define contexts for your port-types are very similar to those already followed for the service context, and we illustrate them next.
First, you will want to dedicate a section of the JNDI file to the configuration of the port-type:
<service name="acme/sample/stateless"> ....will place your configuration here... </service>
We will offer an example of port-type configuration below. For now, notice that the name
of the service
section is the relative endpoint of the port-type, exactly as specified in the service profile and deployment descriptor.
Next, you define the port-type context implementation by derivation, either from GCUBEPortTypeContext
or GCUBEStatefulPortTypeContext
. the choice depends on whether the port-type is stateless or does manage state. Since our Stateless
port-type is...well, stateless...the choice is obvious here. We will consider later the (much more interesting) case of a stateful port-type and its context. As to the derivation, it follows pretty much the same template pattern already encountered for service contexts:
package org.acme.sample.stateless; import... public class StatelessContext extends GCUBEPortTypeContext { /** Single context instance, created eagerly */ private static StatelessContext cache = new StatelessContext(); private StatelessContext(){} /** Returns cached instance */ public static StatelessContext getContext() {return cache;} /**{@inheritDoc}*/ public String getJNDIName() {return "acme/sample/stateless";} /** {@inheritDoc}*/ public String getNamespace() {return "http://acme.org/sample";} /** {@inheritDoc}*/ public GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();} }
Notice here that StatelessContext
:
- follows the singleton pattern by returning always a single, eagerly created instance. This should be familiar ground by now.
- follows the template pattern which requires it to indicate its relative endpoint (method
getJNDIName()
), namespace (methodgetNamespace()
), and the context of the associated service (getServiceContext()
). As was the case for the service context, this is information and linkage required by gCF to act transparently on behalf of the port-type.
This further refinement of the about()
method shows some of the information you can obtain from a port-type context (name, endpoint, deployment descriptor data):
public String about(String name) throws GCUBERetryEquivalentFault, GCUBERetrySameFault, GCUBEUnrecoverableFault,GCUBEFault { StringBuilder output = new StringBuilder(); GHNContext nctx = GHNContext.getContext(); ServiceContext sctx = ServiceContext.getContext(); GCUBEPortTypeContext pctx = StatelessContext.getContext(); try { simulateRequestProcessing(); output.append("Hello "+name).append(", you have invoked porttype "). append(pctx.getName()+" of service "+sctx.getName()).append(", which you found "). append (" on the GHN "+nctx.getGHNID()). append(" at "+pctx.getEPR()+" in the gCube infrastructure "+nctx.getGHN().getInfrastructure()). append("\n").append(". The port-type is implemented by class "+pctx.getDeploymentDescriptor().getParameter("className")+"."); } catch(GCUBEException e) { logger.error("Problem in about():",e); throw e.toFault();} catch(Exception e) { logger.error("Problem in about()",e); throw sctx.getDefaultException("Problem in about()", e).toFault(); } }
After having rebuilt and redeployed SampleService
, an invocation of StatelessTest
should produce the following ouput:
Hello John Doe, you have invoked porttype stateless of service SampleService, which you found on the GHN 6a9123f0-00cf-11dd-b91c-dcf83e3c74c8 at Address: http://localhost:8080/wsrf/services/acme/sample/stateless in the gCube infrastructure development. The port-type is implemented by class org.acme.sample.stateless.Stateless.
To conclude, we show an example of custom port-type configuration. Here, showImplementationClass
indicates whether the output of the about()
method ought to include information about the implementation of the Stateless
port-type:
<service name="acme/sample/stateless"> <environment name="showImplementationClass" value="true" type="java.lang.Boolean" override="false" /> </service>
And here is yet another self-explanatory refinement of the about()
method which shows how the port-type context may be used to access port-type configuration:
public String about(String name) throws GCUBERetryEquivalentFault, GCUBERetrySameFault, GCUBEUnrecoverableFault,GCUBEFault { StringBuilder output = new StringBuilder(); GHNContext nctx = GHNContext.getContext(); ServiceContext sctx = ServiceContext.getContext(); GCUBEPortTypeContext pctx = StatelessContext.getContext(); try { simulateRequestProcessing(); output.append("Hello "+name).append(", you have invoked porttype "). append(pctx.getName()+" of service "+sctx.getName()).append(", which you found "). append (" on the GHN "+nctx.getGHNID()). append(" at "+pctx.getEPR()+" in the gCube infrastructure "+nctx.getGHN().getInfrastructure()). append("\n"); if ((Boolean) pctx.getProperty("showImplementationClass")) output.append(". The port-type is implemented by class "+ pctx.getDeploymentDescriptor().getParameter("className")+"."); } catch(GCUBEException e) { logger.error("Problem in about():",e); throw e.toFault();} catch(Exception e) { logger.error("Problem in about()",e); throw sctx.getDefaultException("Problem in about()", e).toFault();} return output.toString(); }
More information on the capabiities of port-type contexts will be found in the rest of the primer. Alternatively, you can refer to the developer guide or even the code documentation.
With addition of StatelessContext
our implementation ought now to look as follows:
|-SampleService |--etc |---profile.xml |---deploy-jndi-config.xml |---deploy-server.wsdd |---build.properties | |--src |---org |----acme |-----sample |------ServiceContext.java |------stateless |-------Stateless.java |-------StatelessContext.java [new] |------tests |-------StatelessTest.java | |--schema |---Stateless.wsdl | | |--build.xml | |-Dependencies |--SampleService