View Javadoc
1   /*
2    * Copyright (c) 2011-2024 PrimeFaces Extensions
3    *
4    *  Permission is hereby granted, free of charge, to any person obtaining a copy
5    *  of this software and associated documentation files (the "Software"), to deal
6    *  in the Software without restriction, including without limitation the rights
7    *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8    *  copies of the Software, and to permit persons to whom the Software is
9    *  furnished to do so, subject to the following conditions:
10   *
11   *  The above copyright notice and this permission notice shall be included in
12   *  all copies or substantial portions of the Software.
13   *
14   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15   *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16   *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17   *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18   *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19   *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20   *  THE SOFTWARE.
21   */
22  package org.primefaces.extensions.application;
23  
24  import java.util.Arrays;
25  import java.util.List;
26  
27  import javax.faces.application.ProjectStage;
28  import javax.faces.component.UIOutput;
29  import javax.faces.component.UIViewRoot;
30  import javax.faces.context.FacesContext;
31  import javax.faces.event.AbortProcessingException;
32  import javax.faces.event.SystemEvent;
33  import javax.faces.event.SystemEventListener;
34  import javax.faces.lifecycle.ClientWindow;
35  import javax.servlet.http.Cookie;
36  import javax.servlet.http.HttpServletResponse;
37  
38  import org.primefaces.clientwindow.PrimeClientWindow;
39  import org.primefaces.clientwindow.PrimeClientWindowUtils;
40  import org.primefaces.config.PrimeConfiguration;
41  import org.primefaces.config.PrimeEnvironment;
42  import org.primefaces.context.PrimeApplicationContext;
43  import org.primefaces.context.PrimeRequestContext;
44  import org.primefaces.util.LocaleUtils;
45  
46  /**
47   * Creates a custom SystemEventListener for PostAddToViewEvent on UIViewRoot. This will run after all those @ResourceDependency annotations of PrimeFaces
48   * components have been processed. This is thus an ideal moment to add the PrimeFaces.settings script as a component resource, as intended by PrimeFaces.
49   * <p>
50   * Register it as below in faces-config.xml:
51   * </p>
52   * 
53   * <pre>
54   *     &lt;application&gt;
55   *        &lt;system-event-listener&gt;
56   *            &lt;system-event-listener-class&gt;com.example.PrimeFacesScriptProcessor&lt;/system-event-listener-class&gt;
57   *            &lt;system-event-class&gt;javax.faces.event.PostAddToViewEvent&lt;/system-event-class&gt;
58   *            &lt;source-class&gt;javax.faces.component.UIViewRoot&lt;/source-class&gt;
59   *        &lt;/system-event-listener&gt;
60   *     &lt;/application&gt;
61   * </pre>
62   *
63   * @see <a href="https://github.com/omnifaces/omnifaces/wiki/Combine-hardcoded-PrimeFaces-resources-using-CombinedResourceHandler">OmniFaces</a>
64   * @since 10.0.0
65   */
66  public class PrimeFacesScriptProcessor implements SystemEventListener {
67  
68      @Override
69      public boolean isListenerForSource(final Object source) {
70          return source instanceof UIViewRoot;
71      }
72  
73      @Override
74      public void processEvent(final SystemEvent event) throws AbortProcessingException {
75          // Get the current stack trace
76          // https://github.com/primefaces-extensions/primefaces-extensions/issues/517
77          boolean shouldDiscard = Arrays.stream(Thread.currentThread().getStackTrace())
78                      .anyMatch(element -> "org.apache.myfaces.lifecycle.RestoreViewExecutor".equals(element.getClassName()));
79          if (shouldDiscard) {
80              // Discard this as it is also processed in RenderResponseExecutor
81              return; // Exit
82          }
83  
84          final FacesContext context = event.getFacesContext();
85          final StringBuilder script = new StringBuilder(4000);
86  
87          encodeSettingScripts(context, script);
88          encodeInitScripts(context, script);
89  
90          addJS(context, script.toString());
91      }
92  
93      protected void encodeSettingScripts(final FacesContext context, final StringBuilder writer) {
94          final PrimeRequestContext requestContext = PrimeRequestContext.getCurrentInstance(context);
95          final PrimeApplicationContext applicationContext = requestContext.getApplicationContext();
96          final PrimeConfiguration configuration = applicationContext.getConfig();
97  
98          final ProjectStage projectStage = context.getApplication().getProjectStage();
99  
100         writer.append("if(window.PrimeFaces){");
101 
102         writer.append("PrimeFaces.settings.locale='").append(LocaleUtils.getCurrentLocale(context)).append("';");
103         writer.append("PrimeFaces.settings.viewId='").append(context.getViewRoot().getViewId()).append("';");
104         writer.append("PrimeFaces.settings.contextPath='").append(context.getExternalContext().getRequestContextPath())
105                     .append("';");
106 
107         writer.append("PrimeFaces.settings.cookiesSecure=")
108                     .append(requestContext.isSecure() && configuration.isCookiesSecure()).append(";");
109         if (applicationContext.getConfig().getCookiesSameSite() != null) {
110             writer.append("PrimeFaces.settings.cookiesSameSite='").append(configuration.getCookiesSameSite())
111                         .append("';");
112         }
113 
114         if (configuration.isClientSideValidationEnabled()) {
115             writer.append("PrimeFaces.settings.validateEmptyFields=").append(configuration.isValidateEmptyFields())
116                         .append(";");
117             writer.append("PrimeFaces.settings.considerEmptyStringNull=")
118                         .append(configuration.isInterpretEmptyStringAsNull()).append(";");
119         }
120 
121         if (configuration.isLegacyWidgetNamespace()) {
122             writer.append("PrimeFaces.settings.legacyWidgetNamespace=true;");
123         }
124 
125         if (configuration.isEarlyPostParamEvaluation()) {
126             writer.append("PrimeFaces.settings.earlyPostParamEvaluation=true;");
127         }
128 
129         if (configuration.isPartialSubmitEnabled()) {
130             writer.append("PrimeFaces.settings.partialSubmit=true;");
131         }
132 
133         if (projectStage != ProjectStage.Production) {
134             writer.append("PrimeFaces.settings.projectStage='").append(projectStage.toString()).append("';");
135         }
136 
137         if (context.getExternalContext().getClientWindow() != null) {
138 
139             final ClientWindow clientWindow = context.getExternalContext().getClientWindow();
140             if (clientWindow instanceof PrimeClientWindow) {
141 
142                 boolean initialRedirect = false;
143 
144                 final Object cookie = PrimeClientWindowUtils.getInitialRedirectCookie(context, clientWindow.getId());
145                 if (cookie instanceof Cookie) {
146                     final Cookie servletCookie = (Cookie) cookie;
147                     initialRedirect = true;
148 
149                     // expire/remove cookie
150                     servletCookie.setMaxAge(0);
151                     ((HttpServletResponse) context.getExternalContext().getResponse()).addCookie(servletCookie);
152                 }
153                 writer.append(
154                             String.format("PrimeFaces.clientwindow.init('%s', %s);",
155                                         PrimeClientWindowUtils.secureWindowId(clientWindow.getId()),
156                                         initialRedirect));
157             }
158         }
159 
160         writer.append("}");
161     }
162 
163     protected void encodeInitScripts(final FacesContext context, final StringBuilder writer) {
164         final PrimeRequestContext requestContext = PrimeRequestContext.getCurrentInstance(context);
165         final List<String> scripts = requestContext.getInitScriptsToExecute();
166 
167         if (!scripts.isEmpty()) {
168             final boolean moveScriptsToBottom = requestContext.getApplicationContext().getConfig()
169                         .isMoveScriptsToBottom();
170 
171             if (!moveScriptsToBottom) {
172                 writer.append("$(function(){");
173             }
174 
175             for (final String script : scripts) {
176                 writer.append(script);
177                 writer.append(';');
178             }
179 
180             if (!moveScriptsToBottom) {
181                 writer.append("});");
182             }
183         }
184     }
185 
186     private void addJS(final FacesContext context, final String script) {
187         final UIOutput js = new UIOutput();
188         js.setRendererType("javax.faces.resource.Script");
189         // https://github.com/primefaces-extensions/primefaces-extensions/issues/486
190         // https://github.com/primefaces-extensions/primefaces-extensions/issues/517
191         // MyFaces needs ID set to prevent duplicate ID check in Dev mode, Mojarra does not
192         final PrimeEnvironment environment = PrimeApplicationContext.getCurrentInstance(context).getEnvironment();
193         if (!environment.isMojarra()) {
194             js.setId("primfaces-script-processor");
195         }
196         final UIOutput content = new UIOutput();
197         content.setValue(script);
198         js.getChildren().add(content);
199         context.getViewRoot().addComponentResource(context, js);
200     }
201 }