View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.view.jsp;
20  
21  import java.io.IOException;
22  import java.util.Arrays;
23  import java.util.LinkedList;
24  import java.util.Locale;
25  import java.util.logging.Level;
26  import java.util.logging.Logger;
27  import java.util.stream.Stream;
28  
29  import javax.faces.FacesException;
30  import javax.faces.application.ViewVisitOption;
31  import javax.faces.component.UIViewRoot;
32  import javax.faces.context.ExternalContext;
33  import javax.faces.context.FacesContext;
34  import javax.faces.event.PostAddToViewEvent;
35  import javax.faces.render.ResponseStateManager;
36  import javax.servlet.ServletRequest;
37  import javax.servlet.ServletResponse;
38  import javax.servlet.http.HttpServletResponse;
39  import javax.servlet.jsp.jstl.core.Config;
40  
41  import org.apache.myfaces.application.jsp.ServletViewResponseWrapper;
42  import org.apache.myfaces.application.viewstate.StateCacheUtils;
43  import org.apache.myfaces.shared.view.JspViewDeclarationLanguageBase;
44  import org.apache.myfaces.view.ViewDeclarationLanguageStrategy;
45  import org.apache.myfaces.view.facelets.tag.composite.CompositeLibrary;
46  import org.apache.myfaces.view.facelets.tag.jsf.core.CoreLibrary;
47  import org.apache.myfaces.view.facelets.tag.jsf.html.HtmlLibrary;
48  import org.apache.myfaces.view.facelets.tag.ui.UILibrary;
49  
50  /**
51   * @author Simon Lessard (latest modification by $Author$)
52   * @version $Revision$ $Date$
53   * 
54   * @since 2.0
55   */
56  public class JspViewDeclarationLanguage extends JspViewDeclarationLanguageBase
57  {
58      public static final Logger log = Logger.getLogger(JspViewDeclarationLanguage.class.getName());
59      
60      /**
61       * Tags that are only available on facelets and not on JSP.
62       * If a user uses one of these tags on a JSP, we will provide
63       * a more informative error message than the standard one.
64       */
65      public static final String[] FACELETS_ONLY_F_TAGS = {"ajax", "event", "metadata"};
66      public static final String[] FACELETS_ONLY_H_TAGS = {"outputScript", "outputStylesheet",
67                                                           "head", "body", "button", "link"};
68  
69      private final ViewDeclarationLanguageStrategy _strategy;
70      private LinkedList<String> _suffixes;
71      
72      /**
73       * 
74       */
75      public JspViewDeclarationLanguage()
76      {
77          if (log.isLoggable(Level.FINEST))
78          {
79              log.finest("New JspViewDeclarationLanguage instance created");
80          }
81          
82          _strategy = new JspViewDeclarationLanguageStrategy();
83      }
84  
85      public JspViewDeclarationLanguage(FacesContext facesContext, ViewDeclarationLanguageStrategy strategy, 
86                                        LinkedList<String> suffixes)
87      {
88          this._strategy = strategy;
89          this._suffixes = suffixes;
90      }
91  
92      /**
93       * {@inheritDoc}
94       */
95      @Override
96      public void buildView(FacesContext context, UIViewRoot view) throws IOException
97      {
98          // call buildView() from JspViewDeclarationLanguageBase to do some startup work
99          super.buildView(context, view);
100         
101         ExternalContext externalContext = context.getExternalContext();
102         ServletResponse response = (ServletResponse) externalContext.getResponse();
103         ServletRequest request = (ServletRequest) externalContext.getRequest();
104         
105         Locale locale = view.getLocale();
106         response.setLocale(locale);
107         Config.set(request, Config.FMT_LOCALE, context.getViewRoot().getLocale());
108 
109         String viewId = view.getViewId();
110         ServletViewResponseWrapper wrappedResponse = new ServletViewResponseWrapper((HttpServletResponse) response);
111 
112         externalContext.setResponse(wrappedResponse);
113         try
114         {
115             externalContext.dispatch(viewId);
116         }
117         catch (FacesException e)
118         {
119             // try to extract the most likely exceptions here
120             // and provide a better error message for them
121             
122             String message = e.getMessage(); 
123             
124             // errors related to using facelets-only tags on a JSP page
125             if (message != null)
126             {
127                 // does the message contain "f" (prefix f of tags)
128                 // or the related uri http://java.sun.com/jsf/core
129                 if (message.contains("\"f\"") 
130                         || message.contains("\"" + CoreLibrary.NAMESPACE + "\""))
131                 {
132                     // check facelets-only f tags
133                     for (String tag : FACELETS_ONLY_F_TAGS)
134                     {
135                         if (message.contains("\"" + tag + "\""))
136                         {
137                             String exceptionMessage = "The tag f:" + tag + 
138                                     " is only available on facelets.";
139                             throw new FacesException(exceptionMessage, 
140                                     new FaceletsOnlyException(exceptionMessage, e.getCause()));
141                         }
142                     }
143                 }  
144                 else if (message.contains("\"h\"") 
145                         || message.contains("\"" + HtmlLibrary.NAMESPACE + "\""))
146                 {
147                     // check facelets-only h tags
148                     for (String tag : FACELETS_ONLY_H_TAGS)
149                     {
150                         if (message.contains("\"" + tag + "\""))
151                         {
152                             String exceptionMessage = "The tag h:" + tag + 
153                                     " is only available on facelets.";
154                             throw new FacesException(exceptionMessage, 
155                                     new FaceletsOnlyException(exceptionMessage, e.getCause()));
156                         }
157                     }
158                 }
159                 else 
160                 {
161                     // check facelets-only namespaces
162                     String namespace = null;
163                     if (message.contains(UILibrary.NAMESPACE))
164                     {
165                         namespace = UILibrary.NAMESPACE;
166                     }
167                     else if (message.contains(CompositeLibrary.NAMESPACE))
168                     {
169                         namespace = CompositeLibrary.NAMESPACE;
170                     }
171                     
172                     if (namespace != null)
173                     {
174                         // the message contains a facelets-only namespace
175                         String exceptionMessage = "All tags with namespace " +
176                                 namespace + " are only available on facelets.";
177                         throw new FacesException(exceptionMessage, 
178                                 new FaceletsOnlyException(exceptionMessage, e.getCause()));
179                     }
180                 }
181             }
182             
183             // no rule applied to this Exception - rethrow it
184             throw e;
185         }
186         finally
187         {
188             externalContext.setResponse(response);
189         }
190 
191         boolean errorResponse = wrappedResponse.getStatus() < 200 || wrappedResponse.getStatus() > 299;
192         if (errorResponse)
193         {
194             wrappedResponse.flushToWrappedResponse();
195             return;
196         }
197 
198         // Skip this step if we are rendering an ajax request, because no content outside
199         // f:view tag should be output.
200         // Note that the ResponseSwitch would prevent this output from beeing written
201         // in renderView(), but not providing the information at all makes it faster!
202         if (!context.getPartialViewContext().isPartialRequest())
203         {
204             // store the wrapped response in the request, so it is thread-safe
205             setAfterViewTagResponseWrapper(externalContext, wrappedResponse);
206         }
207         
208         // Publish PostAddToView over UIViewRoot, because this is not done automatically.
209         context.getApplication().publishEvent(context, PostAddToViewEvent.class, UIViewRoot.class, view);
210     }
211 
212     /**
213      * 
214      */
215     @Override
216     protected boolean isViewStateAlreadyEncoded(FacesContext context)
217     {
218         ResponseStateManager responseStateManager = context.getRenderKit().getResponseStateManager();
219         if (StateCacheUtils.isMyFacesResponseStateManager(responseStateManager))
220         {
221             if (StateCacheUtils.getMyFacesResponseStateManager(responseStateManager).
222                     isWriteStateAfterRenderViewRequired(context))
223             {
224                 return false;
225             }
226             else
227             {
228                 return true;
229             }
230         }
231         else
232         {
233             return false;
234         }
235     }
236 
237     @Override
238     protected void sendSourceNotFound(FacesContext context, String message)
239     {
240         HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
241         try
242         {
243             context.responseComplete();
244             response.sendError(HttpServletResponse.SC_NOT_FOUND, message);
245         }
246         catch (IOException ioe)
247         {
248             throw new FacesException(ioe);
249         }
250     }
251 
252     @Override
253     public boolean viewExists(FacesContext facesContext, String viewId)
254     {
255         if (_strategy.handles(viewId))
256         {
257             return super.viewExists(facesContext, viewId);
258         }
259         return false;
260     }
261     
262     @Override
263     public Stream<String> getViews(FacesContext facesContext, String path, int maxDepth, ViewVisitOption... options)
264     {
265         Stream<String> stream = super.getViews(facesContext, path, maxDepth, options);
266         stream = stream.filter(f -> (_strategy.handles(f) && isValidJSPView(facesContext,f)));
267         if (options != null &&
268             Arrays.binarySearch(options, ViewVisitOption.RETURN_AS_MINIMAL_IMPLICIT_OUTCOME) >= 0)
269         {
270             stream = stream.map(f -> _strategy.getMinimalImplicitOutcome(f));
271         }
272         return stream;
273     }
274 
275     private boolean isValidJSPView(FacesContext facesContext, String path)
276     {
277         boolean isValid = false;
278 
279         // _suffixes should only be null if this JSPViewDeclarationLanguage was created
280         // via the default constructor.
281         if (_suffixes == null)
282         {
283             _suffixes = JspViewDeclarationLanguageStrategy.loadSuffixes(facesContext.getExternalContext());
284         }
285 
286         for (String suffix : _suffixes)
287         {
288             if (path != null && path.endsWith (suffix)) 
289             {
290                 isValid = true;
291                 break;
292             }
293         }
294 
295         return isValid;
296     }
297 
298 }