- Introduction
- Getting to the Controller
- System.Messaging Namespace
- RequestQueMonitor Project
- RequestsProcessor Discussion
- Summary
RequestsProcessor Discussion
We finish this chapter with a discussion of the RequestsProcessor class. As we look at RequestsProcessor, we jump briefly to a helper class that it calls RequestHandlerFactory, because it has some interesting and powerful code. As mentioned earlier, the RequestsProcessor class actually kicks off the processing efforts for each Request block in an incoming transaction. If all goes well, each Request block, identified by a separate <Request> . . . </Request> element in the incoming XML message, is processed by an appropriate RequestHandler, and an EAIResponse message is generated for return.
If one or more request blocks fail, however, RequestsProcessor needs to call the rollback() method on the RequestHandler objects. Any specific RequestHandler does not need to do any real rollback processing, but all RequestHandler components do need to implement (override) the abstract RequestHandlerBase.rollback() method.
The FailOnFirstError attribute, which can decorate the <Requests> XML element in the EAIRequest message, tells the RequestsProcessor whether to continue processing when the first Request processing returns an error. If FailOnFirstError is true, the overall processing is halted at that point and the rollback() method is called on the offending request by calling its RequestHandler object. Figure 7-11 shows an activity diagram detailing the important actions that take place, mostly in the RequestsProcessor class, for a synchronously processed transaction.
Figure 7-11 Synchronous processing activity diagram.
NOTE
One bit of sorcery called from the RequestsProcessor class is really fun. The name of the request handler class, which is subclassed from the RequestHandlerBase class, either is explicitly defined in the database table T_RequestDefinitions or is derived from the name of the Request sent in. This -name is identified in the Name attribute of each <Request> XML element. For example, a request to catalogue the MP3 files on a machine could be called like so:
<EAIRequest> <SessionID>987-cba-321-zyx</SessionID> <Requests> <Request Name="Catalogue"> <OutputFilename>MyMP3Files.txt</OutputFilename> </Request> </Requests> </EAIRequest>
Alright, so this might not solve a burning industry-wide need, but I wanted to compile a list of MP3 files on my server. Sure, I could have used the Search utility that comes with Windows 2003, but what fun would that be? Being the geek that I am, I instead wrote a RequestHandler to perform this function. When the server is connected to the Internet, I can get a list of files from anywhere in the world. Now, when I'm in a dark alley in Rome, and a thug says "Gimme a list of all yer MP3 files on yer server, or else," I can fire off an EAIRequest message and display the results. (Remind me to always have my wireless device in all dark alleys from now on.)
The name of the RequestHandler for the Request named Catalogue would be CatalogueHandler, in the EAIFramework.Handler namespace. RequestsProcessor generates the name, creates an instance of the class on the fly, and finally calls the process method. This is all accomplished through classes in the System.Reflection namespace. In the next section, you will see how you can use these classes to instantiate an appropriate handler class on the fly, given just the name of the handler class.
System.Reflection Namespace
The System.Reflection namespace contains a large number of utility classes that are very handy in certain situations. They enable you to interact with and manipulate types, assemblies, and so on. Major classes in this namespace include Assembly, Module, ConstructorInfo, MemberInfo, and MethodInfo.
With the tools available in System.Reflection, you now can plug in new functionality and allow for new <Request Name="xxx"> blocks simply by writing a subclass of EAIFramework.Handler.RequestHandlerBase and making it accessible to the EAIFramework engine. The framework figures out what class should be handling the request, attempts to create an instance of it, and then calls its process method.
RequestHandlerFactory.cs
Before we look at the entire RequestsProcessor class, let's just pull out the code that creates the instance of the handler class (see Listing 7-7). It is fairly small and performs quite a bit of functionality in these few lines. It has been placed in a factory class named RequestHandlerFactory.cs, and it is in the EAIFramework.Handler namespace.
Listing 7-7: RequestHandlerFactory.cs
using System; using System.Xml; using System.Runtime.Remoting; using System.Reflection; using EAIFramework.Messages; using EAIFramework.Util; namespace EAIFramework.Handler { /// <summary> /// RequestHandlerFactory class follows a factory pattern /// and has a single static public method: getHandler(Req). /// It also has a private constructor. /// </summary> public class RequestHandlerFactory { private RequestHandlerFactory(){} /// <summary> /// The only public method in this class, getHandler, /// takes in a Request object. This method then /// checks to see what type of request it is, checks to /// see what the handler name is (from the database), /// and instantiates and returns the new object. /// If it cannot instantiate an appropriately named /// object, it will return null. /// </summary> /// <param name="rIn">Request object to be processed /// </param> /// <returns>Subclassed object of RequestHandlerBase /// </returns> public static EAIFramework.Handler.RequestHandlerBase getHandler(Request rIn) { // First, get the Handler name for this request EAIFramework.Util.DBUtilProxy tdp = new DBUtilProxy(); string strHandlerName= tdp.getHandlerName(rIn.Name); // *** If we don't have a specific Handler Name // from the database, use the Request.Name if( (strHandlerName == null) || (strHandlerName.Equals(""))) strHandlerName = rIn.Name; // Now get an ObjectHandle for the appropriate type // of RequestHandler object ObjectHandle oh = Activator.CreateInstance( "EAIUtilities", "EAIFramework.Handler." + strHandlerName + "Handler"); Logger.log(Logger.INFO, "New Handler obj = " + oh.ToString()); // Next, get a Type object Type ht = oh.Unwrap().GetType(); Logger.log(Logger.INFO, "New Type from Handler = " + ht.Name + "-" + ht.ToString()); // Finally, get a ConstructorInfo object for this // Type. With this, we will Invoke the CI and // get a 'ready-to-run' object that we can return ConstructorInfo ci = ht.GetConstructor(new Type[]{ new EAIFramework.Messages.Request().GetType()}); Logger.log(Logger.INFO, "ConstructorInfo () returned: " + ci.ToString()); // Now all we have to do is invoke the // ConstructorInfo object to get the handler EAIFramework.Handler.RequestHandlerBase rhb = (EAIFramework.Handler.RequestHandlerBase) ci.Invoke(new object[]{rIn}); // ------------------------------------------------- // For testing, uncomment these lines, and the // newly created RequestHandlerBase (or // descendent class) will process the Request. // This method can be called from a standalone // test program to test a specific Handler or // to ensure that the above code works as expected. // // EAIFramework.Messages.Component cBack = // rhb.process(); // Logger.log(Logger.INFO, // "Component returned = " + cBack.Name + ", " + // cBack.Status + ", " + cBack.ToString()); // ------------------------------------------------- return rhb; }//end getHandler() }//end class }//end namespace
RequestHandlerFactory has a single public, static method named getHandler(). It takes in a Request object and returns an instance of the Handler class that it determines should be handling this type of request, or it returns null. Because all handlers should be subclassed from the RequestHandlerBase class, the abstract type RequestHandlerBase is returned. All concrete subclasses have process and rollback() methods that are called by the RequestsProcessor class during actual execution.
We include the System.Runtime.Remoting namespace to get access to the ObjectHandle class, used in the getHandler() method. Also included is the System.Reflection namespace for access to the ConstructorInfo class.
As the method is entered, it instantiates the DBUtilProxy class, used to interact with some of the support tables in the database. In this case, we want to see if a specific handler name is associated with the request name. The table T_RequestDefinitions has four columns:
-
RequestName
-
Description
-
Status
-
HandlerName
You might be asking why we have this table: We have already said that you get the name of the handler from the name of the request, right? Well, you might want to have several different requests handled by the same handler. For example, in this instance, we want to provide the following kinds of requests for EAIFramework reporting and statuses:
-
ListAllRequests (used to get a list of supported request types)
-
TransactionStatus (used to get the status/results of a given TransactionID)
-
ListToDo (used to get all requests sitting in the ToDo database table)
-
ListUsers (used to get a list of all authorized EAIFramework users)
This is just a brief list of the many possible support request types that the EAIFramework could provide to its users and administrators. Because each will have a different request name, each would have to have a separate RequestHandler component. However, each of the requests will be quite small in terms of processing, and it would be unnecessary overhead for each one to have its own handler. Instead, the T_RequestDefinitions table tells you what RequestHandlerBase subclass to use. In this way, you can process more than one request type in a single RequestHandler. For example, the rows in the T_RequestDefinitions table for the preceding scenario might look something like Table 7-2.
Table 7-2 Requests supported by Admin request handler
RequestName |
Description |
Status |
HandlerName |
ListAllRequests |
List supported requests |
A |
Admin |
TransactionStatus |
Return saved status info for TrnxID |
A |
Admin |
ListToDo |
Snapshot of current ToDo requests |
A |
Admin |
ListUsers |
Return list of all EAIFramework users |
A |
Admin |
You can see that if any of the four Request blocks comes in for processing, they all will be handled with a handler named Admin. This handler name, as described earlier, is used to generate the full handler name. In this case, all four requests would generate the full handler name:
EAIFramework.Handler.AdminHandler
Then, in the process method of the AdminHandler class, a quick check of the request name is made to determine which functionality to perform.
After you have checked the database for a handler name for the supplied request name, a check is made to see if the database call returned null or a blank string. If either of these were returned, it means that the request name supplied does not exist in the table. If that's the case, just use the Request Name as the handler name. Essentially, the name of a request is the default handler name; you have to insert rows in the T_RequestDefinitions table only if more than one Request Name will be supported by a handler. For example, if the <Request Name="Catalogue"> request mentioned earlier is the only request handled by the CatalogueHandler class, it doesn't need to be in the database, because Catalogue will be used to generate the name of the handler on the fly.
Next, you get an ObjectHandle object for the handler class. The static method CreateInstance() of the Activator class is used for this purpose. You pass in the name of an assembly and the name of the class to be instantiated. In this case, you build the class name by using the strHandlerName member that should contain either the request name or the handler name returned from the database for this request and a constant. This is done with the following code:
strHandlerName + "Handler"
You use the ObjectHandle instance for the required handler to get a Type object. This is accomplished by calling the Unwrap() method on the ObjectHandle returned earlier and immediately calling the GetType() method. You are left with a Type object that represents the handler class you need.
Finally, you create a ConstructorInfo object from the System.Reflection namespace. This instance is used to create the actual handler instance that gets returned to the caller. The constructor for the ConstructorInfo class takes in an array of Type objects. This is used when the constructor for the object in question is called. In this case, the constructor for a RequestHandler subclass takes in a single parameter, a Request object. Therefore, the ConstructorInfo object takes in a Type array that contains a single Type, the EAIFramework.Messages.Request type.
With the newly created ConstructorInfo instance for the handler you need, you are ready to fire up the constructor and get an instance of the handler class. A call to Invoke() on the ConstructorInfo class accomplishes this. The last line of real code in the getHandler() method creates the instance:
EAIFramework.Handler.RequestHandlerBase rhb = (EAIFramework.Handler.RequestHandlerBase) ci.Invoke(new object[]{rIn});
The member rhb is returned to the caller. The Request object, sent into the getHandler() method, is sent into the constructor in the Invoke() call. Invoke() takes an array of Object objects. In this way, you could call any constructor with any signature by setting the appropriate input parameter list in the object array.
RequestsProcessor Code
Now we look at the RequestsProcessor class (see Listing 7-8). I'm going to blow past some of the helper methods, because they are really self-explanatory. However, I do want to spend some time on the process method. This is called from the Controller class when a transaction is sent in that needs to be processed synchronously. It is also called from the RequestQueMonitor class that watches the request message queue to process asynchronous transactions.
Listing 7-8: The RequestsProcessor Class
using System; using System.Collections; using EAIFramework.Util; using EAIFramework.Messages; using EAIFramework.Handler; using EAIFramework.BusinessRules; namespace EAIFramework.Controller { /// <summary> /// Summary description for RequestsProcessor. /// </summary> public class RequestsProcessor { protected EAIFramework.Messages.EAIRequest eaiReq = null; protected Requests reqObjects = new Requests (); /// <summary> /// Default constructor, takes only a single argument, /// An EAIRequest. /// </summary> /// <param name="rIn"></param> public RequestsProcessor( EAIFramework.Messages.EAIRequest rIn) { eaiReq = rIn; reqObjects = eaiReq.Requests; }//end constructor()
There is nothing remarkable about the first portion of the class. It has a single constructor that takes an EAIRequest as its only parameter, which it stores in an instance member. Now on to the process method (see Listing 7-9).
Listing 7-9: The process Method
/// <summary> /// This method adds each Request to the RequestsToDo /// table and then processes each Request. /// When it completes, each Request should remove /// its own entry from the RequestsToDo table. /// </summary> public ArrayList process() { // Set up the ArrayList we'll return to the caller ArrayList alRet = new ArrayList(); // Get the list of Request objects reqObjects = eaiReq.Requests; // Set up a place to store handlers ArrayList alHandlers = new ArrayList(); // Before anything gets added to the ToDo list, // see if this set of Requests passes // PreProcBusinessRules PreProcBusinessRules preBR = new PreProcBusinessRules(); EAIFramework.Messages.BusinessRuleResponse brr = preBR.Check(eaiReq); if( brr.StatusCode != StatusCodes.OK ) { // This is an error. There was something wrong // with the PreProcessing Business Rule checks! // Generate response and return w/o processing. Logger.log(Logger.ERROR, "PreProcBusinessRules.Check Failed: " + brr.Description); RequestResponse rBack = new RequestResponse(); rBack.StatusCode = StatusCodes.FAILED_PREPROCBUSINESSRULES; rBack.Status= StatusCodes.Descriptions(rBack.StatusCode); rBack.Description = "Failed PreProcBusinessRules checking: " + brr.Description; rBack.Name = "PreProcBusinessRules"; alRet.Add(rBack); return alRet; }//end if PreProcBusRules.Check() //Otherwise... Logger.log(Logger.INFO, "PreProcBusinessRules.Check Passed"); // Add to the RequestsToDo list this.addToToDo(reqObjects); // Now process each Request object foreach(Request req in reqObjects.RequestList) { RequestResponse rBack = null; Logger.log(Logger.INFO, "Processing Request # " + req.Iteration); try{ // Here we check to see if the user is // authorized to execute this type of // request. Authenticator auth = new Authenticator(); EAIUser theUser = auth.GetUserFromSessionID( eaiReq.SessionID); if(theUser.Group.ToLower().Equals("admin")) { // this is okay, they're an ADMIN } else { // See if they have rights to process // this type of Request now. // If not, send them on their way w/o // processing this Request if( ! auth.UserCanExecuteRequest( theUser.Username, req.Name)) { // If we're here, then the user is // NOT an ADMIN, *AND* they're not // authorized for this Request type. // Therefore, create an error and // continue to the next one. rBack = new RequestResponse(); rBack.StatusCode = StatusCodes.USER_NOT_AUTHORIZED_FOR_REQUEST; rBack.Status= StatusCodes.Descriptions( rBack.StatusCode); rBack.Description = "User: " + theUser.Username + " attempting Unauthorized Request Type: " + req.Name; rBack.Name = req.Name; alRet.Add(rBack); continue; }//end if authed == false }//end else (they're NOT an ADMIN) RequestHandlerBase hand = RequestHandlerFactory.getHandler(req); if(hand == null) { rBack = new RequestResponse(); rBack.StatusCode = StatusCodes.UNKNOWN_HANDLER; rBack.Status=StatusCodes.Descriptions (rBack.StatusCode); rBack.Description = "Cannot find Handler: " + " EAIFramework.Handler." + req.Name + "Handler"; rBack.Name = req.Name; }//end if handler == null else { // The Handler is not null, so add it // to the list, and then call // the process() method to do the work alHandlers.Add(hand); rBack = hand.process(); }//end else } catch (Exception exc) { // Need to build a failed RequestResponse // here to return good info. rBack = new RequestResponse(); rBack.StatusCode = StatusCodes.UNKNOWN_HANDLER; rBack.Status= StatusCodes.Descriptions(rBack.StatusCode); rBack.Description = "Cannot find Handler: " + "EAIFramework.Handler." + req.Name + "Handler - " + exc.Message; rBack.Name = req.Name; }//end catch() alRet.Add(rBack); // If there is an error AND we're supposed to // FailOnFirstError, then break out and don't // process any more Request objects. if( (rBack.StatusCode != StatusCodes.OK ) && eaiReq.FailOnFirstError) { // However, b/f we go, we need to remove all // the Requests that are currently in the // T_RequestToDo table for this TrnxID this.removeAllFromToDo( req.TrnxID.ToString()); // Now call roll back on any of the Requests // that have already been fired for(int x = 0; x < req.Iteration; x++) { Request rRoll = (Request) reqObjects.RequestList[x]; RequestHandlerBase hRoll = (RequestHandlerBase) alHandlers[x]; RequestResponse rrRoll = hRoll.rollback(); alRet.Add(rrRoll); }//end for break; } // Otherwise, just remove this processed Request // from the T_RequestsToDo table this.removeFromToDo(req); }//end foreach // Now, right before we leave, // see if this set of Requests passes // PostProcBusinessRules PostProcBusinessRules postBR = new PostProcBusinessRules(); brr = postBR.Check(reqObjects); if( brr.StatusCode != StatusCodes.OK ) { // This is an error. There was something wrong // with the PreProcessing Business Rule checks! // Generate response and return w/o processing. Logger.log(Logger.ERROR, "PostProcBusinessRules.Check Failed: " + brr.Description); RequestResponse rBack = new RequestResponse(); rBack.StatusCode = StatusCodes.FAILED_POSTPROCBUSINESSRULES; rBack.Status= StatusCodes.Descriptions(rBack.StatusCode); rBack.Description = "Failed PostProcBusinessRules checking: " + brr.Description; rBack.Name = "PostProcBusinessRules"; alRet.Add(rBack); return alRet; }//end if PostProcBusRules.Check() //Otherwise... Logger.log(Logger.INFO, "PostProcBusinessRules.Check Passed"); return alRet; }//end process()
process returns an ArrayList instance filled with RequestResponse objects. These are the RequestResponse instances sent back from each called RequestHandler instance. The method starts by creating the ArrayList that will be returned. It then pulls out the Requests object from the EAIRequest object and creates an ArrayList to hold the RequestHandler instances to be used for each Request. You create and store a list of handler objects, because it might be necessary to call the rollback() method on each one.
Next, all of the requests in the Requests object are added to the ToDo table in the database. This is accomplished by calling the helper method addToToDo(). It makes the code a little cleaner in this method to stick the call to the ToDo proxy class in a helper method.
A foreach loop then blasts through each Request contained in the Requests object. The handler object, cast as a RequestHandlerBase object, is created by calling the RequestHandlerFactory.getHandler() method, discussed earlier. Providing that the handler is found, instantiated, and returned, you can send off the request for processing. If all is well, the handler is added to the ArrayList of the handler, and a new RequestResponse object is created by calling the process method of the handler. If there is an error along the way for this Request, an error RequestResponse instance is created. You drop out of the code for this Request and add the RequestResponse, good or bad, to the ArrayList to be returned, named alRet.
Next, if the status was unsuccessful for any reason and this transaction is set for FailOnFirstError, you delete all requests for this transaction from the ToDo table, because you're bailing on this one. Then each handler that has already been called is cycled through, and the rollback() method is called. A RequestResponse object is returned from the rollback() method, just as the process method does. These status objects are added to the alRet list along with those returned from process.
Otherwise, you remove the current Request from the ToDo table, whether the response was successful or unsuccessful. When the foreach loop completes, either because of running through each Request processed or because of a failure and FailOnFirstError being set to true, the compiled ArrayList of RequestResponse objects is returned to the caller (see Listing 7-10).
Listing 7-10: addToToDo() Method
/// <summary> /// Helper method to insert all Request objects into the /// RequestToDo table. /// </summary> /// <param name="rsIn"></param> private void addToToDo( Requests rsIn ) { DBUtilProxy tdprox = new DBUtilProxy(); foreach( Request req in rsIn.RequestList) { tdprox.addRequest(req); }//end foreach }//end addToToDo() private void removeAllFromToDo(string strReq) { DBUtilProxy dbup = new DBUtilProxy(); int nRow = dbup.removeToDoRequests(strReq); }//end removeAllFromToDo() private void removeFromToDo(Request req) { DBUtilProxy dbup = new DBUtilProxy(); int nRows = dbup.removeRequest(req); }//end removeFromToDo() }//end class }//end namespace
As you can see in Listing 7-10, the method addToToDo() takes in a Requests object. It instantiates the DBUtilProxy class, which is used to interact with some of the utility tables. In this case, it inserts rows into the T_RequestsToDo table. This table is used to hold any Request steps that need to be processed. Because all the processing is happening in memory, if the box takes a nosedive for some reason, you would lose all traces of submitted but as yet unprocessed requests. Therefore, the first thing you must do is add each step to a database table. Now, if the cleaning crew decides to unplug your server so that it can use the floor polisher, you can query the ToDo table and continue processing (more or less) as if nothing had happened.
The removeAllFromToDo() method is called if the request processing is being aborted. You already know that you are not going to continue processing any subsequent requests, so just remove all of the requests in the T_RequestsToDo table.
removeFromToDo() performs essentially the opposite function as the addToToDo() method. It is called when an individual request has been processed. When that happens, you can safely remove this request from the T_RequestsToDo table so that it's not in danger of being reprocessed later.