/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.esri.gpt.framework.jsf;
import com.esri.gpt.framework.collection.StringAttributeMap;
import com.esri.gpt.framework.context.RequestContext;
import com.esri.gpt.framework.http.CredentialProvider;
import com.esri.gpt.framework.http.HttpClient401Exception;
import com.esri.gpt.framework.security.identity.NotAuthorizedException;
import com.esri.gpt.framework.security.principal.RoleSet;
import com.esri.gpt.framework.util.LogUtil;
import com.esri.gpt.framework.util.Val;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.faces.context.ExternalContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;
import javax.servlet.http.HttpServletResponse;
/**
* Super-class for a JSF based action listener.
*/
public class BaseActionListener implements ActionListener {
//class variables =============================================================
// instance variables ==========================================================
private FacesContextBroker _contextBroker = new FacesContextBroker();
private String _key = "";
private String _navigationOutcome = "";
private String _securityRoles = "";
private RoleSet _securityRoleSet = new RoleSet();
// constructors ================================================================
/** Default constructor. */
public BaseActionListener() {}
// properties ==================================================================
/**
* Gets the Faces context broker.
* @return the Faces context broker
*/
protected FacesContextBroker getContextBroker() {
return _contextBroker;
}
/**
* Gets the key associated with the action listener.
* @return the key
*/
public String getKey() {
return _key;
}
/**
* Sets the key associated with the action listener.
* @param key the key
*/
public void setKey(String key) {
_key = Val.chkStr(key);
}
/**
* Gets the logger.
* @return the logger
*/
public Logger getLogger() {
return LogUtil.getLogger();
}
/**
* Gets the navigation outcome.
* @return the navigation outcome
*/
public String getNavigationOutcome() {
return _navigationOutcome;
}
/**
* Sets the navigation outcome.
* @param outcome the navigation outcome
*/
protected void setNavigationOutcome(String outcome) {
_navigationOutcome = Val.chkStr(outcome);
}
/**
* Provides an interface to prepare the view for a page.
* <p>
* Some pages may require preparation during the render response phase
* of the JSF cycle. In such cases, the page should include a hidden
* variable to trigger view preparation. Example:
* <h:inputHidden value="#{SomeController.prepareView}"/>
* <p>
* Once triggered, the onPrepareView() method for the controller
* will be invoked. Override the onPrepareView() method to provide
* controller specific behavior.
* @return always an empty string
*/
public String getPrepareView() {
try {
RequestContext context = onPrepareViewStarted();
authorizeAction(context);
onPrepareView(context);
} catch (NotAuthorizedException e) {
try {
ExternalContext ec = getContextBroker().getExternalContext();
ec.redirect(Val.chkStr(ec.getRequestContextPath())+"/catalog/main/home.page");
} catch (Throwable t) {
getLogger().log(Level.SEVERE,"Exception raised.",t);
}
} catch (Throwable t) {
getLogger().log(Level.SEVERE,"Exception raised.",t);
} finally {
onPrepareViewCompleted();
}
return "";
}
/**
* Provides the setter for the prepareView property.
* @param ignored always ignored
*/
public void setPrepareView(String ignored) {}
/**
* Gets the security roles string associated with the action listener.
* @return the security roles string
*/
public String getSecurityRoles() {
return _securityRoles;
}
/**
* Gets the security roles string associated with the action listener.
* @param roles the security roles string
*/
public void setSecurityRoles(String roles) {
_securityRoles = Val.chkStr(roles);
_securityRoleSet.clear();
_securityRoleSet.addDelimited(getSecurityRoles());
}
/**
* Gets the security role set associated with the action listener.
* @return the security role set
*/
protected RoleSet getSecurityRoleSet() {
return _securityRoleSet;
}
// methods =====================================================================
/**
* Asserts that the active user is logged in.
* @param context the request context associated with this execution thread
* @throws NotAuthorizedException if the user does not have a required role
*/
protected void assertLoggedIn(RequestContext context)
throws NotAuthorizedException {
context.getUser().getAuthenticationStatus().assertLoggedIn();
}
/**
* Authorizes an action.
* <br/>Authorization is based upon the security roles for this
* action listener and the authenticated roles for the user associated
* with the supplied request context.
* <br/>If the security roles for this action are empty, the action is authorized.
* @param context the request context associated with this execution thread
* @throws NotAuthorizedException if the user does not have a required role
*/
protected void authorizeAction(RequestContext context)
throws NotAuthorizedException {
context.getUser().getAuthenticationStatus().authorizeAction(getSecurityRoleSet());
}
/**
* Extract the MessageBroker from the Faces context instance.
* @return the MessageBroker
*/
protected MessageBroker extractMessageBroker() {
return getContextBroker().extractMessageBroker();
}
/**
* Extract the request context from the Faces context instance.
* @return the request context
*/
public RequestContext extractRequestContext() {
return getContextBroker().extractRequestContext();
}
/**
* Handles an exception.
* @param t the exception
*/
protected void handleException(Throwable t) {
if (t instanceof NotAuthorizedException) {
setNavigationOutcome("homeDirect");
extractMessageBroker().addErrorMessage(t);
} else {
extractMessageBroker().addErrorMessage(t);
getLogger().log(Level.SEVERE,"Exception raised.",t);
}
}
/**
* Fired when the execution phase has completed.
*/
protected void onExecutionPhaseCompleted() {
RequestContext rc = extractRequestContext();
rc.onExecutionPhaseCompleted();
}
/**
* Fired when the execution phase has started.
*/
protected RequestContext onExecutionPhaseStarted() {
return extractRequestContext();
}
/**
* Fired when the getPrepareView() property is accessed.
* <br/>This event is triggered from the page during the
* render response phase of the JSF cycle.
* @param context the context associated with the active request
* @throws Exception if an exception occurs
*/
protected void onPrepareView(RequestContext context) throws Exception {
// no default implementation
}
/**
* Fired when the onPrepareView() event has completed.
*/
protected void onPrepareViewCompleted() {
RequestContext rc = extractRequestContext();
rc.onPrepareViewCompleted();
}
/**
* Fired when the onPrepareView()event has started.
*/
protected RequestContext onPrepareViewStarted() {
return extractRequestContext();
}
/**
* Processes the JSF based action.
* <br/>This is the default entry point for a JSF ActionListener.
* <br/>The default behavior is: flag the start of the execution phase,
* authorize the action, invoke the processPreparedAction() method,
* handle exceptions, flag the completion of the execution phase.
* @param event the associated JSF action event
* @throws AbortProcessingException if processing should be aborted
*/
public void processAction(ActionEvent event)
throws AbortProcessingException {
boolean autoAuthenticate = true;
try {
RequestContext context = onExecutionPhaseStarted();
StringAttributeMap params = context.getCatalogConfiguration().getParameters();
autoAuthenticate = !Val.chkStr(params.getValue("BaseServlet.autoAuthenticate")).equalsIgnoreCase("false");
authorizeAction(context);
if (autoAuthenticate) {
CredentialProvider.establishThreadLocalInstance(this.getContextBroker().extractHttpServletRequest());
}
processSubAction(event,context);
} catch (AbortProcessingException e) {
throw(e);
} catch (HttpClient401Exception remoteAuthException) {
if (autoAuthenticate) {
String realm = Val.chkStr(remoteAuthException.getRealm());
HttpServletResponse httpResponse = this.getContextBroker().extractHttpServletResponse();
httpResponse.setHeader("WWW-Authenticate","Basic realm=\""+realm+"\"");
try {
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} catch (IOException ioe) {
throw new AbortProcessingException("Cannot send 401 to client.",ioe);
} finally{
this.getContextBroker().getFacesContext().responseComplete();
}
} else {
handleException(remoteAuthException);
}
} catch (Throwable t) {
handleException(t);
} finally {
onExecutionPhaseCompleted();
}
}
/**
* This is the default entry point for a sub-class of this ActionListener.
* <br/>This BaseActionListener handles the JSF processAction method and
* invokes the processSubAction method of the sub-class.
* @param event the associated JSF action event
* @param context the context associated with the active request
* @throws AbortProcessingException if processing should be aborted
* @throws Exception if an exception occurs
*/
protected void processSubAction(ActionEvent event, RequestContext context)
throws AbortProcessingException, Exception {
// no default behavior
}
}