All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.omnifaces.context.OmniPartialViewContext Maven / Gradle / Ivy

/*
 * Copyright OmniFaces
 *
 * 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
 *
 *     https://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.omnifaces.context;

import static jakarta.servlet.RequestDispatcher.FORWARD_REQUEST_URI;
import static java.lang.String.format;
import static org.omnifaces.util.Faces.getContext;
import static org.omnifaces.util.Faces.responseReset;
import static org.omnifaces.util.Faces.setContextAttribute;
import static org.omnifaces.util.FacesLocal.getContextAttribute;
import static org.omnifaces.util.FacesLocal.getRequest;
import static org.omnifaces.util.FacesLocal.getRequestAttribute;
import static org.omnifaces.util.FacesLocal.getResponse;
import static org.omnifaces.util.FacesLocal.getViewId;
import static org.omnifaces.util.FacesLocal.invalidateSession;
import static org.omnifaces.util.FacesLocal.normalizeViewId;
import static org.omnifaces.util.Servlets.facesRedirect;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jakarta.faces.FacesException;
import jakarta.faces.application.ViewExpiredException;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.PartialResponseWriter;
import jakarta.faces.context.PartialViewContext;
import jakarta.faces.context.PartialViewContextWrapper;
import jakarta.faces.event.PhaseId;

import org.omnifaces.config.WebXml;
import org.omnifaces.exceptionhandler.FullAjaxExceptionHandler;
import org.omnifaces.util.Ajax;
import org.omnifaces.util.Json;

/**
 * 

* This OmniFaces partial view context extends and improves the standard partial view context as follows: *

    *
  • Support for executing callback scripts by {@link PartialResponseWriter#startEval()}.
  • *
  • Support for adding arguments to an ajax response.
  • *
  • Any XML tags which Mojarra and MyFaces has left open after an exception in rendering of an already committed * ajax response, will now be properly closed. This prevents errors about malformed XML.
  • *
  • Fixes the no-feedback problem when a {@link ViewExpiredException} occurs during an ajax request on a page which * is restricted by web.xml <security-constraint>. The enduser will now properly be * redirected to the login page instead of retrieving an ajax response with only a changed view state (and effectively * thus no visual feedback at all).
  • *
* You can use the {@link Ajax} utility class to easily add callback scripts and arguments. *

* This partial view context is already registered by OmniFaces' own faces-config.xml and thus gets * auto-initialized when the OmniFaces JAR is bundled in a web application, so end-users do not need to register this * partial view context explicitly themselves. * * @author Bauke Scholtz * @since 1.2 * @see OmniPartialViewContextFactory * @see FullAjaxExceptionHandler * @see WebXml * @see Ajax * @see Json */ public class OmniPartialViewContext extends PartialViewContextWrapper { // Constants ------------------------------------------------------------------------------------------------------ private static final String AJAX_DATA = "var OmniFaces=OmniFaces||{};OmniFaces.Ajax={data:%s};"; private static final String ERROR_NO_OMNI_PVC = "There is no current OmniPartialViewContext instance."; // Variables ------------------------------------------------------------------------------------------------------ private Map arguments; private List callbackScripts; private OmniPartialResponseWriter writer; // Constructors --------------------------------------------------------------------------------------------------- /** * Construct a new OmniFaces partial view context around the given wrapped partial view context. * @param wrapped The wrapped partial view context. */ public OmniPartialViewContext(PartialViewContext wrapped) { super(wrapped); setCurrentInstance(this); } // Actions -------------------------------------------------------------------------------------------------------- @Override public PartialResponseWriter getPartialResponseWriter() { if (writer == null) { writer = new OmniPartialResponseWriter(this, super.getPartialResponseWriter()); } return writer; } /** * An override which checks if the web.xml security constraint has been triggered during this ajax request * (which can happen when the session has been timed out) and if so, then perform a redirect to the originally * requested page. Otherwise the enduser ends up with an ajax response containing only the new view state * without any form of visual feedback. */ @Override public void processPartial(PhaseId phaseId) { if (phaseId == PhaseId.RENDER_RESPONSE && redirectToFormLoginPageIfNecessary()) { return; } super.processPartial(phaseId); } private boolean redirectToFormLoginPageIfNecessary() { String loginURL = WebXml.instance().getFormLoginPage(); if (loginURL == null) { return false; } FacesContext facesContext = FacesContext.getCurrentInstance(); String loginViewId = normalizeViewId(facesContext, loginURL); if (!loginViewId.equals(getViewId(facesContext))) { return false; } String originalURL = getRequestAttribute(facesContext, FORWARD_REQUEST_URI); if (originalURL == null) { return false; } invalidateSession(facesContext); // Prevent server from remembering security constraint fail caused by ajax. facesRedirect(getRequest(facesContext), getResponse(facesContext), originalURL); // This also clears cache. return true; } @Override // Necessary because this is missing in PartialViewContextWrapper (will be fixed in JSF 2.2). public void setPartialRequest(boolean partialRequest) { getWrapped().setPartialRequest(partialRequest); } /** * Add an argument to the partial response. This is as JSON object available by OmniFaces.Ajax.data. * For supported argument value types, read {@link Json#encode(Object)}. If a given argument type is not supported, * then an {@link IllegalArgumentException} will be thrown during end of render response. * @param name The argument name. * @param value The argument value. */ public void addArgument(String name, Object value) { if (arguments == null) { arguments = new HashMap<>(); } arguments.put(name, value); } /** * Add a callback script to the partial response. This script will be executed once the partial response is * successfully retrieved at the client side. * @param callbackScript The callback script to be added to the partial response. */ public void addCallbackScript(String callbackScript) { if (callbackScripts == null) { callbackScripts = new ArrayList<>(); } callbackScripts.add(callbackScript); } /** * Reset the partial response. This clears any JavaScript arguments and callbacks set any data written to the * {@link PartialResponseWriter}. * @see FullAjaxExceptionHandler */ public void resetPartialResponse() { if (writer != null) { writer.reset(); } arguments = null; callbackScripts = null; } /** * Close the partial response. If the writer is still in update phase, then end the update and the document. This * fixes the Mojarra problem of incomplete ajax responses caused by exceptions during ajax render response. * @see FullAjaxExceptionHandler */ public void closePartialResponse() { if (writer != null && writer.updating) { try { writer.endUpdate(); writer.endDocument(); } catch (IOException e) { throw new FacesException(e); } } } // Static --------------------------------------------------------------------------------------------------------- /** * Returns the current instance of the OmniFaces partial view context. * @return The current instance of the OmniFaces partial view context. * @throws IllegalStateException When there is no current instance of the OmniFaces partial view context. That can * happen when the {@link OmniPartialViewContextFactory} is not properly registered, or when there's another * {@link PartialViewContext} implementation which doesn't properly delegate through the wrapped instance. */ public static OmniPartialViewContext getCurrentInstance() { return getCurrentInstance(getContext()); } /** * Returns the current instance of the OmniFaces partial view context from the given faces context. * @param context The faces context to obtain the current instance of the OmniFaces partial view context from. * @return The current instance of the OmniFaces partial view context from the given faces context. * @throws IllegalStateException When there is no current instance of the OmniFaces partial view context. That can * happen when the {@link OmniPartialViewContextFactory} is not properly registered, or when there's another * {@link PartialViewContext} implementation which doesn't properly delegate through the wrapped instance. */ public static OmniPartialViewContext getCurrentInstance(FacesContext context) { OmniPartialViewContext instance = getContextAttribute(context, OmniPartialViewContext.class.getName()); if (instance != null) { return instance; } // Not found. Well, maybe the context attribute map was cleared for some reason. Get it once again. instance = unwrap(context.getPartialViewContext()); if (instance != null) { setCurrentInstance(instance); return instance; } // Still not found. Well, it's end of story. throw new IllegalStateException(ERROR_NO_OMNI_PVC); } private static void setCurrentInstance(OmniPartialViewContext instance) { setContextAttribute(OmniPartialViewContext.class.getName(), instance); } private static OmniPartialViewContext unwrap(PartialViewContext context) { PartialViewContext unwrappedContext = context; while (!(unwrappedContext instanceof OmniPartialViewContext) && unwrappedContext instanceof PartialViewContextWrapper) { unwrappedContext = ((PartialViewContextWrapper) unwrappedContext).getWrapped(); } if (unwrappedContext instanceof OmniPartialViewContext) { return (OmniPartialViewContext) unwrappedContext; } else { return null; } } // Nested classes ------------------------------------------------------------------------------------------------- /** * This OmniFaces partial response writer adds support for passing arguments to JavaScript context, executing * oncomplete callback scripts, resetting the ajax response (specifically for {@link FullAjaxExceptionHandler}) and * fixing incomlete XML response in case of exceptions. * @author Bauke Scholtz */ private static class OmniPartialResponseWriter extends PartialResponseWriter { // Variables -------------------------------------------------------------------------------------------------- private OmniPartialViewContext context; private PartialResponseWriter wrapped; private boolean updating; private boolean updated; // Constructors ----------------------------------------------------------------------------------------------- public OmniPartialResponseWriter(OmniPartialViewContext context, PartialResponseWriter wrapped) { super(wrapped); this.wrapped = wrapped; // We can't rely on getWrapped() due to MyFaces broken PartialResponseWriter. this.context = context; } // Overridden actions ----------------------------------------------------------------------------------------- /** * An override which remembers if we're updating or not. * @see #endDocument() * @see #reset() */ @Override public void startUpdate(String targetId) throws IOException { updating = updated = true; wrapped.startUpdate(targetId); } /** * An override which remembers if we're updating or not. * @see #endDocument() * @see #reset() */ @Override public void endUpdate() throws IOException { updating = false; wrapped.endUpdate(); } /** * An override which writes all {@link OmniPartialViewContext#arguments} as JSON to the extension and all * {@link OmniPartialViewContext#callbackScripts} to the eval. It also checks if we're still updating, which * may occur when MyFaces is used and an exception was thrown during rendering the partial response, and then * gently closes the partial response which MyFaces has left open. */ @Override public void endDocument() throws IOException { if (updating) { // If endDocument() method is entered with updating=true, then it means that MyFaces is used and that // an exception was been thrown during ajax render response. The following calls will gently close the // partial response which MyFaces has left open. // Mojarra never enters endDocument() method with updating=true, this is handled in reset() method. endCDATA(); endUpdate(); } else { if (context.arguments != null) { startEval(); write(format(AJAX_DATA, Json.encode(context.arguments))); endEval(); } if (context.callbackScripts != null) { for (String callbackScript : context.callbackScripts) { startEval(); write(callbackScript); endEval(); } } } wrapped.endDocument(); } // Custom actions --------------------------------------------------------------------------------------------- /** * Reset the partial response writer. It checks if we're still updating, which may occur when Mojarra is used * and an exception was thrown during rendering the partial response, and then gently closes the partial * response which Mojarra has left open. This would clear the internal state of the wrapped partial response * writer and thus make it ready for reuse without risking malformed XML. */ public void reset() { try { wrapped.flush(); // Note: this doesn't actually flush to writer, but clears internal state. if (updated) { // If reset() method is entered with updated=true, then it means that Mojarra is used and that // an exception was been thrown during ajax render response. The following calls will gently close // the partial response which Mojarra has left open. // MyFaces never enters reset() method with updated=true, this is handled in endDocument() method. updated = updating = false; wrapped.startError(""); wrapped.endError(); wrapped.endElement("partial-response"); // Don't use endDocument() as it will flush. } } catch (IOException e) { throw new FacesException(e); } finally { responseReset(); } } // Delegate actions ------------------------------------------------------------------------------------------- // Due to MyFaces broken PartialResponseWriter, which doesn't delegate to getWrapped() method, but instead to // the local variable wrapped, we can't use getWrapped() in our own PartialResponseWriter implementations. // Update 22 Dec 2017: This is still not fixed in MyFaces 2.3.0-beta :( @Override public void startDocument() throws IOException { wrapped.startDocument(); } @Override public void startError(String errorName) throws IOException { wrapped.startError(errorName); } @Override public void startEval() throws IOException { wrapped.startEval(); } @Override public void startExtension(Map attributes) throws IOException { wrapped.startExtension(attributes); } @Override public void startInsertAfter(String targetId) throws IOException { wrapped.startInsertAfter(targetId); } @Override public void startInsertBefore(String targetId) throws IOException { wrapped.startInsertBefore(targetId); } @Override public void endError() throws IOException { wrapped.endError(); } @Override public void endEval() throws IOException { wrapped.endEval(); } @Override public void endExtension() throws IOException { wrapped.endExtension(); } @Override public void endInsert() throws IOException { wrapped.endInsert(); } @Override public void delete(String targetId) throws IOException { wrapped.delete(targetId); } @Override public void redirect(String url) throws IOException { wrapped.redirect(url); } @Override public void updateAttributes(String targetId, Map attributes) throws IOException { wrapped.updateAttributes(targetId, attributes); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy