Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

MYFACES-3660 Component resource added using @ResourceDependency annot…

…ation from a facelet component should have an id defined by facelets

git-svn-id: https://svn.apache.org/repos/asf/myfaces/core/trunk@1417719 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information...
commit ed8bb3f2d91fa868a5c6e8696898e0498e938b94 1 parent 760b53e
Leonardo Uribe authored
Showing with 976 additions and 11 deletions.
  1. +44 −4 api/src/main/java/javax/faces/component/UIViewRoot.java
  2. +181 −0 api/src/main/java/javax/faces/component/_ViewAttributeMap.java
  3. +61 −0 impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java
  4. +18 −6 impl/src/main/java/org/apache/myfaces/view/facelets/DefaultFaceletsStateManagementStrategy.java
  5. +14 −0 impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/ComponentTagHandlerDelegate.java
  6. +328 −1 impl/src/test/java/org/apache/myfaces/mc/test/core/AbstractMyFacesTestCase.java
  7. +90 −0 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/AcidMyFacesRequestTestCase.java
  8. +35 −0 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/component/UIRDComponent.java
  9. +75 −0 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/managed/ComponentBindingBean.java
  10. +49 −0 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/managed/ResourceDependencyBean.java
  11. +7 −0 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/WEB-INF/testcomponent.taglib.xml
  12. +28 −0 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/componentBinding1.xhtml
  13. +31 −0 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/resourceDependency1.xhtml
  14. +15 −0 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/resources/custom.css
