blob: 7d86e5edfaee969daab00ecccafaa181ba10f004 [file] [log] [blame]
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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 org.apache.struts2.jsf;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Iterator;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.application.ViewHandler;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.el.ValueBinding;
import javax.faces.event.PhaseId;
/**
* Restores the view or component tree
*/
public class RestoreViewInterceptor extends FacesInterceptor {
private static final long serialVersionUID = -1500785113037140668L;
/**
* Restore View (JSF.2.2.1)
*
* @param viewId
* The view id
* @param facesContext
* The faces context
* @return true, if immediate rendering should occur
*/
protected boolean executePhase(String viewId, FacesContext facesContext) {
boolean skipFurtherProcessing = false;
if (log.isTraceEnabled())
log.trace("entering restoreView");
informPhaseListenersBefore(facesContext, PhaseId.RESTORE_VIEW);
try {
if (isResponseComplete(facesContext, "restoreView", true)) {
// have to skips this phase
return true;
}
if (shouldRenderResponse(facesContext, "restoreView", true)) {
skipFurtherProcessing = true;
}
ExternalContext externalContext = facesContext.getExternalContext();
String defaultSuffix = externalContext
.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
String suffix = defaultSuffix != null ? defaultSuffix
: ViewHandler.DEFAULT_SUFFIX;
if (viewId != null) {
viewId += suffix;
}
if (viewId == null) {
if (!externalContext.getRequestServletPath().endsWith("/")) {
try {
externalContext.redirect(externalContext
.getRequestServletPath()
+ "/");
facesContext.responseComplete();
return true;
} catch (IOException e) {
throw new FacesException("redirect failed", e);
}
}
}
Application application = facesContext.getApplication();
ViewHandler viewHandler = application.getViewHandler();
// boolean viewCreated = false;
UIViewRoot viewRoot = viewHandler.restoreView(facesContext, viewId);
if (viewRoot == null) {
viewRoot = viewHandler.createView(facesContext, viewId);
viewRoot.setViewId(viewId);
facesContext.renderResponse();
// viewCreated = true;
}
facesContext.setViewRoot(viewRoot);
/*
* This section has been disabled because it causes some bug. Be
* careful if you need to re-enable it. Furthermore, for an unknown
* reason, it seems that by default it is executed (i.e.
* log.isTraceEnabled() is true). Bug example : This traceView
* causes DebugUtil.printComponent to print all the attributes of
* the view components. And if you have a data table within an
* aliasBean, this causes the data table to initialize it's value
* attribute while the alias isn't set. So, the value initializes
* with an UIData.EMPTY_DATA_MODEL, and not with the aliased one.
* But as it's initialized, it will not try to get the value from
* the ValueBinding next time it needs to. I expect this to cause
* more similar bugs. TODO : Completely remove or be SURE by default
* it's not executed, and it has no more side-effects.
*
* if (log.isTraceEnabled()) { //Note: DebugUtils Logger must also
* be in trace level DebugUtils.traceView(viewCreated ? "Newly
* created view" : "Restored view"); }
*/
if (facesContext.getExternalContext().getRequestParameterMap()
.isEmpty()) {
// no POST or query parameters --> set render response flag
facesContext.renderResponse();
}
recursivelyHandleComponentReferencesAndSetValid(facesContext,
viewRoot);
} finally {
informPhaseListenersAfter(facesContext, PhaseId.RESTORE_VIEW);
}
if (isResponseComplete(facesContext, "restoreView", false)
|| shouldRenderResponse(facesContext, "restoreView", false)) {
// since this phase is completed we don't need to return right away
// even if the response is completed
skipFurtherProcessing = true;
}
if (!skipFurtherProcessing && log.isTraceEnabled())
log.trace("exiting restoreView ");
return skipFurtherProcessing;
}
/**
* Walk the component tree, executing any component-bindings to reattach
* components to their backing beans. Also, any UIInput component is marked
* as Valid.
* <p>
* Note that this method effectively breaks encapsulation; instead of asking
* each component to update itself and its children, this method just
* reaches into each component. That makes it impossible for any component
* to customise its behaviour at this point.
* <p>
* This has been filed as an issue against the spec. Until this issue is
* resolved, we'll add a new marker-interface for components to allow them
* to define their interest in handling children bindings themselves.
*/
protected void recursivelyHandleComponentReferencesAndSetValid(
FacesContext facesContext, UIComponent parent) {
recursivelyHandleComponentReferencesAndSetValid(facesContext, parent,
false);
}
protected void recursivelyHandleComponentReferencesAndSetValid(
FacesContext facesContext, UIComponent parent, boolean forceHandle) {
Method handleBindingsMethod = getBindingMethod(parent);
if (handleBindingsMethod != null && !forceHandle) {
try {
handleBindingsMethod.invoke(parent, new Object[] {});
} catch (Throwable th) {
log.error(
"Exception while invoking handleBindings on component with client-id:"
+ parent.getClientId(facesContext), th);
}
} else {
for (Iterator it = parent.getFacetsAndChildren(); it.hasNext();) {
UIComponent component = (UIComponent) it.next();
ValueBinding binding = component.getValueBinding("binding"); // TODO:
// constant
if (binding != null && !binding.isReadOnly(facesContext)) {
binding.setValue(facesContext, component);
}
if (component instanceof UIInput) {
((UIInput) component).setValid(true);
}
recursivelyHandleComponentReferencesAndSetValid(facesContext,
component);
}
}
}
/**
* This is all a hack to work around a spec-bug which will be fixed in
* JSF2.0
*
* @param parent
* @return true if this component is bindingAware (e.g. aliasBean)
*/
private static Method getBindingMethod(UIComponent parent) {
Class[] clazzes = parent.getClass().getInterfaces();
for (int i = 0; i < clazzes.length; i++) {
Class clazz = clazzes[i];
if (clazz.getName().indexOf("BindingAware") != -1) {
try {
return parent.getClass().getMethod("handleBindings",
new Class[] {});
} catch (NoSuchMethodException e) {
// return
}
}
}
return null;
}
}