blob: 914a440d15b193812d34378a925c159da2a4a78d [file] [log] [blame]
/*
* 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 javax.faces.component;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FactoryFinder;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.FacesEvent;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
import javax.faces.webapp.FacesServlet;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFJspProperty;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFProperty;
/**
* Creates a JSF View, which is a container that holds all of the components
* that are part of the view.
* <p>
* Unless otherwise specified, all attributes accept static values or EL
* expressions.
* </p>
* <p>
* See the javadoc for this class in the <a
* href="http://java.sun.com/j2ee/javaserverfaces/1.2/docs/api/index.html">JSF
* Specification</a> for further details.
* </p>
*/
@JSFComponent(name="f:view", bodyContent="JSP", tagClass="org.apache.myfaces.taglib.core.ViewTag")
@JSFJspProperty(name="binding", returnType="java.lang.String", tagExcluded=true)
public class UIViewRoot extends UIComponentBase
{
public static final String COMPONENT_TYPE = "javax.faces.ViewRoot";
public static final String COMPONENT_FAMILY = "javax.faces.ViewRoot";
public static final String UNIQUE_ID_PREFIX = "j_id";
private static final int ANY_PHASE_ORDINAL = PhaseId.ANY_PHASE.getOrdinal();
private final Logger logger = Logger.getLogger(UIViewRoot.class.getName());
/**
* The counter which will ensure a unique component id for every component instance in the tree that
* doesn't have an id attribute set.
*/
private long _uniqueIdCounter = 0;
private Locale _locale;
private String _renderKitId;
private String _viewId;
// todo: is it right to save the state of _events and _phaseListeners?
private List<FacesEvent> _events;
private List<PhaseListener> _phaseListeners;
private MethodExpression _beforePhaseListener;
private MethodExpression _afterPhaseListener;
private transient Lifecycle _lifecycle = null;
private interface Processor
{
void process();
}
/**
* Construct an instance of the UIViewRoot.
*/
public UIViewRoot()
{
setRendererType(null);
}
public void queueEvent(FacesEvent event)
{
checkNull(event, "event");
if (_events == null)
{
_events = new ArrayList<FacesEvent>();
}
_events.add(event);
}
public void processDecodes(final FacesContext context)
{
checkNull(context, "context");
process(context, PhaseId.APPLY_REQUEST_VALUES, new Processor()
{
public void process()
{
UIViewRoot.super.processDecodes(context);
}
}, true);
}
public void processValidators(final FacesContext context)
{
checkNull(context, "context");
process(context, PhaseId.PROCESS_VALIDATIONS, new Processor()
{
public void process()
{
UIViewRoot.super.processValidators(context);
}
}, true);
}
public void processUpdates(final FacesContext context)
{
checkNull(context, "context");
process(context, PhaseId.UPDATE_MODEL_VALUES, new Processor()
{
public void process()
{
UIViewRoot.super.processUpdates(context);
}
}, true);
}
public void processApplication(final FacesContext context)
{
checkNull(context, "context");
process(context, PhaseId.INVOKE_APPLICATION, null, true);
}
public void encodeBegin(FacesContext context) throws java.io.IOException
{
checkNull(context, "context");
boolean skipPhase = false;
try
{
skipPhase = notifyListeners(context, PhaseId.RENDER_RESPONSE,
getBeforePhaseListener(), true);
}
catch (Exception e)
{
// following the spec we have to swallow the exception
logger.log(Level.SEVERE,
"Exception while processing phase listener: "
+ e.getMessage(), e);
}
if (!skipPhase)
{
super.encodeBegin(context);
}
}
public void encodeEnd(FacesContext context) throws java.io.IOException
{
checkNull(context, "context");
super.encodeEnd(context);
try
{
notifyListeners(context, PhaseId.RENDER_RESPONSE,
getAfterPhaseListener(), false);
}
catch (Exception e)
{
// following the spec we have to swallow the exception
logger.log(Level.SEVERE,
"Exception while processing phase listener: "
+ e.getMessage(), e);
}
}
/**
* Provides a unique id for this component instance.
*/
public String createUniqueId()
{
StringBuilder bld = __getSharedStringBuilder();
return bld.append(UNIQUE_ID_PREFIX).append(
_uniqueIdCounter++).toString();
}
/**
* The locale for this view.
* <p>
* Defaults to the default locale specified in the faces configuration file.
* </p>
*/
@JSFProperty
public Locale getLocale()
{
if (_locale != null)
{
return _locale;
}
ValueExpression expression = getValueExpression("locale");
if (expression != null)
{
return (Locale) expression.getValue(getFacesContext()
.getELContext());
}
else
{
Object locale = getFacesContext().getApplication().getViewHandler()
.calculateLocale(getFacesContext());
if (locale instanceof Locale)
{
return (Locale) locale;
}
else if (locale instanceof String)
{
return stringToLocale((String) locale);
}
}
return getFacesContext().getApplication().getViewHandler()
.calculateLocale(getFacesContext());
}
public void setLocale(Locale locale)
{
this._locale = locale;
}
private boolean process(FacesContext context, PhaseId phaseId,
Processor processor, boolean broadcast)
{
if (!notifyListeners(context, phaseId, getBeforePhaseListener(), true))
{
if (processor != null)
processor.process();
if (broadcast)
{
_broadcastForPhase(phaseId);
}
}
if (context.getRenderResponse() || context.getResponseComplete())
{
clearEvents();
}
return notifyListeners(context, phaseId, getAfterPhaseListener(), false);
}
/**
* Invoke view-specific phase listeners, plus an optional EL MethodExpression.
* <p>
* JSF1.2 adds the ability for PhaseListener objects to be added to a UIViewRoot instance,
* and for "beforePhaseListener" and "afterPhaseListener" EL expressions to be defined
* on the viewroot. This method is expected to be called at appropriate times, and will
* then execute the relevant listener callbacks.
* <p>
* Parameter "listener" may be null. If not null, then it is an EL expression pointing
* to a user method that will be invoked.
* <p>
* Note that the global PhaseListeners are invoked via the Lifecycle implementation, not
* from this method here.
*/
private boolean notifyListeners(FacesContext context, PhaseId phaseId,
MethodExpression listener, boolean beforePhase)
{
boolean skipPhase = false;
if (listener != null
|| (_phaseListeners != null && !_phaseListeners.isEmpty()))
{
PhaseEvent event = createEvent(context, phaseId);
if (listener != null)
{
listener.invoke(context.getELContext(), new Object[]
{ event });
skipPhase = context.getResponseComplete()
|| context.getRenderResponse();
}
if (_phaseListeners != null && !_phaseListeners.isEmpty())
{
for (PhaseListener phaseListener : _phaseListeners)
{
PhaseId listenerPhaseId = phaseListener.getPhaseId();
if (phaseId.equals(listenerPhaseId)
|| PhaseId.ANY_PHASE.equals(listenerPhaseId))
{
if (beforePhase)
{
phaseListener.beforePhase(event);
}
else
{
phaseListener.afterPhase(event);
}
skipPhase = context.getResponseComplete()
|| context.getRenderResponse();
}
}
}
}
return skipPhase;
}
private PhaseEvent createEvent(FacesContext context, PhaseId phaseId)
{
if (_lifecycle == null)
{
LifecycleFactory factory = (LifecycleFactory) FactoryFinder
.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
String id = context.getExternalContext().getInitParameter(
FacesServlet.LIFECYCLE_ID_ATTR);
if (id == null)
{
id = LifecycleFactory.DEFAULT_LIFECYCLE;
}
_lifecycle = factory.getLifecycle(id);
}
return new PhaseEvent(context, phaseId, _lifecycle);
}
private void _broadcastForPhase(PhaseId phaseId)
{
if (_events == null)
{
return;
}
boolean abort = false;
int phaseIdOrdinal = phaseId.getOrdinal();
for (ListIterator<FacesEvent> listiterator = _events.listIterator(); listiterator
.hasNext();)
{
FacesEvent event = listiterator.next();
int ordinal = event.getPhaseId().getOrdinal();
if (ordinal == ANY_PHASE_ORDINAL || ordinal == phaseIdOrdinal)
{
UIComponent source = event.getComponent();
try
{
source.broadcast(event);
}
catch (AbortProcessingException e)
{
// abort event processing
// Page 3-30 of JSF 1.1 spec: "Throw an
// AbortProcessingException, to tell the JSF implementation
// that no further broadcast of this event, or any further
// events, should take place."
abort = true;
break;
}
finally
{
try
{
listiterator.remove();
}
catch (ConcurrentModificationException cme)
{
int eventIndex = listiterator.previousIndex();
_events.remove(eventIndex);
listiterator = _events.listIterator();
}
}
}
}
if (abort)
{
// TODO: abort processing of any event of any phase or just of any
// event of the current phase???
clearEvents();
}
}
private void clearEvents()
{
_events = null;
}
private void checkNull(Object value, String valueLabel)
{
if (value == null)
{
throw new NullPointerException(valueLabel + " is null");
}
}
private Locale stringToLocale(String localeStr)
{
// locale expr: \[a-z]{2}((-|_)[A-Z]{2})?
if (localeStr.contains("_") || localeStr.contains("-"))
{
if (localeStr.length() == 2)
{
// localeStr is the lang
return new Locale(localeStr);
}
}
else
{
if (localeStr.length() == 5)
{
String lang = localeStr.substring(0, 1);
String country = localeStr.substring(3, 4);
return new Locale(lang, country);
}
}
return Locale.getDefault();
}
/**
* Defines what renderkit should be used to render this view.
*/
@JSFProperty
public String getRenderKitId()
{
if (_renderKitId != null)
{
return _renderKitId;
}
ValueExpression expression = getValueExpression("renderKitId");
if (expression != null)
{
return (String) expression.getValue(getFacesContext()
.getELContext());
}
return null;
}
public void setRenderKitId(String renderKitId)
{
this._renderKitId = renderKitId;
}
/**
* DO NOT USE.
* <p>
* This inherited property is disabled. Although this class extends a base-class that
* defines a read/write rendered property, this particular subclass does not
* support setting it. Yes, this is broken OO design: direct all complaints
* to the JSF spec group.
*/
@Override
@JSFProperty(tagExcluded=true)
public void setRendered(boolean state)
{
//Call parent method due to TCK problems
super.setRendered(state);
//throw new UnsupportedOperationException();
}
@Override
public boolean isRendered()
{
//Call parent method due to TCK problems
return super.isRendered();
}
/**
* DO NOT USE.
* <p>
* Although this class extends a base-class that defines a read/write id
* property, it makes no sense for this particular subclass to support it.
* The tag library does not export this property for use, but there is no
* way to "undeclare" a java method. Yes, this is broken OO design: direct
* all complaints to the JSF spec group.
* <p>
* This property should be disabled (ie throw an exception if invoked).
* However there are currently several places that call this method (eg
* during restoreState) so it just does the normal thing for the moment.
* TODO: fix callers then make this throw an exception.
*
* @JSFProperty tagExcluded="true"
*/
public void setId(String id)
{
// throw new UnsupportedOperationException();
// Leave enabled for now. Things like the TreeStructureManager call this,
// even though they probably should not.
super.setId(id);
}
public String getId()
{
// Should just return null. But as setId passes the method on, do same here.
return super.getId();
}
/**
* DO NOT USE.
* <p>
* As this component has no "id" property, it has no clientId property either.
*/
public String getClientId(FacesContext context)
{
return super.getClientId(context);
//Call parent method due to TCK problems
//return null;
}
/**
* A unique identifier for the "template" from which this view was generated.
* <p>
* Typically this is the filesystem path to the template file, but the exact
* details are the responsibility of the current ViewHandler implementation.
*/
@JSFProperty(tagExcluded = true)
public String getViewId()
{
return _viewId;
}
public void setViewId(String viewId)
{
// It really doesn't make much sense to allow null here.
// However the TCK does not check for it, and sun's implementation
// allows it so here we allow it too.
this._viewId = viewId;
}
/**
* Adds a The phaseListeners attached to ViewRoot.
*/
public void addPhaseListener(PhaseListener phaseListener)
{
if (phaseListener == null)
throw new NullPointerException("phaseListener");
if (_phaseListeners == null)
_phaseListeners = new ArrayList<PhaseListener>();
_phaseListeners.add(phaseListener);
}
/**
* Removes a The phaseListeners attached to ViewRoot.
*/
public void removePhaseListener(PhaseListener phaseListener)
{
if (phaseListener == null || _phaseListeners == null)
return;
_phaseListeners.remove(phaseListener);
}
/**
* MethodBinding pointing to a method that takes a
* javax.faces.event.PhaseEvent and returns void,
* called before every phase except for restore view.
*
* @return the new beforePhaseListener value
*/
@JSFProperty(stateHolder = true, returnSignature = "void", methodSignature = "javax.faces.event.PhaseEvent", jspName = "beforePhase")
public MethodExpression getBeforePhaseListener()
{
if (_beforePhaseListener != null)
{
return _beforePhaseListener;
}
ValueExpression expression = getValueExpression("beforePhaseListener");
if (expression != null)
{
return (MethodExpression) expression.getValue(getFacesContext()
.getELContext());
}
return null;
}
/**
* Sets
*
* @param beforePhaseListener
* the new beforePhaseListener value
*/
public void setBeforePhaseListener(MethodExpression beforePhaseListener)
{
this._beforePhaseListener = beforePhaseListener;
}
/**
* MethodBinding pointing to a method that takes a
* javax.faces.event.PhaseEvent and returns void,
* called after every phase except for restore view.
*
* @return the new afterPhaseListener value
*/
@JSFProperty(stateHolder = true, returnSignature = "void", methodSignature = "javax.faces.event.PhaseEvent", jspName = "afterPhase")
public MethodExpression getAfterPhaseListener()
{
if (_afterPhaseListener != null)
{
return _afterPhaseListener;
}
ValueExpression expression = getValueExpression("afterPhaseListener");
if (expression != null)
{
return (MethodExpression) expression.getValue(getFacesContext()
.getELContext());
}
return null;
}
/**
* Sets
*
* @param afterPhaseListener
* the new afterPhaseListener value
*/
public void setAfterPhaseListener(MethodExpression afterPhaseListener)
{
this._afterPhaseListener = afterPhaseListener;
}
@Override
public Object saveState(FacesContext facesContext)
{
Object[] values = new Object[8];
values[0] = super.saveState(facesContext);
values[1] = _locale;
values[2] = _renderKitId;
values[3] = _viewId;
values[4] = _uniqueIdCounter;
values[5] = saveAttachedState(facesContext, _phaseListeners);
values[6] = saveAttachedState(facesContext, _beforePhaseListener);
values[7] = saveAttachedState(facesContext, _afterPhaseListener);
return values;
}
@Override
public void restoreState(FacesContext facesContext, Object state)
{
Object[] values = (Object[]) state;
super.restoreState(facesContext, values[0]);
_locale = (Locale) values[1];
_renderKitId = (String) values[2];
_viewId = (String) values[3];
_uniqueIdCounter = (Long) values[4];
_phaseListeners = (List) restoreAttachedState(facesContext, values[5]);
_beforePhaseListener = (MethodExpression) restoreAttachedState(
facesContext, values[6]);
_afterPhaseListener = (MethodExpression) restoreAttachedState(
facesContext, values[7]);
}
@Override
public String getFamily()
{
return COMPONENT_FAMILY;
}
}