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 }