1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.internal.context;
21
22 import org.apache.commons.collections.CollectionUtils;
23 import org.apache.commons.io.IOUtils;
24 import org.apache.myfaces.tobago.context.ThemeImpl;
25 import org.apache.myfaces.tobago.internal.config.ThemeParser;
26 import org.apache.myfaces.tobago.internal.config.TobagoConfigFragment;
27 import org.apache.myfaces.tobago.internal.config.TobagoConfigParser;
28 import org.apache.myfaces.tobago.internal.util.Deprecation;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import org.xml.sax.SAXException;
32
33 import javax.servlet.ServletContext;
34 import javax.servlet.ServletException;
35 import java.io.File;
36 import java.io.FileNotFoundException;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.net.MalformedURLException;
40 import java.net.URI;
41 import java.net.URISyntaxException;
42 import java.net.URL;
43 import java.util.ArrayList;
44 import java.util.Enumeration;
45 import java.util.List;
46 import java.util.Properties;
47 import java.util.Set;
48 import java.util.zip.ZipEntry;
49 import java.util.zip.ZipInputStream;
50
51
52
53
54
55
56
57 class ResourceLocator {
58
59 private static final Logger LOG = LoggerFactory.getLogger(ResourceLocator.class);
60
61 private static final String META_INF_TOBAGO_CONFIG_XML = "META-INF/tobago-config.xml";
62 private static final String META_INF_TOBAGO_THEME_XML = "META-INF/tobago-theme.xml";
63 private static final String META_INF_RESOURCES = "META-INF/resources";
64
65 private ServletContext servletContext;
66 private ResourceManagerImpl resourceManager;
67 private ThemeBuilder themeBuilder;
68
69 public ResourceLocator(
70 ServletContext servletContext, ResourceManagerImpl resourceManager, ThemeBuilder themeBuilder) {
71 this.servletContext = servletContext;
72 this.resourceManager = resourceManager;
73 this.themeBuilder = themeBuilder;
74 }
75
76 public void locate()
77 throws ServletException {
78
79 locateResourcesInWar(servletContext, resourceManager, "/");
80 locateResourcesFromClasspath(resourceManager);
81 locateResourcesServlet30Alike(resourceManager);
82 }
83
84 private void locateResourcesInWar(
85 ServletContext servletContext, ResourceManagerImpl resources, String path)
86 throws ServletException {
87
88 if (path.startsWith("/WEB-INF/")) {
89 return;
90 }
91
92 if (path.endsWith("/") && path.length() > 1) {
93 path = path.substring(0, path.length() - 1);
94 }
95 Set<String> resourcePaths = servletContext.getResourcePaths(path);
96 if (resourcePaths == null || resourcePaths.isEmpty()) {
97 if (LOG.isDebugEnabled()) {
98 LOG.debug("Skipping empty resource path: path='{}'", path);
99 }
100 return;
101 }
102 for (String childPath : resourcePaths) {
103 if (childPath.endsWith("/")) {
104
105 if (!childPath.equals(path)) {
106 if (LOG.isDebugEnabled()) {
107 LOG.debug("childPath dir {}", childPath);
108 }
109 locateResourcesInWar(servletContext, resources, childPath);
110 }
111 } else {
112
113 if (childPath.endsWith(".properties")) {
114 InputStream inputStream = servletContext.getResourceAsStream(childPath);
115 try {
116 addProperties(inputStream, resources, childPath, false, 0);
117 } finally {
118 IOUtils.closeQuietly(inputStream);
119 }
120 } else if (childPath.endsWith(".properties.xml")) {
121 InputStream inputStream = servletContext.getResourceAsStream(childPath);
122 try {
123 addProperties(inputStream, resources, childPath, true, 0);
124 } catch (RuntimeException e) {
125 LOG.error("childPath = \"" + childPath + "\" ", e);
126 throw e;
127 } finally {
128 IOUtils.closeQuietly(inputStream);
129 }
130 } else {
131 resources.add(childPath);
132 }
133 }
134 }
135 }
136
137 private void locateResourcesFromClasspath(ResourceManagerImpl resources)
138 throws ServletException {
139
140 ThemeParser parser = new ThemeParser();
141 try {
142 if (LOG.isInfoEnabled()) {
143 LOG.info("Searching for '" + META_INF_TOBAGO_THEME_XML + "' and '" + META_INF_TOBAGO_CONFIG_XML +"'");
144 }
145 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
146 List<URL> urls = new ArrayList<URL>();
147 CollectionUtils.addAll(urls, classLoader.getResources(META_INF_TOBAGO_CONFIG_XML));
148 CollectionUtils.addAll(urls, classLoader.getResources(META_INF_TOBAGO_THEME_XML));
149
150 for (URL themeUrl : urls) {
151 if (themeUrl.toString().endsWith(META_INF_TOBAGO_CONFIG_XML)) {
152 TobagoConfigFragment tobagoConfig = new TobagoConfigParser().parse(themeUrl);
153 for (ThemeImpl theme : tobagoConfig.getThemeDefinitions()) {
154 if (theme.isVersioned()) {
155 String themeUrlStr = themeUrl.toString();
156 int index = themeUrlStr.indexOf(META_INF_TOBAGO_CONFIG_XML);
157 String metaInf = themeUrlStr.substring(0, index) + "META-INF/MANIFEST.MF";
158 Properties properties = new Properties();
159 final URL url = new URL(metaInf);
160 InputStream inputStream = null;
161 String version = null;
162 try {
163 inputStream = url.openStream();
164 properties.load(inputStream);
165 version = properties.getProperty("Implementation-Version");
166 } catch (FileNotFoundException e) {
167
168 LOG.error("No Manifest-File found.");
169 } finally {
170 IOUtils.closeQuietly(inputStream);
171 }
172 if (version != null) {
173 theme.setVersion(version);
174 } else {
175 theme.setVersioned(false);
176 LOG.error("No Implementation-Version found in Manifest-File for theme: '" + theme.getName()
177 + "'. Resetting the theme to unversioned. Please correct the Manifest-File.");
178 }
179 }
180 addThemeResources(resources, themeUrl, theme);
181 }
182 } else {
183
184 addThemeResources(resources, themeUrl, parser.parse(themeUrl));
185 }
186 }
187 } catch (IOException e) {
188 String msg = "while loading ";
189 LOG.error(msg, e);
190 throw new ServletException(msg, e);
191 } catch (SAXException e) {
192 String msg = "while loading ";
193 LOG.error(msg, e);
194 throw new ServletException(msg, e);
195 }
196 }
197
198 private void addThemeResources(ResourceManagerImpl resources, URL themeUrl, ThemeImpl theme)
199 throws IOException, ServletException {
200 themeBuilder.addTheme(theme);
201 String prefix = ensureSlash(theme.getResourcePath());
202
203 String protocol = themeUrl.getProtocol();
204
205
206
207 if (!"jar".equals(protocol) && !"zip".equals(protocol) && !"wsjar".equals(protocol)) {
208 LOG.warn("Unknown protocol '" + themeUrl + "'");
209 }
210 addResources(resources, themeUrl, prefix, 0);
211 }
212
213
214
215
216
217
218
219
220 private void locateResourcesServlet30Alike(ResourceManagerImpl resources) throws ServletException {
221
222 try {
223 if (LOG.isInfoEnabled()) {
224 LOG.info("Searching for " + META_INF_RESOURCES);
225 }
226 ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
227 Enumeration<URL> urls = classLoader.getResources(META_INF_RESOURCES);
228
229 while (urls.hasMoreElements()) {
230 URL resourcesUrl = urls.nextElement();
231
232 LOG.info("resourcesUrl='"+resourcesUrl + "'");
233 if (!resourcesUrl.toString().matches(".*/WEB-INF/lib/.*\\.jar\\!.*")) {
234 LOG.info("skip ...");
235 continue;
236
237 }
238 LOG.info("going on ...");
239
240 String protocol = resourcesUrl.getProtocol();
241
242
243
244 if (!"jar".equals(protocol) && !"zip".equals(protocol) && !"wsjar".equals(protocol)) {
245 LOG.warn("Unknown protocol '" + resourcesUrl + "'");
246 }
247 addResources(resources, resourcesUrl, "/" + META_INF_RESOURCES, META_INF_RESOURCES.length() + 1);
248 }
249 } catch (IOException e) {
250 String msg = "while loading ";
251 LOG.error(msg, e);
252 throw new ServletException(msg, e);
253 }
254 }
255
256 private void addResources(ResourceManagerImpl resources, URL themeUrl, String prefix, int skipPrefix)
257 throws IOException, ServletException {
258 String fileName = themeUrl.toString();
259 if (fileName.endsWith(META_INF_TOBAGO_THEME_XML)) {
260 Deprecation.LOG.warn(
261 "The use of 'tobago-theme.xml' is deprecated, please use 'tobago-config.xml' to define a theme!");
262 }
263 int index = fileName.indexOf("!");
264 String protocol = themeUrl.getProtocol();
265 if (index != -1) {
266 fileName = fileName.substring(protocol.length() + 1, index);
267 }
268 if (LOG.isInfoEnabled()) {
269 LOG.info("Adding resources from fileName='" + fileName + "' prefix='" + prefix + "' skip=" + skipPrefix + "");
270 }
271
272
273 if (!protocol.equals("vfszip")
274 && (fileName.endsWith(META_INF_TOBAGO_THEME_XML) || fileName.endsWith(META_INF_TOBAGO_CONFIG_XML))) {
275 try {
276 URI uri = themeUrl.toURI();
277 File tobagoThemeXml = new File(uri);
278 File directoryFile = tobagoThemeXml.getParentFile().getParentFile();
279 String resourcePath = "";
280 resolveTheme(resources, directoryFile, resourcePath, prefix, false);
281 } catch (URISyntaxException e) {
282 LOG.error("themeUrl='" + themeUrl + "'", e);
283 }
284 } else {
285 URL jarFile;
286 try {
287
288 if (protocol.equals("vfszip")) {
289 fileName = new File(fileName).getParentFile().getParentFile().getPath();
290 if (File.separatorChar == '\\' && fileName.contains("\\")) {
291 fileName = fileName.replace('\\', '/');
292 if (LOG.isInfoEnabled()) {
293 LOG.info("Fixed slashes for virtual filesystem protocol on windows system: " + fileName);
294 }
295 }
296 }
297 jarFile = new URL(fileName);
298 } catch (MalformedURLException e) {
299
300 jarFile = new URL("file:" + fileName);
301 }
302 InputStream stream = null;
303 ZipInputStream zipStream = null;
304 try {
305 stream = jarFile.openStream();
306 zipStream = new ZipInputStream(stream);
307 while (zipStream.available() > 0) {
308 ZipEntry nextEntry = zipStream.getNextEntry();
309 if (nextEntry == null || nextEntry.isDirectory()) {
310 continue;
311 }
312 String name = "/" + nextEntry.getName();
313 if (name.startsWith(prefix)) {
314 addResource(resources, name, skipPrefix);
315 }
316 }
317 } finally {
318 IOUtils.closeQuietly(stream);
319 IOUtils.closeQuietly(zipStream);
320 }
321 }
322 }
323
324 private void resolveTheme(ResourceManagerImpl resources, File directoryFile,
325 String resourcePath, String prefix, boolean inResourcePath) throws ServletException {
326 File[] files = directoryFile.listFiles();
327 for (File file : files) {
328 if (file.isDirectory()) {
329 String currentResourcePath = resourcePath + File.separator + file.getName();
330 if (!inResourcePath && currentResourcePath.startsWith(prefix)) {
331 inResourcePath = true;
332 }
333 resolveTheme(resources, file, currentResourcePath, prefix, inResourcePath);
334 } else {
335 if (LOG.isInfoEnabled()) {
336 LOG.info(resourcePath + File.separator + file.getName());
337 }
338 if (inResourcePath) {
339 addResource(resources, resourcePath + File.separator + file.getName(), 0);
340 }
341 }
342 }
343 }
344
345 private void addResource(ResourceManagerImpl resources, String name, int skipPrefix)
346 throws ServletException {
347
348 if (name.endsWith(".class")) {
349
350 } else if (name.endsWith(".properties")) {
351 if (LOG.isInfoEnabled()) {
352 LOG.info("Adding properties from: '" + name.substring(1) + "'");
353 }
354 InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name.substring(1));
355 try {
356 addProperties(inputStream, resources, name, false, skipPrefix);
357 } finally {
358 IOUtils.closeQuietly(inputStream);
359 }
360 } else if (name.endsWith(".properties.xml")) {
361 if (LOG.isInfoEnabled()) {
362 LOG.info("Adding properties from: '" + name.substring(1) + "'");
363 }
364 InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name.substring(1));
365 try {
366 addProperties(inputStream, resources, name, true, skipPrefix);
367 } finally {
368 IOUtils.closeQuietly(inputStream);
369 }
370 } else {
371 resources.add(name.substring(skipPrefix));
372 }
373 }
374
375 private String ensureSlash(String resourcePath) {
376 if (!resourcePath.startsWith("/")) {
377 resourcePath = '/' + resourcePath;
378 }
379 if (!resourcePath.endsWith("/")) {
380 resourcePath = resourcePath + '/';
381 }
382 return resourcePath;
383 }
384
385 private void addProperties(
386 InputStream stream, ResourceManagerImpl resources, String childPath, boolean xml, int skipPrefix)
387 throws ServletException {
388
389 String directory = childPath.substring(skipPrefix, childPath.lastIndexOf('/'));
390 String filename = childPath.substring(childPath.lastIndexOf('/') + 1);
391
392 int end = filename.lastIndexOf('.');
393 if (xml) {
394 end = filename.lastIndexOf('.', end - 1);
395 }
396
397 String locale = filename.substring(0, end);
398
399
400 Properties temp = new Properties();
401 try {
402 if (xml) {
403 temp.loadFromXML(stream);
404 if (LOG.isDebugEnabled()) {
405 LOG.debug(childPath);
406 LOG.debug("xml properties: {}", temp.size());
407 }
408 } else {
409 temp.load(stream);
410 if (LOG.isDebugEnabled()) {
411 LOG.debug(childPath);
412 LOG.debug(" properties: {}", temp.size());
413 }
414 }
415 } catch (IOException e) {
416 String msg = "while loading " + childPath;
417 LOG.error(msg, e);
418 throw new ServletException(msg, e);
419 } finally {
420 IOUtils.closeQuietly(stream);
421 }
422
423 for (Enumeration e = temp.propertyNames(); e.hasMoreElements();) {
424 String key = (String) e.nextElement();
425 resources.add(directory + '/' + locale + '/' + key, temp.getProperty(key));
426 if (LOG.isDebugEnabled()) {
427 LOG.debug(directory + '/' + locale + '/' + key + "=" + temp.getProperty(key));
428 }
429 }
430 }
431 }