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.custom.stylesheet;
20  
21  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  import javax.faces.context.FacesContext;
26  import javax.servlet.ServletContext;
27  import java.io.Serializable;
28  import java.io.StringReader;
29  import java.io.BufferedReader;
30  import java.io.StringWriter;
31  import java.io.PrintWriter;
32  import java.io.IOException;
33  import java.util.Map;
34  import java.util.TreeMap;
35  import java.util.Collections;
36  
37  /**
38   * Loads, filters and then caches any resource available to the webapp.
39   * <p>
40   * The resource can then be retrieved from the cache when desired. In
41   * particular, it can be retrieved via a URL that invokes the Tomahawk
42   * ExtensionsFilter and TextResourceFilterProvider classes.
43   * <p>
44   * The "filtering" process looks for any strings of form #{...} in the
45   * resource, and executes it as an EL expression. The original expression
46   * is then replaced with the return value of the expression.
47   * <p>
48   * Note that evaluation of EL expressions happens <i>only once</i>, when
49   * the resource is loaded into the cache for the first time. And the EL
50   * expressions are evaluated using the context of whatever faces request
51   * happens to be active at the time. EL expressions in filtered resources
52   * should therefore not reference any request-scoped or session-scoped values
53   * as the cached result would be unpredictable.
54   * <p>
55   * This class is a per-webapp singleton, accessed via the getInstance method.
56   *
57   * @author imario
58   */
59  public class TextResourceFilter implements Serializable
60  {
61      private static final Log log = LogFactory.getLog(TextResourceFilter.class);
62  
63      private final static String CONTEXT_KEY = TextResourceFilter.class.getName() + ".INSTANCE";
64  
65      // A cache of all the resources ever loaded via getOrCreateFilteredResource
66      private final Map filteredResources = Collections.synchronizedMap(new TreeMap());
67  
68      public static class ResourceInfo
69      {
70          private final long lastModified;
71          private final String text;
72  
73          protected ResourceInfo(long lastModified, String text)
74          {
75              this.lastModified = lastModified;
76              this.text = text;
77          }
78  
79          public long getLastModified()
80          {
81              return lastModified;
82          }
83  
84          public String getText()
85          {
86              return text;
87          }
88  
89          public int getSize()
90          {
91              return text.length();
92          }
93      }
94  
95      protected TextResourceFilter()
96      {
97      }
98  
99      protected static TextResourceFilter create()
100     {
101         return new TextResourceFilter();
102     }
103 
104     /**
105      * Return the application-singleton instance of this class.
106      */
107     public static TextResourceFilter getInstance(ServletContext context)
108     {
109         synchronized(TextResourceFilter.class)
110         {
111             TextResourceFilter filterText = (TextResourceFilter) context.getAttribute(CONTEXT_KEY);
112             if (filterText == null)
113             {
114                 filterText = create();
115                 context.setAttribute(CONTEXT_KEY, filterText);
116             }
117             return filterText;
118         }
119     }
120 
121     /**
122      * Return the application-singleton instance of this class.
123      */
124     public static TextResourceFilter getInstance(FacesContext context)
125     {
126         Map appMap = context.getExternalContext().getApplicationMap();
127         synchronized(TextResourceFilter.class)
128         {
129             TextResourceFilter filterText = (TextResourceFilter) appMap.get(CONTEXT_KEY);
130             if (filterText == null)
131             {
132                 filterText = create();
133                 appMap.put(CONTEXT_KEY, filterText);
134             }
135             return filterText;
136         }
137     }
138 
139     /**
140      * Return the cached content for the specified resource.
141      * <p>
142      * If the resource is not already in the cache (due to an earlier call to
143      * getOrCreateFilteredResource) then null is returned.
144      * <p>
145      * The path param is a simple key that must match the value passed to
146      * an earlier call to getOrCreateFilteredResource.
147      */
148     public ResourceInfo getFilteredResource(String path)
149     {
150         ResourceInfo filteredResource = (ResourceInfo) filteredResources.get(path);
151         if (filteredResource == null)
152         {
153             return null;
154         }
155         return filteredResource;
156     }
157 
158     /**
159      * Load, filter and cache the specified resource (if it isn't already cached).
160      * <p>
161      * Note: This method is not synchronized for performance reasons (the map is).
162      * The worst case is that we filter a resource twice the first time which is not
163      * a problem.
164      * <P>
165      * The path param must start with a slash, and is interpreted as a path relative
166      * to the webapp root (not the current page).
167      */
168     public ResourceInfo getOrCreateFilteredResource(FacesContext context, String path) throws IOException
169     {
170         if (!path.startsWith("/"))
171         {
172             throw new IllegalArgumentException("Path must start with a slash, but was: " + path);
173         }
174 
175         ResourceInfo filteredResource = getFilteredResource(path);
176         if (filteredResource != null)
177         {
178             return filteredResource;
179         }
180 
181         //Tomcat ASF Bugzilla � Bug 43241
182         //ServletContext.getResourceAsStream() does not follow API spec
183         //ALL resources must start with '/' 
184         String text = RendererUtils.loadResourceFile(context, path);
185         if (text == null)
186         {
187             // avoid loading the errorneous resource over and over again
188             text = "";
189         }
190 
191         StringWriter stringWriter = new StringWriter();
192         PrintWriter writer = new PrintWriter(stringWriter);
193         BufferedReader reader = null;
194         try
195         {
196             reader = new BufferedReader(new StringReader(text.toString()));
197 
198             String line;
199             while ((line = reader.readLine()) != null)
200             {
201                 int pos = line.indexOf("#{");
202                 if (pos > -1 && line.indexOf("}", pos) > -1)
203                 {
204                     line = RendererUtils.getStringValue(context, context.getApplication().createValueBinding(line));
205                 }
206 
207                 if (line != null)
208                 {
209                     writer.println(line);
210                 }
211             }
212         }
213         finally
214         {
215             if (reader != null)
216             {
217                 try
218                 {
219                     reader.close();
220                 }
221                 catch (IOException e)
222                 {
223                     log.warn(e.getLocalizedMessage(), e);
224                 }
225             }
226 
227             writer.close();
228         }
229 
230         filteredResource = new ResourceInfo(System.currentTimeMillis(), stringWriter.toString());
231         filteredResources.put(path, filteredResource);
232 
233         return filteredResource;
234     }
235 }