View
48 api/src/main/java/javax/faces/component/UIViewRoot.java
@@ -116,6 +116,9 @@
private static final String JAVAX_FACES_LOCATION_BODY = "javax_faces_location_body";
private static final String JAVAX_FACES_LOCATION_FORM = "javax_faces_location_form";
+ private transient boolean _resourceDependencyUniqueId;
+ private transient Map<String,Object> _attributesMap;
+
/**
* Construct an instance of the UIViewRoot.
*/
@@ -343,10 +346,20 @@ public String createUniqueId(FacesContext context, String seed)
// UNIQUE_ID_PREFIX, and will be unique within this UIViewRoot.
if(seed==null)
{
- Long uniqueIdCounter = (Long) getStateHelper().get(PropertyKeys.uniqueIdCounter);
- uniqueIdCounter = (uniqueIdCounter == null) ? 0 : uniqueIdCounter;
- getStateHelper().put(PropertyKeys.uniqueIdCounter, (uniqueIdCounter+1L));
- return bld.append(UNIQUE_ID_PREFIX).append(uniqueIdCounter).toString();
+ if (isResourceDependencyUniqueId())
+ {
+ Long uniqueIdCounter = (Long) getStateHelper().get(PropertyKeys.resourceDependencyUniqueIdCounter);
+ uniqueIdCounter = (uniqueIdCounter == null) ? 0 : uniqueIdCounter;
+ getStateHelper().put(PropertyKeys.resourceDependencyUniqueIdCounter, (uniqueIdCounter+1L));
+ return bld.append(UNIQUE_ID_PREFIX).append("__rd_").append(uniqueIdCounter).toString();
+ }
+ else
+ {
+ Long uniqueIdCounter = (Long) getStateHelper().get(PropertyKeys.uniqueIdCounter);
+ uniqueIdCounter = (uniqueIdCounter == null) ? 0 : uniqueIdCounter;
+ getStateHelper().put(PropertyKeys.uniqueIdCounter, (uniqueIdCounter+1L));
+ return bld.append(UNIQUE_ID_PREFIX).append(uniqueIdCounter).toString();
+ }
}
// Optionally, a unique seed value can be supplied by component creators which
// should be included in the generated unique id.
@@ -1234,6 +1247,32 @@ public void setAfterPhaseListener(MethodExpression afterPhaseListener)
getStateHelper().put(PropertyKeys.afterPhaseListener, afterPhaseListener);
}
+ @Override
+ public Map<String, Object> getAttributes()
+ {
+ if (_attributesMap == null)
+ {
+ _attributesMap = new _ViewAttributeMap(this, super.getAttributes());
+ }
+ return _attributesMap;
+ }
+
+ /**
+ * Indicates if the component is created when facelets builds the view and
+ * is caused by the presence of a ResourceDependency annotation.
+ *
+ * @return the _resourceDependencyUniqueId
+ */
+ boolean isResourceDependencyUniqueId()
+ {
+ return _resourceDependencyUniqueId;
+ }
+
+ void setResourceDependencyUniqueId(boolean resourceDependencyUniqueId)
+ {
+ this._resourceDependencyUniqueId = resourceDependencyUniqueId;
+ }
+
enum PropertyKeys
{
afterPhaseListener
@@ -1243,6 +1282,7 @@ public void setAfterPhaseListener(MethodExpression afterPhaseListener)
, renderKitId
, viewId
, uniqueIdCounter
+ , resourceDependencyUniqueIdCounter
}
@Override
View
181 api/src/main/java/javax/faces/component/_ViewAttributeMap.java
@@ -0,0 +1,181 @@
+/*
+ * 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.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ *
+ * @author Leonardo Uribe
+ */
+class _ViewAttributeMap implements Map<String, Object>
+{
+ //private static final String RESET_SAVE_STATE_MODE_KEY =
+ // "oam.view.resetSaveStateMode";
+
+ /**
+ * Key under UIViewRoot to generated unique ids for components added
+ * by @ResourceDependency effect.
+ */
+ private static final String RESOURCE_DEPENDENCY_UNIQUE_ID_KEY =
+ "oam.view.resourceDependencyUniqueId";
+ private static final String UNIQUE_ID_COUNTER_KEY =
+ "oam.view.uniqueIdCounter";
+
+ private Map<String, Object> _delegate;
+ private UIViewRoot _root;
+
+ public _ViewAttributeMap(UIViewRoot root, Map<String, Object> delegate)
+ {
+ this._delegate = delegate;
+ this._root = root;
+ }
+
+ public int size()
+ {
+ return _delegate.size();
+ }
+
+ public boolean isEmpty()
+ {
+ return _delegate.isEmpty();
+ }
+
+ public boolean containsKey(Object key)
+ {
+ return _delegate.containsKey(key);
+ }
+
+ public boolean containsValue(Object value)
+ {
+ return _delegate.containsValue(value);
+ }
+
+ public Object get(Object key)
+ {
+ checkKey(key);
+ int keyLength = ((String)key).length();
+ /*
+ if (RESET_SAVE_STATE_MODE_KEY.length() == keyLength
+ && RESET_SAVE_STATE_MODE_KEY.equals(key))
+ {
+ return _root.getResetSaveStateMode();
+ }*/
+ if (RESOURCE_DEPENDENCY_UNIQUE_ID_KEY.length() == keyLength
+ && RESOURCE_DEPENDENCY_UNIQUE_ID_KEY.equals(key))
+ {
+ return _root.isResourceDependencyUniqueId();
+ }
+ if (UNIQUE_ID_COUNTER_KEY.length() == keyLength
+ && UNIQUE_ID_COUNTER_KEY.equals(key))
+ {
+ return _root.getStateHelper().get(UIViewRoot.PropertyKeys.uniqueIdCounter);
+ }
+ return _delegate.get(key);
+ }
+
+ public Object put(String key, Object value)
+ {
+ int keyLength = ((String)key).length();
+
+ /*
+ if (RESET_SAVE_STATE_MODE_KEY.length() == keyLength
+ && RESET_SAVE_STATE_MODE_KEY.equals(key))
+ {
+ Integer b = _root.getResetSaveStateMode();
+ _root.setResetSaveStateMode(value == null ? 0 : (Integer) value);
+ return b;
+ }*/
+ if (RESOURCE_DEPENDENCY_UNIQUE_ID_KEY.length() == keyLength
+ && RESOURCE_DEPENDENCY_UNIQUE_ID_KEY.equals(key))
+ {
+ boolean b = _root.isResourceDependencyUniqueId();
+ _root.setResourceDependencyUniqueId(value == null ? false : (Boolean) value);
+ return b;
+ }
+ if (UNIQUE_ID_COUNTER_KEY.length() == keyLength
+ && UNIQUE_ID_COUNTER_KEY.equals(key))
+ {
+ Long v = (Long) _root.getStateHelper().get(UIViewRoot.PropertyKeys.uniqueIdCounter);
+ _root.getStateHelper().put(UIViewRoot.PropertyKeys.uniqueIdCounter, value);
+ return v;
+ }
+ return _delegate.put(key, value);
+ }
+
+ public Object remove(Object key)
+ {
+ return _delegate.remove(key);
+ }
+
+ public void putAll(Map<? extends String, ? extends Object> m)
+ {
+ _delegate.putAll(m);
+ }
+
+ public void clear()
+ {
+ _delegate.clear();
+ }
+
+ public Set<String> keySet()
+ {
+ return _delegate.keySet();
+ }
+
+ public Collection<Object> values()
+ {
+ return _delegate.values();
+ }
+
+ public Set<Entry<String, Object>> entrySet()
+ {
+ return _delegate.entrySet();
+ }
+
+ public boolean equals(Object o)
+ {
+ return _delegate.equals(o);
+ }
+
+ public int hashCode()
+ {
+ return _delegate.hashCode();
+ }
+
+ public String toString()
+ {
+ return _delegate.toString();
+ }
+
+ private void checkKey(Object key)
+ {
+ if (key == null)
+ {
+ throw new NullPointerException("key");
+ }
+ if (!(key instanceof String))
+ {
+ throw new ClassCastException("key is not a String");
+ }
+ }
+}
View
61 impl/src/main/java/org/apache/myfaces/application/ApplicationImpl.java
@@ -104,6 +104,7 @@
import org.apache.myfaces.lifecycle.LifecycleImpl;
import org.apache.myfaces.shared.config.MyfacesConfig;
import org.apache.myfaces.shared.util.ClassUtils;
+import org.apache.myfaces.view.facelets.FaceletCompositionContext;
import org.apache.myfaces.view.facelets.el.ELText;
/**
@@ -157,6 +158,14 @@
private static final boolean LAZY_LOAD_CONFIG_OBJECTS_DEFAULT_VALUE = true;
private Boolean _lazyLoadConfigObjects = null;
+
+ /**
+ * Key under UIViewRoot to generated unique ids for components added
+ * by @ResourceDependency effect.
+ */
+ private static final String RESOURCE_DEPENDENCY_UNIQUE_ID_KEY =
+ "oam.view.resourceDependencyUniqueId";
+
// ~ Instance fields
// --------------------------------------------------------------------------
// --
@@ -1798,6 +1807,50 @@ else if (dependencyList.isEmpty())
rvc.setClassProcessed(inspectedClass);
}
}
+
+ /**
+ * If the ResourceDependency component is created under facelets processing, it should receive
+ * an special unique component id. This method check if there is a FaceletCompositionContext
+ * and if that so, set the id. Components added by the effect of ResourceDependency are special,
+ * because they do not have state, but they depends on the view structure, so with PSS,
+ * each time the view is built they are "recalculated", so they work as if they were transient
+ * components that needs to be created at each request, but there are some cases were the
+ * components needs to be saved and restored fully. If a component is created outside facelets
+ * control (render response phase) it is expected to use the default implementation of
+ * createUniqueId(), but in that case, note that this happens after markInitialState() is
+ * called, and the component in this case is saved and restored fully, as expected.
+ *
+ * This code cannot be called from facelets component tag handler, because in cases where a
+ * component subtree is created using binding property, facelets lost control over component
+ * creation and delegates it to the user, but since the binding code is executed each time the
+ * view is created, the effect over ResourceDependency persists and the binding code takes into
+ * account in the recalculation step, even if later the node related to the binding property
+ * is dropped and recreated from the state fully.
+ *
+ * @param facesContext
+ * @param component
+ */
+ private void setResourceIdOnFaceletsMode(FacesContext facesContext, UIComponent component)
+ {
+ if (component.getId() == null)
+ {
+ FaceletCompositionContext mctx = FaceletCompositionContext.getCurrentInstance(facesContext);
+ if (mctx != null)
+ {
+ UIViewRoot root = facesContext.getViewRoot();
+ root.getAttributes().put(RESOURCE_DEPENDENCY_UNIQUE_ID_KEY, Boolean.TRUE);
+ try
+ {
+ String uid = root.createUniqueId(facesContext, null);
+ component.setId(uid);
+ }
+ finally
+ {
+ root.getAttributes().put(RESOURCE_DEPENDENCY_UNIQUE_ID_KEY, Boolean.FALSE);
+ }
+ }
+ }
+ }
private void _handleAttachedResourceDependency(FacesContext context, ResourceDependency annotation)
{
@@ -1826,6 +1879,10 @@ private void _handleAttachedResourceDependency(FacesContext context, ResourceDep
// Call setRendererType on the UIOutput instance, passing the renderer-type.
output.setRendererType(rendererType);
+ // If the @ResourceDependency was done inside facelets processing,
+ // call setId() and set a proper id from facelets
+ setResourceIdOnFaceletsMode(context, output);
+
// Obtain the Map of attributes from the UIOutput component by calling UIComponent.getAttributes().
Map<String, Object> attributes = output.getAttributes();
@@ -2301,6 +2358,10 @@ private void _handleResourceDependency(FacesContext context, UIComponent compone
// Call setRendererType on the UIOutput instance, passing the renderer-type.
output.setRendererType(rendererType);
+
+ // If the @ResourceDependency was done inside facelets processing,
+ // call setId() and set a proper id from facelets
+ setResourceIdOnFaceletsMode(context, output);
// Obtain the Map of attributes from the UIOutput component by calling UIComponent.getAttributes().
Map<String, Object> attributes = output.getAttributes();
View
24 impl/src/main/java/org/apache/myfaces/view/facelets/DefaultFaceletsStateManagementStrategy.java
@@ -171,6 +171,9 @@
private static final Set<VisitHint> VISIT_HINTS = Collections.unmodifiableSet(
EnumSet.of(VisitHint.SKIP_ITERATION));
+ private static final String UNIQUE_ID_COUNTER_KEY =
+ "oam.view.uniqueIdCounter";
+
private ViewDeclarationLanguageFactory _vdlFactory;
private RenderKitFactory _renderKitFactory = null;
@@ -259,8 +262,16 @@ public UIViewRoot restoreView (FacesContext context, String viewId, String rende
{
view.getAttributes().put(ComponentSupport.FACELET_STATE_INSTANCE, faceletViewState);
}
+ if (state.length == 3)
+ {
+ if (view.getId() == null)
+ {
+ view.setId(view.createUniqueId(context, null));
+ }
+ //Jump to where the count is
+ view.getAttributes().put(UNIQUE_ID_COUNTER_KEY, state[2]);
+ }
}
-
// TODO: Why is necessary enable event processing?
// ANS: On RestoreViewExecutor, setProcessingEvents is called first to false
// and then to true when postback. Since we need listeners registered to PostAddToViewEvent
@@ -291,7 +302,6 @@ public UIViewRoot restoreView (FacesContext context, String viewId, String rende
if (state != null && state[1] != null)
{
states = (Map<String, Object>) state[1];
-
// Visit the children and restore their state.
boolean emptyState = false;
boolean containsFaceletState = states.containsKey(
@@ -391,7 +401,6 @@ public void invokeContextCallback(FacesContext context,
context.getAttributes().remove(FaceletViewDeclarationLanguage.REMOVING_COMPONENTS_BUILD);
}
}
-
List<String> clientIdsAdded = getClientIdsAdded(view);
if (clientIdsAdded != null)
{
@@ -464,7 +473,6 @@ public void invokeContextCallback(FacesContext context,
}
}
}
-
// Restore binding, because UIViewRoot.processRestoreState() is never called
//the event processing has to be enabled because of the restore view event triggers
//TODO ask the EG the this is a spec violation if we do it that way
@@ -578,8 +586,12 @@ else if (CHECK_ID_PRODUCTION_MODE_TRUE.equals(getCheckIdProductionMode(context))
// As required by ResponseStateManager, the return value is an Object array. First
// element is the structure object, second is the state map.
-
- if (states == null)
+ Long uniqueIdCount = (Long) view.getAttributes().get(UNIQUE_ID_COUNTER_KEY);
+ if (uniqueIdCount != null && !uniqueIdCount.equals(1L))
+ {
+ serializedView = new Object[] { null, states, uniqueIdCount };
+ }
+ else if (states == null)
{
serializedView = EMPTY_STATES;
}
View
14 impl/src/main/java/org/apache/myfaces/view/facelets/tag/jsf/ComponentTagHandlerDelegate.java
@@ -50,6 +50,8 @@
import org.apache.myfaces.util.ExternalSpecifications;
import org.apache.myfaces.view.facelets.AbstractFaceletContext;
+import org.apache.myfaces.view.facelets.ComponentState;
+import org.apache.myfaces.view.facelets.DefaultFaceletsStateManagementStrategy;
import org.apache.myfaces.view.facelets.FaceletCompositionContext;
import org.apache.myfaces.view.facelets.tag.MetaRulesetImpl;
import org.apache.myfaces.view.facelets.tag.jsf.core.AjaxHandler;
@@ -435,6 +437,18 @@ protected UIComponent createComponent(FaceletContext ctx)
ComponentSupport.getViewRoot(ctx, c).getAttributes().put("oam.CALL_PRE_DISPOSE_VIEW", Boolean.TRUE);
c.subscribeToEvent(PreDisposeViewEvent.class, new ClearBindingValueExpressionListener());
}
+
+ if (c.getChildCount() > 0 || c.getFacetCount() > 0)
+ {
+ // In this case, this component is used to hold a subtree that is generated
+ // dynamically. In this case, the best is mark this component to be restored
+ // fully, because this ensures the state is correctly preserved. Note this
+ // is only necessary when the component has additional children or facets,
+ // because those components requires an unique id provided by createUniqueId(),
+ // and this ensures stability of the generated ids.
+ c.getAttributes().put(DefaultFaceletsStateManagementStrategy.COMPONENT_ADDED_AFTER_BUILD_VIEW,
+ ComponentState.REMOVE_ADD);
+ }
}
}
else
View
329 impl/src/test/java/org/apache/myfaces/mc/test/core/AbstractMyFacesTestCase.java
@@ -34,13 +34,23 @@
import javax.el.ExpressionFactory;
import javax.faces.FacesException;
import javax.faces.FactoryFinder;
+import javax.faces.application.Application;
+import javax.faces.application.FacesMessage;
+import javax.faces.application.ProjectStage;
+import javax.faces.application.ViewHandler;
+import javax.faces.component.UIViewRoot;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.context.FacesContextFactory;
+import javax.faces.context.Flash;
+import javax.faces.event.ExceptionQueuedEvent;
+import javax.faces.event.ExceptionQueuedEventContext;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;
+import javax.faces.event.PreRenderViewEvent;
import javax.faces.lifecycle.Lifecycle;
import javax.faces.lifecycle.LifecycleFactory;
+import javax.faces.view.ViewDeclarationLanguage;
import javax.faces.webapp.FacesServlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
@@ -52,6 +62,7 @@
import org.apache.myfaces.config.element.FacesConfig;
import org.apache.myfaces.config.impl.digester.elements.Factory;
import org.apache.myfaces.lifecycle.LifecycleImpl;
+import org.apache.myfaces.lifecycle.ViewNotFoundException;
import org.apache.myfaces.mc.test.core.annotation.DeclareFacesConfig;
import org.apache.myfaces.mc.test.core.annotation.ManagedBeans;
import org.apache.myfaces.mc.test.core.annotation.PageBean;
@@ -62,6 +73,7 @@
import org.apache.myfaces.test.mock.MockPrintWriter;
import org.apache.myfaces.test.mock.MockServletConfig;
import org.apache.myfaces.test.mock.MockServletContext;
+import org.apache.myfaces.util.DebugUtils;
import org.apache.myfaces.view.facelets.FaceletViewDeclarationLanguage;
import org.apache.myfaces.webapp.AbstractFacesInitializer;
import org.apache.myfaces.webapp.StartupServletContextListener;
@@ -99,6 +111,13 @@
public static final String LAST_PHASE_PROCESSED = "oam.LAST_PHASE_PROCESSED";
+ public static final String LAST_RENDER_PHASE_STEP = "oam.LAST_RENDER_PHASE_STEP";
+
+ public static final int BEFORE_RENDER_STEP = 1;
+ public static final int BUILD_VIEW_CYCLE_STEP = 2;
+ public static final int VIEWHANDLER_RENDER_STEP = 3;
+ public static final int AFTER_RENDER_STEP = 4;
+
// ------------------------------------------------------------ Constructors
/**
@@ -446,6 +465,7 @@ protected void processRender(FacesContext facesContext) throws Exception
processRemainingExecutePhases(facesContext);
lifecycle.render(facesContext);
facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.RENDER_RESPONSE);
+ facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, AFTER_RENDER_STEP);
}
protected void processRemainingExecutePhases(FacesContext facesContext) throws Exception
@@ -516,7 +536,29 @@ protected void processRemainingPhases(FacesContext facesContext) throws Exceptio
}
if (continueProcess || PhaseId.INVOKE_APPLICATION.equals(lastPhaseId))
{
- processRender(facesContext);
+ Integer step = (Integer) facesContext.getAttributes().get(LAST_RENDER_PHASE_STEP);
+ if (step == null)
+ {
+ processRender(facesContext);
+ }
+ else
+ {
+ if (BEFORE_RENDER_STEP == step.intValue())
+ {
+ executeBuildViewCycle(facesContext);
+ executeViewHandlerRender(facesContext);
+ executeAfterRender(facesContext);
+ }
+ else if (BUILD_VIEW_CYCLE_STEP == step.intValue())
+ {
+ executeViewHandlerRender(facesContext);
+ executeAfterRender(facesContext);
+ }
+ else if (VIEWHANDLER_RENDER_STEP == step.intValue())
+ {
+ executeAfterRender(facesContext);
+ }
+ }
}
}
}
@@ -532,6 +574,291 @@ protected boolean isScanAnnotations()
return false;
}
+ protected void executeBeforeRender(FacesContext facesContext) throws Exception
+ {
+ if (lifecycle instanceof LifecycleImpl)
+ {
+ LifecycleImpl lifecycleImpl = (LifecycleImpl) lifecycle;
+
+ Object phaseExecutor = null;
+ Field renderExecutorField = lifecycleImpl.getClass().getDeclaredField("renderExecutor");
+ if (!renderExecutorField.isAccessible())
+ {
+ renderExecutorField.setAccessible(true);
+ }
+ phaseExecutor = renderExecutorField.get(lifecycleImpl);
+
+ if (facesContext.getResponseComplete())
+ {
+ return;
+ }
+
+ Object phaseManager = facesContext.getAttributes().get(PHASE_MANAGER_INSTANCE);
+ if (phaseManager == null)
+ {
+ Method getPhaseListenersMethod = lifecycleImpl.getClass().getDeclaredMethod("getPhaseListeners");
+ if (!getPhaseListenersMethod.isAccessible())
+ {
+ getPhaseListenersMethod.setAccessible(true);
+ }
+
+ Constructor<?> plmc = PHASE_MANAGER_CLASS.getDeclaredConstructor(new Class[]{Lifecycle.class, FacesContext.class, PhaseListener[].class});
+ if (!plmc.isAccessible())
+ {
+ plmc.setAccessible(true);
+ }
+ phaseManager = plmc.newInstance(lifecycle, facesContext, getPhaseListenersMethod.invoke(lifecycleImpl, null));
+ facesContext.getAttributes().put(PHASE_MANAGER_INSTANCE, phaseManager);
+ }
+
+ Flash flash = facesContext.getExternalContext().getFlash();
+
+ try
+ {
+ facesContext.setCurrentPhaseId(PhaseId.RENDER_RESPONSE);
+
+ flash.doPrePhaseActions(facesContext);
+
+ // let the PhaseExecutor do some pre-phase actions
+
+ //renderExecutor.doPrePhaseActions(facesContext);
+ Method doPrePhaseActionsMethod = phaseExecutor.getClass().getMethod("doPrePhaseActions", FacesContext.class);
+ if(!(doPrePhaseActionsMethod.isAccessible()))
+ {
+ doPrePhaseActionsMethod.setAccessible(true);
+ }
+ doPrePhaseActionsMethod.invoke(phaseExecutor, facesContext);
+
+ //phaseListenerMgr.informPhaseListenersBefore(PhaseId.RENDER_RESPONSE);
+ Method informPhaseListenersBeforeMethod = phaseManager.getClass().getDeclaredMethod("informPhaseListenersBefore", PhaseId.class);
+ if(!(informPhaseListenersBeforeMethod.isAccessible()))
+ {
+ informPhaseListenersBeforeMethod.setAccessible(true);
+ }
+ informPhaseListenersBeforeMethod.invoke(phaseManager, PhaseId.RENDER_RESPONSE);
+
+ // also possible that one of the listeners completed the response
+ if (facesContext.getResponseComplete())
+ {
+ return;
+ }
+
+ //renderExecutor.execute(facesContext);
+ }
+
+ catch (Throwable e)
+ {
+ // JSF 2.0: publish the executor's exception (if any).
+ ExceptionQueuedEventContext context = new ExceptionQueuedEventContext (facesContext, e, null, PhaseId.RENDER_RESPONSE);
+ facesContext.getApplication().publishEvent (facesContext, ExceptionQueuedEvent.class, context);
+ }
+
+ finally
+ {
+ /*
+ phaseListenerMgr.informPhaseListenersAfter(renderExecutor.getPhase());
+ flash.doPostPhaseActions(facesContext);
+
+ // publish a field in the application map to indicate
+ // that the first request has been processed
+ requestProcessed(facesContext);
+ */
+ }
+
+ facesContext.getExceptionHandler().handle();
+
+
+ facesContext.getAttributes().remove(PHASE_MANAGER_INSTANCE);
+
+ facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, BEFORE_RENDER_STEP);
+ }
+ else
+ {
+ throw new UnsupportedOperationException("Cannot execute phase on custom lifecycle instances");
+ }
+ }
+
+ public void executeBuildViewCycle(FacesContext facesContext) throws Exception
+ {
+ Application application = facesContext.getApplication();
+ ViewHandler viewHandler = application.getViewHandler();
+ UIViewRoot root;
+ UIViewRoot previousRoot;
+ String viewId;
+ String newViewId;
+ boolean isNotSameRoot;
+ int loops = 0;
+ int maxLoops = 15;
+
+ if (facesContext.getViewRoot() == null)
+ {
+ throw new ViewNotFoundException("A view is required to execute "+facesContext.getCurrentPhaseId());
+ }
+
+ try
+ {
+ // do-while, because the view might change in PreRenderViewEvent-listeners
+ do
+ {
+ root = facesContext.getViewRoot();
+ previousRoot = root;
+ viewId = root.getViewId();
+
+ ViewDeclarationLanguage vdl = viewHandler.getViewDeclarationLanguage(
+ facesContext, viewId);
+ if (vdl != null)
+ {
+ vdl.buildView(facesContext, root);
+ }
+
+ // publish a PreRenderViewEvent: note that the event listeners
+ // of this event can change the view, so we have to perform the algorithm
+ // until the viewId does not change when publishing this event.
+ application.publishEvent(facesContext, PreRenderViewEvent.class, root);
+
+ // was the response marked as complete by an event listener?
+ if (facesContext.getResponseComplete())
+ {
+ return;
+ }
+
+ root = facesContext.getViewRoot();
+
+ newViewId = root.getViewId();
+
+ isNotSameRoot = !( (newViewId == null ? newViewId == viewId : newViewId.equals(viewId) ) &&
+ previousRoot.equals(root) );
+
+ loops++;
+ }
+ while ((newViewId == null && viewId != null)
+ || (newViewId != null && (!newViewId.equals(viewId) || isNotSameRoot ) ) && loops < maxLoops);
+
+ if (loops == maxLoops)
+ {
+ // PreRenderView reach maxLoops - probably a infinitive recursion:
+ boolean production = facesContext.isProjectStage(ProjectStage.Production);
+ /*
+ Level level = production ? Level.FINE : Level.WARNING;
+ if (log.isLoggable(level))
+ {
+ log.log(level, "Cicle over buildView-PreRenderViewEvent on RENDER_RESPONSE phase "
+ + "reaches maximal limit, please check listeners for infinite recursion.");
+ }*/
+ }
+
+ facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, BUILD_VIEW_CYCLE_STEP);
+ }
+ catch (IOException e)
+ {
+ throw new FacesException(e.getMessage(), e);
+ }
+ }
+
+ public void executeViewHandlerRender(FacesContext facesContext)
+ {
+ Application application = facesContext.getApplication();
+ ViewHandler viewHandler = application.getViewHandler();
+
+ try
+ {
+ viewHandler.renderView(facesContext, facesContext.getViewRoot());
+
+ // log all unhandled FacesMessages, don't swallow them
+ // perf: org.apache.myfaces.context.servlet.FacesContextImpl.getMessageList() creates
+ // new Collections.unmodifiableList with every invocation-> call it only once
+ // and messageList is RandomAccess -> use index based loop
+ List<FacesMessage> messageList = facesContext.getMessageList();
+ if (!messageList.isEmpty())
+ {
+ StringBuilder builder = new StringBuilder();
+ //boolean shouldLog = false;
+ for (int i = 0, size = messageList.size(); i < size; i++)
+ {
+ FacesMessage message = messageList.get(i);
+ if (!message.isRendered())
+ {
+ builder.append("\n- ");
+ builder.append(message.getDetail());
+
+ //shouldLog = true;
+ }
+ }
+ /*
+ if (shouldLog)
+ {
+ log.log(Level.WARNING, "There are some unhandled FacesMessages, " +
+ "this means not every FacesMessage had a chance to be rendered.\n" +
+ "These unhandled FacesMessages are: " + builder.toString());
+ }*/
+ }
+ facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, VIEWHANDLER_RENDER_STEP);
+ }
+ catch (IOException e)
+ {
+ throw new FacesException(e.getMessage(), e);
+ }
+ }
+
+ public void executeAfterRender(FacesContext facesContext) throws Exception
+ {
+ if (lifecycle instanceof LifecycleImpl)
+ {
+ LifecycleImpl lifecycleImpl = (LifecycleImpl) lifecycle;
+
+ Object phaseExecutor = null;
+ Field renderExecutorField = lifecycleImpl.getClass().getDeclaredField("renderExecutor");
+ if (!renderExecutorField.isAccessible())
+ {
+ renderExecutorField.setAccessible(true);
+ }
+ phaseExecutor = renderExecutorField.get(lifecycleImpl);
+
+ Object phaseManager = facesContext.getAttributes().get(PHASE_MANAGER_INSTANCE);
+ if (phaseManager == null)
+ {
+ Method getPhaseListenersMethod = lifecycleImpl.getClass().getDeclaredMethod("getPhaseListeners");
+ if (!getPhaseListenersMethod.isAccessible())
+ {
+ getPhaseListenersMethod.setAccessible(true);
+ }
+
+ Constructor<?> plmc = PHASE_MANAGER_CLASS.getDeclaredConstructor(new Class[]{Lifecycle.class, FacesContext.class, PhaseListener[].class});
+ if (!plmc.isAccessible())
+ {
+ plmc.setAccessible(true);
+ }
+ phaseManager = plmc.newInstance(lifecycle, facesContext, getPhaseListenersMethod.invoke(lifecycleImpl, null));
+ facesContext.getAttributes().put(PHASE_MANAGER_INSTANCE, phaseManager);
+ }
+
+
+ Flash flash = facesContext.getExternalContext().getFlash();
+
+ //phaseListenerMgr.informPhaseListenersAfter(renderExecutor.getPhase());
+ Method informPhaseListenersAfterMethod = phaseManager.getClass().getDeclaredMethod("informPhaseListenersAfter", PhaseId.class);
+ if(!(informPhaseListenersAfterMethod.isAccessible()))
+ {
+ informPhaseListenersAfterMethod.setAccessible(true);
+ }
+ informPhaseListenersAfterMethod.invoke(phaseManager, PhaseId.RENDER_RESPONSE);
+
+ flash.doPostPhaseActions(facesContext);
+
+ facesContext.getExceptionHandler().handle();
+
+ facesContext.getAttributes().remove(PHASE_MANAGER_INSTANCE);
+
+ facesContext.getAttributes().put(LAST_RENDER_PHASE_STEP, AFTER_RENDER_STEP);
+ //End render response phase
+ facesContext.getAttributes().put(LAST_PHASE_PROCESSED, PhaseId.RENDER_RESPONSE);
+ }
+ else
+ {
+ throw new UnsupportedOperationException("Cannot execute phase on custom lifecycle instances");
+ }
+ }
+
/**
* Execute an specified phase, doing some reflection over LifecycleImpl.
*
View
90 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/AcidMyFacesRequestTestCase.java
@@ -22,12 +22,14 @@
import javax.faces.component.UICommand;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
+import javax.faces.component.UIPanel;
import javax.faces.component.html.HtmlDataTable;
import junit.framework.Assert;
import org.apache.myfaces.mc.test.core.AbstractMyFacesRequestTestCase;
import org.apache.myfaces.shared.config.MyfacesConfig;
+import org.apache.myfaces.view.facelets.pss.acid.managed.ResourceDependencyBean;
import org.junit.Test;
public class AcidMyFacesRequestTestCase extends AbstractMyFacesRequestTestCase
@@ -418,4 +420,92 @@ public void testInclude2() throws Exception
//Check it is restored
Assert.assertNotNull(component);
}
+
+ /**
+ * Check if a dynamic subtree can be created from a binding property, and if it
+ * will be preserved across request.
+ *
+ * The idea is just inject a subtree using some code like this:
+ * <code>&lt;h:panelGroup id="panel" binding="#{componentBindingBean.panel}"&gt;</code>
+ *
+ * The solution is if a binding returns a component that has children or facets
+ * attached, it is not elegible for PSS algorithm because the additional components
+ * are created outside facelets control, and there is no warrant that the same structure
+ * will be generated across requests, violating PSS base principle (it is possible to
+ * restore to the initial state calling vdl.buildView).
+ *
+ * This test is here because all state saving modes should support this method.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testComponentBinding() throws Exception
+ {
+ setupRequest("/componentBinding1.xhtml");
+ processLifecycleExecuteAndRender();
+
+ UIComponent comp = facesContext.getViewRoot().findComponent("panel");
+ Assert.assertNotNull(comp);
+ Assert.assertEquals(1, comp.getChildCount());
+
+ UICommand button = (UICommand) facesContext.getViewRoot().findComponent("mainForm:postback");
+ submit(button);
+ processLifecycleExecuteAndRender();
+
+ comp = facesContext.getViewRoot().findComponent("panel");
+
+ Assert.assertEquals("value1", comp.getAttributes().get("attr1"));
+ Assert.assertEquals("value2", comp.getChildren().get(0).getAttributes().get("attr2"));
+
+ button = (UICommand) facesContext.getViewRoot().findComponent("mainForm:postback");
+ submit(button);
+ processLifecycleExecuteAndRender();
+
+ comp = facesContext.getViewRoot().findComponent("panel");
+
+ Assert.assertEquals("value1", comp.getAttributes().get("attr1"));
+ Assert.assertEquals("value2", comp.getChildren().get(0).getAttributes().get("attr2"));
+
+ tearDownRequest();
+ }
+
+ @Test
+ public void testResourceDependency() throws Exception
+ {
+ setupRequest("/resourceDependency1.xhtml");
+ processLifecycleExecute();
+
+ executeBeforeRender(facesContext);
+ executeBuildViewCycle(facesContext);
+
+ UIPanel headPanel = (UIPanel) facesContext.getViewRoot().getFacet("head");
+ Assert.assertNotNull(headPanel);
+ Assert.assertEquals(1, headPanel.getChildCount());
+
+ String nextUniqueId = facesContext.getViewRoot().createUniqueId(facesContext, null);
+
+ executeViewHandlerRender(facesContext);
+ executeAfterRender(facesContext);
+
+ UICommand button = (UICommand) facesContext.getViewRoot().findComponent("mainForm:postback");
+ submit(button);
+
+ processLifecycleExecute();
+
+ ResourceDependencyBean bean = facesContext.getApplication().evaluateExpressionGet(
+ facesContext, "#{resourceDependencyBean}", ResourceDependencyBean.class);
+ bean.setIncludeContent(true);
+
+ executeBeforeRender(facesContext);
+ executeBuildViewCycle(facesContext);
+
+ headPanel = (UIPanel) facesContext.getViewRoot().getFacet("head");
+ Assert.assertNotNull(headPanel);
+ Assert.assertEquals(1, headPanel.getChildCount());
+ Assert.assertNotSame(nextUniqueId, headPanel.getChildren().get(0).getId());
+
+ executeViewHandlerRender(facesContext);
+ executeAfterRender(facesContext);
+ }
+
}
View
35 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/component/UIRDComponent.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2012 The Apache Software Foundation.
+ *
+ * Licensed 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.myfaces.view.facelets.pss.acid.component;
+
+import javax.faces.application.ResourceDependency;
+import javax.faces.component.FacesComponent;
+import javax.faces.component.UIOutput;
+
+/**
+ *
+ * @author lu4242
+ */
+@FacesComponent(value = "com.myapp.UIRDComponent")
+@ResourceDependency(name = "custom.css", target="head")
+public class UIRDComponent extends UIOutput
+{
+ public UIRDComponent()
+ {
+ super();
+ }
+
+}
View
75 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/managed/ComponentBindingBean.java
@@ -0,0 +1,75 @@
+/*
+ * 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.myfaces.view.facelets.pss.acid.managed;
+
+import javax.faces.bean.ManagedBean;
+import javax.faces.bean.RequestScoped;
+import javax.faces.component.UIOutput;
+import javax.faces.component.UIPanel;
+import javax.faces.component.html.HtmlPanelGroup;
+import javax.faces.context.FacesContext;
+
+/**
+ *
+ * @author Leonardo Uribe
+ */
+@ManagedBean(name="componentBindingBean")
+@RequestScoped
+public class ComponentBindingBean
+{
+ private UIPanel panel;
+
+ public UIPanel getPanel()
+ {
+ if (panel == null)
+ {
+ panel = new HtmlPanelGroup();
+ if (FacesContext.getCurrentInstance().isPostback())
+ {
+ // Just try to mess the binding. In theory this does
+ // not have effect, because the binding with children
+ // or facets should be restored fully.
+ UIOutput out2 = new UIOutput();
+ out2.setValue("hello2");
+ panel.getChildren().add(out2);
+ }
+ UIOutput out = new UIOutput();
+ out.setValue("hello1");
+ panel.getChildren().add(out);
+ if (!FacesContext.getCurrentInstance().isPostback())
+ {
+ // Store something into the state
+ panel.getAttributes().put("attr1", "value1");
+ panel.getChildren().get(0).getAttributes().put("attr2", "value2");
+ }
+ else
+ {
+ //Try to mess the state, in theory it should not have effect
+ panel.getAttributes().remove("attr1");
+ panel.getChildren().get(0).getAttributes().remove("attr2");
+ }
+ }
+ return panel;
+ }
+
+ public void setPanel(UIPanel panel)
+ {
+ this.panel = panel;
+ }
+}
View
49 impl/src/test/java/org/apache/myfaces/view/facelets/pss/acid/managed/ResourceDependencyBean.java
@@ -0,0 +1,49 @@
+/*
+ * 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.myfaces.view.facelets.pss.acid.managed;
+
+import javax.faces.bean.ManagedBean;
+import javax.faces.bean.SessionScoped;
+
+/**
+ *
+ * @author Leonardo Uribe
+ */
+@ManagedBean(name="resourceDependencyBean")
+@SessionScoped
+public class ResourceDependencyBean
+{
+ private boolean includeContent;
+
+ /**
+ * @return the includeContent
+ */
+ public boolean isIncludeContent()
+ {
+ return includeContent;
+ }
+
+ /**
+ * @param includeContent the includeContent to set
+ */
+ public void setIncludeContent(boolean includeContent)
+ {
+ this.includeContent = includeContent;
+ }
+}
View
7 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/WEB-INF/testcomponent.taglib.xml
@@ -64,5 +64,12 @@
<component-type>com.myapp.UISelfRenderComponent</component-type>
</component>
</tag>
+
+ <tag>
+ <tag-name>rdcomponent</tag-name>
+ <component>
+ <component-type>com.myapp.UIRDComponent</component-type>
+ </component>
+ </tag>
</facelet-taglib>
View
28 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/componentBinding1.xhtml
@@ -0,0 +1,28 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+<!--
+ Licensed 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:f="http://java.sun.com/jsf/core"
+ xmlns:ui="http://java.sun.com/jsf/facelets">
+<h:head>
+</h:head>
+<h:body>
+ <h:panelGroup id="panel" binding="#{componentBindingBean.panel}"/>
+ <h:form id="mainForm">
+ <h:commandButton id="postback" value="POSTBACK"/>
+ </h:form>
+</h:body>
+</html>
+
View
31 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/resourceDependency1.xhtml
@@ -0,0 +1,31 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+<!--
+ Licensed 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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:f="http://java.sun.com/jsf/core"
+ xmlns:ui="http://java.sun.com/jsf/facelets"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:test="http://testcomponent">
+<h:head>
+</h:head>
+<h:body>
+
+ <test:rdcomponent value="test1"/>
+ <h:form id="mainForm">
+ <h:commandButton id="postback" value="POSTBACK"/>
+ </h:form>
+</h:body>
+</html>
+
View
15 impl/src/test/resources/org/apache/myfaces/view/facelets/pss/acid/resources/custom.css
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2012 The Apache Software Foundation.
+ *
+ * Licensed 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.
+ */
Please sign in to comment.
Something went wrong with that request. Please try again.