View Javadoc

1   /**
2    * - Digitalis Internal Framework v2.0 - (C) 2007, Digitalis Informatica. Distribuicao e Gestao de Informatica, Lda.
3    * Estrada de Paco de Arcos num.9 - Piso -1 2780-666 Paco de Arcos Telefone: (351) 21 4408990 Fax: (351) 21 4408999
4    * http://www.digitalis.pt
5    */
6   package pt.digitalis.dif.controller;
7   
8   import java.util.Map;
9   import java.util.Map.Entry;
10  
11  import pt.digitalis.dif.controller.interfaces.IAuthenticationPlugin;
12  import pt.digitalis.dif.controller.interfaces.IControllerCleanupTask;
13  import pt.digitalis.dif.controller.interfaces.IDIFContext;
14  import pt.digitalis.dif.controller.interfaces.IDIFDispatcher;
15  import pt.digitalis.dif.controller.interfaces.IDIFRequest;
16  import pt.digitalis.dif.controller.interfaces.IDIFSession;
17  import pt.digitalis.dif.controller.interfaces.IPrivateDIFSession;
18  import pt.digitalis.dif.controller.objects.Constants;
19  import pt.digitalis.dif.controller.objects.ControllerExecutionStep;
20  import pt.digitalis.dif.controller.objects.DIFContext;
21  import pt.digitalis.dif.controller.objects.DIFResponse;
22  import pt.digitalis.dif.controller.objects.DIFSession;
23  import pt.digitalis.dif.controller.objects.DispatcherAuthenticationResult;
24  import pt.digitalis.dif.controller.objects.SSOInfo;
25  import pt.digitalis.dif.controller.security.managers.IAuthenticationManager;
26  import pt.digitalis.dif.controller.security.managers.IAuthorizationManager;
27  import pt.digitalis.dif.controller.security.managers.IIdentityManager;
28  import pt.digitalis.dif.controller.security.managers.ISessionManager;
29  import pt.digitalis.dif.controller.security.objects.IDIFUser;
30  import pt.digitalis.dif.dem.DEMRegistryImpl;
31  import pt.digitalis.dif.dem.annotations.controller.Channel;
32  import pt.digitalis.dif.dem.interfaces.IStage;
33  import pt.digitalis.dif.dem.interfaces.IStageInstance;
34  import pt.digitalis.dif.dem.managers.IMessageManager;
35  import pt.digitalis.dif.dem.objects.ViewObject;
36  import pt.digitalis.dif.dem.objects.messages.MessageList;
37  import pt.digitalis.dif.exception.controller.BusinessFlowException;
38  import pt.digitalis.dif.exception.controller.ControllerException;
39  import pt.digitalis.dif.exception.controller.LicenseViolationException;
40  import pt.digitalis.dif.exception.controller.SessionTimeoutException;
41  import pt.digitalis.dif.exception.controller.UserHasNoAccessToStage;
42  import pt.digitalis.dif.exception.security.AuthenticationManagerException;
43  import pt.digitalis.dif.exception.security.IdentityManagerException;
44  import pt.digitalis.dif.ioc.DIFIoCRegistry;
45  import pt.digitalis.dif.startup.DIFInitializer;
46  import pt.digitalis.dif.startup.DIFStartupConfiguration;
47  import pt.digitalis.dif.utils.logging.DIFLogger;
48  import pt.digitalis.utils.common.StringUtils;
49  import pt.digitalis.utils.config.IConfigurations;
50  
51  import com.newrelic.api.agent.NewRelic;
52  import com.newrelic.api.agent.Trace;
53  
54  /**
55   * This class is the heart of the framework. Its responsibilities include receive and validate the incoming DIFRequest,
56   * the request processing (if the request is valid) and the routing of the stage-generated DIFResponse.
57   * AbstractDIFDispatcher implements the TEMPLATE METHOD design pattern (see GoF's "Design Patterns: Elements of Reusable
58   * Object-Oriented Software"). As such, the <code> executeTaskSteps() </code> method is declared final. This method
59   * defines the dispatching-cycle steps, and these steps must be always run in the same order to all the requests. There
60   * is a set of protected methods that correspond to the aforementioned dispatching-cycle steps. These methods can be
61   * overridden in derived classes if one wants to modify the behavior associated with a given step. ChALs should call a
62   * particular controller so this class (the superclass) is declared <code>abstract</code> to inhibit instantiation.
63   * 
64   * @author Rodrigo Goçalves <a href="mailto:rgoncalves@digitalis.pt">rgoncalves@digitalis.pt</a>
65   * @author Pedro Viegas <a href="mailto:pviegas@digitalis.pt">pviegas@digitalis.pt</a>
66   * @created 2007/03/16
67   */
68  abstract public class AbstractDIFDispatcher implements IDIFDispatcher {
69  
70      /** The messae for invalid stage requests */
71      final public static String INVALID_STAGE_ERROR_MESSAGE = "The stage is not registered in the DEM. Check the stage id.";
72  
73      /** The message manager to get class messages */
74      static IMessageManager messageManager;
75  
76      /** Messages cache for all constraints */
77      static private MessageList messages;
78  
79      /**
80       * Get's the Message Manager from the IoC
81       * 
82       * @return the message manager instance
83       */
84      static private IMessageManager getMessageManager()
85      {
86          if (messageManager == null)
87              messageManager = DIFIoCRegistry.getRegistry().getImplementation(IMessageManager.class);
88  
89          return messageManager;
90      }
91  
92      /**
93       * Perform any clean up tasks that have been contributed by ...
94       * 
95       * @param context
96       *            the context
97       * @param success
98       *            T if the stage was successfully executed
99       * @throws ControllerException
100      */
101     static public final void performCleanup(IDIFContext context, boolean success) throws ControllerException
102     {
103         ControllerException controllerException = null;
104 
105         for (IControllerCleanupTask task: DIFIoCRegistry.getRegistry().getImplementations(IControllerCleanupTask.class))
106             try
107             {
108                 task.doTask(context, success);
109             }
110             catch (Exception e)
111             {
112                 if (controllerException == null)
113                     controllerException = new ControllerException(ControllerExecutionStep.DISPATCHER_CONCLUDE, e);
114             }
115 
116         if (controllerException != null)
117             throw controllerException;
118     }
119 
120     /** If an authentication error has occurred */
121     protected ControllerException authenticationException = null;
122 
123     /** the channel ID */
124     private String channelID = null;
125 
126     /** The execution context. */
127     protected IDIFContext difContext;
128 
129     /** The Authentication Manager */
130     protected IAuthenticationManager theAuthenticationManager;
131 
132     /** The authorization manager. */
133     protected IAuthorizationManager theAuthorizationManager;
134 
135     /** The configuration manager. */
136     protected IConfigurations theConfigurations;
137 
138     /** The identity manager. */
139     protected IIdentityManager theIdentityManager;
140 
141     /** The session manager. */
142     protected ISessionManager theSessionManager;
143 
144     /** The stage to be executed. */
145     protected IStage theStage;
146 
147     /**
148      * Default constructor. This constructor must be inherited in the extended classes and injected with the instances
149      * from the IoC
150      * 
151      * @param theIdentityManager
152      *            the identity manager
153      * @param theAuthenticationManager
154      *            the authentication manager
155      * @param theAuthorizationManager
156      *            the authorization manager
157      * @param theSessionManager
158      *            the authentication manager
159      * @param theConfigurationManager
160      *            the configurations manager
161      */
162     public AbstractDIFDispatcher(IIdentityManager theIdentityManager, IAuthenticationManager theAuthenticationManager,
163             IAuthorizationManager theAuthorizationManager, ISessionManager theSessionManager,
164             IConfigurations theConfigurationManager)
165     {
166         this.theIdentityManager = theIdentityManager;
167         this.theAuthorizationManager = theAuthorizationManager;
168         this.theSessionManager = theSessionManager;
169         this.theConfigurations = theConfigurationManager;
170         this.theAuthenticationManager = theAuthenticationManager;
171     }
172 
173     /**
174      * Authenticates a user on the framework. The method is defined with protected scope to allow overriding on the
175      * subclasses.
176      * 
177      * @throws ControllerException
178      *             when any runtime exception is thrown or the authentication process has failed
179      */
180     @Trace(metricName = "DIF:Dispatcher:Authenticate", dispatcher = true)
181     protected void authenticate() throws ControllerException
182     {
183         // 1. perform the necessary operations before authentication execution
184         this.preAuthentication();
185         // 2. execute the authentication
186         DispatcherAuthenticationResult authenticationResult = this.performAuthentication();
187         // 3. perform the post authentication operations
188         this.postAuthentication(authenticationResult);
189     }
190 
191     /**
192      * Check the user permissions to run a given stage. The method is defined with protected scope to allow overriding
193      * on the subclasses.
194      * 
195      * @throws ControllerException
196      *             when any runtime exception is thrown
197      */
198     @Trace(metricName = "DIF:Dispatcher:Authorize", dispatcher = true)
199     protected void authorize() throws ControllerException
200     {
201         IDIFSession session = null;
202         IDIFUser user = null;
203         boolean authorized = false;
204         String reason = "";
205         Exception exception = null;
206 
207         try
208         {
209             session = this.getContext().getSession();
210 
211             if (session != null)
212             {
213                 user = session.getUser();
214 
215                 if (user != null)
216                 {
217                     if (user.canAccess(this.getStage()))
218                     {
219                         authorized = true;
220                     }
221                     else
222                     {
223                         authorized = false;
224                         reason = getMessages().get("noAccess").replace("${user}", "\"" + user.getID() + "\"")
225                                 .replace("${name}", this.getStage().getName());
226                     }
227                 }
228                 else
229                 {
230                     if (!theAuthorizationManager.hasAccessPublic(this.getStage()))
231                     {
232                         authorized = false;
233                         reason = getMessages().get("noAccess").replace("${user}", "")
234                                 .replace("${name}", this.getStage().getName());
235                     }
236                     else
237                         authorized = true;
238                 }
239             }
240             else
241             {
242                 // No session! Do not grant access! This is an error. Must
243                 // always exist a session.
244                 authorized = false;
245                 reason = getMessages().get("noSession").replace("${name}", this.getStage().getName());
246             }
247 
248         }
249         catch (RuntimeException runtimeException)
250         {
251             authorized = false;
252             exception = runtimeException;
253         }
254 
255         if (!authorized)
256         {
257             UserHasNoAccessToStage controllerException = new UserHasNoAccessToStage(
258                     ControllerExecutionStep.DISPATCHER_AUTHORIZATION, reason, exception);
259 
260             controllerException.addToExceptionContext("Context", this.getContext());
261 
262             throw controllerException;
263         }
264     }
265 
266     /**
267      * This method checks if the license is valid.
268      * 
269      * @return T if stage is registered, F otherwise
270      */
271     protected boolean checkLicense()
272     {
273         return this.getStage().isRegistered();
274     }
275 
276     /**
277      * Concludes the execution cycle. Touches the session to ensure it stays alive and returns any service objects to
278      * the pool (not implemented as of version 0.0.1). If session is not active, it's not kept alive and thus on the
279      * next request the access will be denied. 'Finalize' would be better name but it's forbidden due to a name-clash
280      * with <code>java.lang.Object's finalize()</code> method.
281      * 
282      * @throws ControllerException
283      *             when any runtime exception is thrown
284      */
285     protected void conclude() throws ControllerException
286     {
287         try
288         {
289             ((IPrivateDIFSession) this.getContext().getRequest().getSession()).forceKeepAlive();
290             // Keep session alive if it has not timed out
291             theSessionManager.update(this.getContext().getSession());
292         }
293         catch (RuntimeException runtimeException)
294         {
295             ControllerException controllerException = new ControllerException(
296                     ControllerExecutionStep.DISPATCHER_CONCLUDE, runtimeException);
297 
298             controllerException.addToExceptionContext("Context", this.getContext());
299 
300             throw controllerException;
301         }
302     }
303 
304     /**
305      * Receives a BaseDIFRequest object as parameter and creates a service execution context for the request.
306      * 
307      * @param difRequest
308      *            the request received from AbstractChAL
309      * @return The request's context for service execution
310      * @throws ControllerException
311      *             when any runtime exception is thrown
312      */
313     final private DIFContext createContext(IDIFRequest difRequest) throws ControllerException
314     {
315         DIFContext theContext = null;
316 
317         try
318         {
319             theContext = new DIFContext();
320 
321             theContext.setStage(difRequest.getStage());
322             theContext.setRequest(difRequest);
323 
324             return theContext;
325 
326         }
327         catch (RuntimeException runtimeException)
328         {
329             ControllerException controllerException = new ControllerException(
330                     ControllerExecutionStep.DISPATCHER_CREATE_CONTEXT, runtimeException);
331 
332             controllerException.addToExceptionContext("Context", theContext);
333 
334             throw controllerException;
335         }
336 
337     }
338 
339     /**
340      * Generates the response that will be sent back to AbstractChAL from the request's execution context.
341      * 
342      * @param stageInstance
343      *            the executed stage instance
344      * @return The response to send back to AbstractChAL
345      * @throws ControllerException
346      *             if an exception has occurred
347      */
348     protected DIFResponse createResponse(IStageInstance stageInstance) throws ControllerException
349     {
350         try
351         {
352             DIFResponse difResponse = new DIFResponse();
353 
354             difResponse.setView(this.getContext().getView());
355             difResponse.setResultMessage(this.getContext().getResultMessage());
356             difResponse.setStageResults(this.getContext().getStageResults());
357             difResponse.setRequest(this.getContext().getRequest());
358             difResponse.setStageInstance(stageInstance);
359 
360             return difResponse;
361         }
362         catch (RuntimeException runtimeException)
363         {
364             ControllerException controllerException = new ControllerException(
365                     ControllerExecutionStep.DISPATCHER_CREATE_RESPONSE, runtimeException);
366 
367             controllerException.addToExceptionContext("Context", this.getContext());
368             controllerException.addToExceptionContext("Stage Instance", stageInstance);
369 
370             throw controllerException;
371         }
372     }
373 
374     /**
375      * @see pt.digitalis.dif.controller.interfaces.IDIFDispatcher#dispatch(pt.digitalis.dif.controller.interfaces.IDIFRequest)
376      */
377     final public DIFResponse dispatch(IDIFRequest difRequest) throws BusinessFlowException, ControllerException
378     {
379         // Initialize framework
380         DIFInitializer.initialize(true, true);
381 
382         // Create an execution context for the incoming request
383         this.setContext(createContext(difRequest));
384 
385         // Create the response
386         DIFResponse difResponse = null;
387         IStageInstance stageInstance = null;
388         boolean firstTime = true;
389 
390         while (firstTime || this.getContext().hasRedirection())
391         {
392             if (!firstTime)
393             {
394                 try
395                 {
396                     this.getContext().handleRedirection();
397 
398                     if (stageInstance != null)
399                         stageInstance.setContext(this.getContext());
400 
401                 }
402                 catch (RuntimeException runtimeException)
403                 {
404                     ControllerException controllerException = new ControllerException(
405                             ControllerExecutionStep.DISPATCHER_REDIRECTION, runtimeException);
406 
407                     controllerException.addToExceptionContext("Context", this.getContext());
408 
409                     throw controllerException;
410                 }
411             }
412             try
413             {
414                 // Run the task steps
415                 stageInstance = runDispatchingSteps(!firstTime);
416 
417             }
418             catch (ControllerException exception)
419             {
420 
421                 if (ControllerExecutionStep.DISPATCHER_AUTHORIZATION.equals(exception.getStep()))
422                 {
423                     // If the stage has no access we must determine what to
424                     // do...
425                     handleNonAuthorizedAccess(stageInstance, exception);
426                 }
427                 else
428                     throw exception;
429             }
430             firstTime = false;
431         }
432 
433         // Create a response to send back to AbstractChAL
434         difResponse = createResponse(stageInstance);
435 
436         return difResponse;
437     }
438 
439     /**
440      * Actions to perform after a successful login
441      * 
442      * @param ssoInfo
443      *            the single sign-on object
444      */
445     protected void doAfterLogin(SSOInfo ssoInfo)
446     {
447         // Nothing to do. This is here to facilitate SSOs integrations
448     }
449 
450     /**
451      * Actions to perform after a successful logout
452      * 
453      * @param ssoInfo
454      *            the single sign-on object
455      */
456     protected void doAfterLogout(SSOInfo ssoInfo)
457     {
458         // Nothing to do. This is here to facilitate SSOs integrations
459     }
460 
461     /**
462      * Executes the appropriate tasks to serve the request.
463      * 
464      * @return the executed stage instance
465      * @throws BusinessFlowException
466      *             when an exception is thrown by the stage's init/execute steps
467      * @throws ControllerException
468      *             when any runtime exception is thrown
469      */
470     @Trace(metricName = "DIF:Dispatcher:Execute", dispatcher = true)
471     protected IStageInstance execute() throws BusinessFlowException, ControllerException
472     {
473         IStageInstance stage = null;
474         ViewObject theView = null;
475 
476         try
477         {
478             stage = this.getStage().getInstance();
479 
480             // If the stage is configured for authentication error injection...
481             if (stage.hasAuthenticationErrorInjection())
482                 stage.setAuthenticationError(getAuthenticationException());
483 
484             // Business initialization
485             stage._CG_init(this.getContext());
486 
487             if (!stage.hasParameterErrorInjection() && stage.getParameterErrors().hasErrors())
488                 DIFLogger.getLogger().warn(
489                         "There were errors in the parameter validation witch will be ignored since"
490                                 + " there is no @InjectParameterErrors anotated attribute on the stage "
491                                 + stage.getID());
492 
493             // Business logic execution
494             theView = stage._CG_execute(this.getContext());
495 
496             // Business finalization
497             stage._CG_finalize(this.getContext());
498 
499             // Set the View in context
500             this.getContext().setView(theView);
501 
502             // Add the current stage to the navigation history object
503             this.getContext().getSession().getNavigationHistory().addStage(stage);
504 
505             return stage;
506         }
507         catch (RuntimeException runtimeException)
508         {
509             ControllerException controllerException = new ControllerException(
510                     ControllerExecutionStep.DISPATCHER_EXECUTE, runtimeException);
511 
512             controllerException.addToExceptionContext("Context", this.getContext());
513             controllerException.addToExceptionContext("Stage", stage);
514             controllerException.addToExceptionContext("View object to Render", theView);
515 
516             throw controllerException;
517         }
518     }
519 
520     /**
521      * @return the authenticationException
522      */
523     public ControllerException getAuthenticationException()
524     {
525         return authenticationException;
526     }
527 
528     /**
529      * @return the channel ID read from the {@link Channel} annotation
530      */
531     public String getChannelID()
532     {
533         if (channelID == null)
534             channelID = this.getClass().getAnnotation(Channel.class).value();
535 
536         return channelID;
537     }
538 
539     /**
540      * Inspector for the execution context.
541      * 
542      * @return the execution context
543      */
544     protected IDIFContext getContext()
545     {
546         return this.difContext;
547     }
548 
549     /**
550      * Inspector for the identity manager.
551      * 
552      * @return the defined identity manager
553      */
554     protected IIdentityManager getIdentityManager()
555     {
556         return this.theIdentityManager;
557     }
558 
559     /**
560      * Lazy loading getter of messages
561      * 
562      * @return the messages
563      */
564     protected Map<String, String> getMessages()
565     {
566         // If the messages have not yet been loaded do it now
567         if (messages == null)
568         {
569             messages = getMessageManager().collectEntityMessagesFromRepository(AbstractDIFDispatcher.class);
570         }
571 
572         return messages.getMessages(getContext().getLanguage());
573     }
574 
575     /**
576      * Used to inject the SSO Info for the dispatcher authentication process
577      * 
578      * @return the SSOInfo if available
579      */
580     protected SSOInfo getSSOInfo()
581     {
582         // Nothing to do. This is here to facilitate SSOs integrations
583         return null;
584     }
585 
586     /**
587      * Inspector for the stage to execute.
588      * 
589      * @return the stage to execute
590      */
591     private IStage getStage()
592     {
593         return this.theStage;
594     }
595 
596     /**
597      * Handles the occurred exception to redirect to a given handler and perform cleanup actions
598      * 
599      * @param exception
600      *            the raised exception
601      * @return T if the exception was handled and should not be re-throwned
602      * @throws BusinessFlowException
603      * @throws ControllerException
604      */
605     protected boolean handleException(Exception exception) throws BusinessFlowException, ControllerException
606     {
607 
608         performCleanup(this.getContext(), false);
609 
610         if (ExceptionHandlers.hasHandler(exception))
611         {
612             ExceptionHandlers.handleException(this.getContext(), exception);
613 
614             return true;
615         }
616         else
617             return false;
618     }
619 
620     /**
621      * Handles a non authorized access
622      * 
623      * @param stageInstance
624      *            the stage instance requested
625      * @param exception
626      *            the exception thrown
627      * @throws ControllerException
628      *             if the error was not a non authorized access
629      */
630     abstract protected void handleNonAuthorizedAccess(IStageInstance stageInstance, ControllerException exception)
631             throws ControllerException;
632 
633     /**
634      * Implements the authentication execution logic
635      * 
636      * @return the authentication result
637      * @throws ControllerException
638      */
639     protected DispatcherAuthenticationResult performAuthentication() throws ControllerException
640     {
641         DispatcherAuthenticationResult dispatcherResult = DispatcherAuthenticationResult.NO_ACTION;
642 
643         if (this.getContext().getSession().isLogged())
644         {
645             /*
646              * The logout action must be performed before any of the other operations. There are two ways to invoke
647              */
648             if ("true".equals(this.getContext().getRequest().getParameter(IDIFRequest.LOGOUT_PARAMETER_ID)))
649             {
650 
651                 String providerId = (String) this.getContext().getSession()
652                         .getAttribute(DIFSession.REMOTE_AUTHENTICATION_PROVIDER_LOGOUT);
653                 if (providerId != null)
654                 {
655                     String title = getMessages().get("remoteAuthenticationLogoutTitle").replace("${providerid}",
656                             providerId);
657                     String text = getMessages().get("remoteAuthenticationLogoutText").replace("${providerid}",
658                             providerId);
659 
660                     this.getContext().addResultMessage("warn", title, text, true);
661                     this.getContext().getSession().addAttribute(DIFSession.REMOTE_AUTHENTICATION_PROVIDER_LOGOUT, null);
662                 }
663 
664                 theSessionManager.logOut(this.getContext().getSession().getSessionID());
665                 dispatcherResult = DispatcherAuthenticationResult.LOGOUT;
666             }
667         }
668 
669         /*
670          * The stage requires authentication
671          */
672         else if (this.getStage().hasAuthentication())
673         {
674             Object userID = null;
675             Object suppliedPassword = null;
676             String reason = "";
677             Exception exception = null;
678 
679             try
680             {
681                 // ...get credentials from the request...
682                 userID = this.getContext().getRequest().getParameter(IDIFRequest.USER_PARAMETER_ID);
683                 suppliedPassword = this.getContext().getRequest().getParameter(IDIFRequest.PASSWORD_PARAMETER_ID);
684 
685                 // The user is logged and is the same user, unnecessary login the user again
686                 if (this.getContext().getSession().isLogged()
687                         && this.getContext().getSession().getUser().getID().equals(userID))
688                 {
689 
690                     /* There is no need to authenticate the same user! */
691                     userID = null;
692                     suppliedPassword = null;
693                 }
694 
695                 if ((userID == null || suppliedPassword == null)
696                         && dispatcherResult == DispatcherAuthenticationResult.NO_ACTION)
697                     // No user logged and no authentication asked, so,
698                     // authenticate does nothing but has passed
699                     return dispatcherResult;
700                 else
701                 {
702 
703                     // If user's credentials are valid
704                     if (userID != null && suppliedPassword != null
705                             && this.validateUserCredentials(userID.toString(), suppliedPassword.toString()))
706                     {
707                         // Log user
708                         IDIFSession session = theSessionManager.logIn(getContext().getSession().getSessionID(),
709                                 userID.toString(), suppliedPassword.toString());
710                         this.difContext.setSession(session);
711 
712                         if (session.isLogged())
713                         {
714                             dispatcherResult = DispatcherAuthenticationResult.LOGIN;
715                         }
716                         else
717                         {
718                             dispatcherResult = DispatcherAuthenticationResult.FAILED;
719                             // result = false;
720                             reason = getMessages().get("failedWithGoodUser");
721                         }
722                     }
723                     else
724                     {
725                         // A bad user/pass was given. Authentication has failed
726                         // result = false;
727                         dispatcherResult = DispatcherAuthenticationResult.FAILED;
728 
729                         if (userID != null && this.getIdentityManager().userExists(userID.toString()))
730                             reason = getMessages().get("wrongPass");
731                         else
732                             reason = getMessages().get("noUser");
733                     }
734                 }
735             }
736             catch (IdentityManagerException identityManagerException)
737             {
738                 // result = false;
739                 dispatcherResult = DispatcherAuthenticationResult.FAILED;
740                 reason = "Could not access identity manager to validate the user's credentials! ";
741                 exception = identityManagerException;
742             }
743             catch (AuthenticationManagerException authenticationManagerException)
744             {
745                 // result = false;
746                 dispatcherResult = DispatcherAuthenticationResult.FAILED;
747                 reason = "Could not access authentication manager to validate the user's credentials! ";
748                 exception = authenticationManagerException;
749             }
750             catch (RuntimeException runtimeException)
751             {
752                 // result = false;
753                 dispatcherResult = DispatcherAuthenticationResult.FAILED;
754                 reason = null;
755                 exception = runtimeException;
756             }
757             catch (ControllerException controllerException)
758             {
759                 dispatcherResult = DispatcherAuthenticationResult.FAILED;
760                 reason = controllerException.getMessage();
761                 exception = controllerException;
762             }
763 
764             if (dispatcherResult != DispatcherAuthenticationResult.FAILED)
765             {
766                 return dispatcherResult;
767             }
768             else
769             {
770                 ControllerException controllerException;
771                 if (exception instanceof ControllerException)
772                 {
773                     controllerException = (ControllerException) exception;
774                 }
775                 else
776                 {
777 
778                     controllerException = new ControllerException(ControllerExecutionStep.DISPATCHER_AUTHENTICATION,
779                             reason, exception);
780 
781                     controllerException.addToExceptionContext("Context", this.getContext());
782                     controllerException.addToExceptionContext("Supplied User ID", userID);
783 
784                     if (suppliedPassword != null)
785                         controllerException.addToExceptionContext("Supplied User Password",
786                                 StringUtils.getRepeatedString("*", suppliedPassword.toString().length()));
787                 }
788                 if (reason != null)
789                 {
790 
791                     // A known error occurred.
792                     if (this.getStage().hasAuthenticationErrorInjection())
793                     {
794                         // If the stage has asked authentication error injection...
795                         // capture, set it and proceed with no
796                         // errors
797                         setAuthenticationException(controllerException);
798 
799                         return dispatcherResult;
800 
801                     }
802                     else
803                         // else proceed with normal error launch...
804                         throw controllerException;
805 
806                 }
807                 else
808                 {
809                     throw controllerException;
810                 }
811             }
812         }
813 
814         return dispatcherResult;
815     }
816 
817     /**
818      * Post Authentication execution
819      * 
820      * @param result
821      *            the DispatcherAuthenticationResult
822      */
823     protected void postAuthentication(DispatcherAuthenticationResult result)
824     {
825         if (result != DispatcherAuthenticationResult.NO_ACTION)
826         {
827             // For each IAuthenticationSSO will execute the result action
828             for (Entry<String, IAuthenticationPlugin> entry: DIFIoCRegistry.getRegistry()
829                     .getImplementationsMap(IAuthenticationPlugin.class).entrySet())
830             {
831                 // The result action was a login, so will execute is after execution
832                 if (result == DispatcherAuthenticationResult.LOGIN)
833                     entry.getValue().doAfterLogin(this.getContext());
834                 // The result action was a logout, so will execute is after execution
835                 else if (result == DispatcherAuthenticationResult.LOGOUT)
836                     entry.getValue().doAfterLogout(this.getContext());
837             }
838         }
839 
840         String providerId = (String) this.getContext().getSession()
841                 .getAttribute(DIFSession.REMOTE_AUTHENTICATION_PROVIDER_LOGIN);
842 
843         /* The Remote Authentication Servlet publish that a authentication was performed by a external provider */
844         if (providerId != null)
845         {
846             String title = getMessages().get("remoteAuthenticationLoginTitle").replace("${providerid}", providerId);
847             String text = getMessages().get("remoteAuthenticationLoginText").replace("${providerid}", providerId);
848 
849             this.getContext().addResultMessage("warn", title, text, true);
850             this.getContext().getSession().addAttribute(DIFSession.REMOTE_AUTHENTICATION_PROVIDER_LOGIN, null);
851             this.getContext().getSession().addAttribute(DIFSession.REMOTE_AUTHENTICATION_PROVIDER_LOGOUT, providerId);
852         }
853 
854     }
855 
856     /**
857      * Pre Authentication execution
858      */
859     protected void preAuthentication()
860     {
861         // For each IAuthenticationSSO will execute is before login or before logout action
862         for (Entry<String, IAuthenticationPlugin> entry: DIFIoCRegistry.getRegistry()
863                 .getImplementationsMap(IAuthenticationPlugin.class).entrySet())
864         {
865             // execute the before login sso login
866             entry.getValue().doBeforeLogin(this.getContext());
867             // execute the before logout logic
868             entry.getValue().doBeforeLogout(this.getContext());
869         }
870     }
871 
872     /**
873      * Execute the dispatching cycle steps according to established rules for the business object. This method defines
874      * the dispatching cycle steps order of execution. This sequence should not change and thus method overriding on
875      * subclasses is forbidden by declaring the method <code>final</code>.
876      * 
877      * @param isRedirection
878      * @return the executed stage instance
879      * @throws BusinessFlowException
880      *             when an exception is thrown by the stage's init/execute steps
881      * @throws ControllerException
882      *             when any runtime exception is thrown
883      */
884     @Trace(metricName = "DIF:Dispatcher", dispatcher = true)
885     final private IStageInstance runDispatchingSteps(Boolean isRedirection) throws BusinessFlowException,
886             ControllerException
887     {
888         IStageInstance stageInstance = null;
889 
890         try
891         {
892             // Get an instance of the stage.
893             this.setStage(DEMRegistryImpl.getRegistry().getStage(this.getContext().getStage()));
894 
895             // MonitorUtil.detailLogEntry("DIF:Dispatcher", "Run: " + this.getStage().getID());
896 
897             // 1st: Validates the session and context. Checks if the
898             // application/service/stage is registered
899 
900             validate(isRedirection);
901 
902             // 2nd: Authenticate
903             authenticate();
904 
905             // Report NewRelic if in test or develop mode...
906             if (DIFStartupConfiguration.getTestingMode() || DIFStartupConfiguration.getDeveloperMode())
907             {
908                 IDIFUser user = this.getContext().getSession().getUser();
909 
910                 if (user != null)
911                     NewRelic.addCustomParameter("User", user.getID() + " [" + user.getName() + "]");
912             }
913 
914             // 3rd: Authorize
915             if (this.getStage().hasAuthorization())
916                 authorize();
917 
918             // 4th: Execute business logic
919             stageInstance = execute();
920 
921             // 5th: End execution
922             conclude();
923 
924             performCleanup(this.getContext(), true);
925 
926         }
927         catch (BusinessFlowException e)
928         {
929             if (!handleException(e))
930                 throw e;
931         }
932         catch (ControllerException e)
933         {
934             if (!handleException(e))
935                 throw e;
936         }
937         catch (Exception e)
938         {
939             if (!handleException(e))
940                 throw new ControllerException(ControllerExecutionStep.DISPATCHER_EXECUTE, e);
941         }
942         finally
943         {
944             // If the session isMarked for removal, it means that in the process of processing the current request (or
945             // one of the redirection requests within, the session has timed out. Since the time out informs the user of
946             // this process, we can get a new one.
947             if (this.getContext().getSession().isMarkedForRemoval())
948             {
949                 this.getContext().setSession(
950                         theSessionManager.createSession(this.getContext().getSession().getSessionID()));
951                 theSessionManager.logOut(this.getContext().getSession().getSessionID());
952                 postAuthentication(DispatcherAuthenticationResult.LOGOUT);
953             }
954 
955         }
956 
957         return stageInstance;
958     }
959 
960     /**
961      * @param authenticationException
962      *            the authenticationException to set
963      */
964     public void setAuthenticationException(ControllerException authenticationException)
965     {
966         this.authenticationException = authenticationException;
967     }
968 
969     /**
970      * Modifier for the execution context.
971      * 
972      * @param newContext
973      *            the new execution context to set
974      */
975     private void setContext(DIFContext newContext)
976     {
977         this.difContext = newContext;
978     }
979 
980     /**
981      * Modifier for the stage to execute.
982      * 
983      * @param newStage
984      *            the new stage to execute
985      */
986     private void setStage(IStage newStage)
987     {
988         this.theStage = newStage;
989     }
990 
991     /**
992      * Validates the session and the stage. This method always runs. To avoid overriding on base classes it is declared
993      * <code>final</code>. Session validation includes checking if the stage is active and hasn't timed out. Stage
994      * validation includes checking if the stage is registrable and if so checks if the license is valid.
995      * 
996      * @param isRedirection
997      * @throws ControllerException
998      *             when any runtime exception is thrown
999      */
1000     @Trace(metricName = "DIF:Dispatcher:Validate", dispatcher = true)
1001     final protected void validate(Boolean isRedirection) throws ControllerException
1002     {
1003         try
1004         {
1005             if (this.getStage() == null)
1006             {
1007                 // the stage does not exist
1008                 ControllerException exception = new ControllerException(
1009                         ControllerExecutionStep.DISPATCHER_VALIDATE_REQUEST, INVALID_STAGE_ERROR_MESSAGE);
1010                 exception.addToExceptionContext("Stage", getContext().getStage());
1011 
1012                 throw exception;
1013             }
1014 
1015             if (!isRedirection && this.getContext().getSession().hasTimedOut()
1016                     && this.getContext().getSession().isLogged()
1017                     && !"true".equals(this.getContext().getRequest().getParameter(IDIFRequest.LOGOUT_PARAMETER_ID))
1018                     && !this.getContext().getRequest().isAjaxMode() && !this.getContext().getRequest().isTemplateMode())
1019             {
1020                 SessionTimeoutException exception = new SessionTimeoutException(
1021                         ControllerExecutionStep.DISPATCHER_VALIDATE_REQUEST);
1022                 exception.addToExceptionContext("Stage", getContext().getStage());
1023 
1024                 ((IPrivateDIFSession) (this.getContext().getSession())).setMarkedForRemoval(true);
1025 
1026                 throw exception;
1027             }
1028 
1029             Object invalidBrowserFlagAccepted = this.getContext().getSession()
1030                     .getAttribute(Constants.INVALID_BROWSER_ACCEPTED);
1031             Object clientValidations = this.getContext().getRequest()
1032                     .getAttribute(IDIFRequest.CLIENT_VALIDATIONS_ATTRIBUTE_ID);
1033             Boolean allowClientValidations = true;
1034             if (clientValidations != null)
1035             {
1036                 allowClientValidations = (Boolean) clientValidations;
1037             }
1038 
1039             if (!this.getContext().getRequest().isComponentMode() && !this.getContext().getRequest().isTemplateMode()
1040                     && invalidBrowserFlagAccepted == null
1041                     && !this.getContext().getRequest().getClient().getSupportedBrowser()
1042                     && this.getContext().getStage() != "BrowserValidator" && allowClientValidations)
1043                 this.getContext().redirectTo("BrowserValidator");
1044 
1045             if (!checkLicense())
1046             {
1047                 LicenseViolationException exception = new LicenseViolationException(
1048                         ControllerExecutionStep.DISPATCHER_VALIDATE_REQUEST);
1049                 exception.addToExceptionContext("Stage", getContext().getStage());
1050 
1051                 throw exception;
1052             }
1053         }
1054         catch (RuntimeException runtimeException)
1055         {
1056             ControllerException controllerException = new ControllerException(
1057                     ControllerExecutionStep.DISPATCHER_VALIDATE_REQUEST, runtimeException);
1058 
1059             controllerException.addToExceptionContext("Context", this.getContext());
1060             controllerException.addToExceptionContext("Stage", this.getContext());
1061 
1062             throw controllerException;
1063         }
1064     }
1065 
1066     /**
1067      * Validate the user credentials
1068      * 
1069      * @param userId
1070      *            the userId
1071      * @param password
1072      *            the password
1073      * @return validation result
1074      * @throws IdentityManagerException
1075      *             if a IdentityManager Exception occurs.
1076      * @throws ControllerException
1077      */
1078     protected boolean validateUserCredentials(String userId, String password) throws IdentityManagerException,
1079             ControllerException
1080     {
1081         boolean result = this.getIdentityManager().isIdentityValid(userId, password);
1082 
1083         if (result)
1084         {
1085             // For each IAuthenticationPlugin will execute validateUser action
1086             for (Entry<String, IAuthenticationPlugin> entry: DIFIoCRegistry.getRegistry()
1087                     .getImplementationsMap(IAuthenticationPlugin.class).entrySet())
1088             {
1089                 // all the authentication plugin's must return true, so that the authentication proceed
1090                 result = entry.getValue().validateUser(this.getContext(), userId, password);
1091                 if (!result)
1092                 {
1093                     break;
1094                 }
1095             }
1096         }
1097 
1098         return result;
1099     }
1100 }