001: /*
002: * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003: *
004: * "The contents of this file are subject to the Mozilla Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License at
007: * http://www.mozilla.org/MPL/
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
011: * License for the specific language governing rights and limitations under
012: * the License.
013: *
014: * The Original Code is ICEfaces 1.5 open source software code, released
015: * November 5, 2006. The Initial Developer of the Original Code is ICEsoft
016: * Technologies Canada, Corp. Portions created by ICEsoft are Copyright (C)
017: * 2004-2006 ICEsoft Technologies Canada, Corp. All Rights Reserved.
018: *
019: * Contributor(s): _____________________.
020: *
021: * Alternatively, the contents of this file may be used under the terms of
022: * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"
023: * License), in which case the provisions of the LGPL License are
024: * applicable instead of those above. If you wish to allow use of your
025: * version of this file only under the terms of the LGPL License and not to
026: * allow others to use your version of this file under the MPL, indicate
027: * your decision by deleting the provisions above and replace them with
028: * the notice and other provisions required by the LGPL License. If you do
029: * not delete the provisions above, a recipient may use your version of
030: * this file under either the MPL or the LGPL License."
031: *
032: */
033:
034: package com.icesoft.faces.webapp.parser;
035:
036: import com.icesoft.jasper.JasperException;
037: import com.icesoft.jasper.compiler.TldLocationsCache;
038: import com.icesoft.jasper.xmlparser.ParserUtils;
039: import com.icesoft.jasper.xmlparser.TreeNode;
040: import org.apache.commons.logging.Log;
041: import org.apache.commons.logging.LogFactory;
042:
043: import javax.faces.context.ExternalContext;
044: import javax.faces.context.FacesContext;
045: import java.io.File;
046: import java.io.FileReader;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.InputStreamReader;
050: import java.io.Reader;
051: import java.io.StringBufferInputStream;
052: import java.io.StringReader;
053: import java.io.StringWriter;
054: import java.net.JarURLConnection;
055: import java.net.URL;
056: import java.net.URLConnection;
057: import java.util.Arrays;
058: import java.util.Enumeration;
059: import java.util.Hashtable;
060: import java.util.Iterator;
061: import java.util.jar.JarEntry;
062: import java.util.jar.JarFile;
063: import java.util.regex.Matcher;
064: import java.util.regex.Pattern;
065: import java.util.zip.ZipEntry;
066:
067: public class JspPageToDocument {
068: static String JSP_TAGLIB_PATTERN = "<%@\\s*taglib\\s+(.*?)%>";
069: static String URI_PATTERN = "uri=\"(.*?)\"";
070: static String PREFIX_PATTERN = "prefix=\"(.*?)\"";
071: static String JSP_DECL_PATTERN = "<%.*?%>";
072: static String STATIC_INCLUDE_PATTERN = "<%@\\s*include\\s+file=\"([^\"]*)\".*?%>";
073: static String STATIC_INCLUDE_DIRECTIVE_PATTERN = "<\\s*jsp:directive.include\\s+file=\"([^\"]*)\".*?/>";
074: static String OPEN_TAG_PATTERN = "<\\s*(\\w*\\:*\\w+)([^>]*)>";
075: static String GENERIC_TAG_PATTERN = "(<\\s*)(/*)(\\w+)([^:])";
076: static String ATTRIBUTE_PATTERN = "\\s*([^=]*)\\s*=\\s*\"([^\"]*)\"";
077: static String HTML_TAG_PATTERN = "<\\s*html";
078: static String P_TAG_PATTERN = "<\\s*/*(p)([^>]*?)/*>";
079: static String IMG_TAG_PATTERN = "<\\s*(img)([^>]*?)/*>";
080: static String JSP_INCLUDE_PATTERN = "<\\s*jsp:include([^>]*?)/>";
081: static String BR_TAG_PATTERN = "<\\s*br\\s*>";
082: static String HR_TAG_PATTERN = "<\\s*hr\\s*>";
083: static String LINK_TAG_PATTERN = "<\\s*(link)([^>]*?)/*>";
084: static String META_TAG_PATTERN = "<\\s*(meta)([^>]*?)/*>";
085: static String INPUT_TAG_PATTERN = "<\\s*(input)([^>]*?)/*>";
086: static String NBSP_ENTITY_PATTERN = " ";
087: static String COPY_ENTITY_PATTERN = "©";
088: static String DOC_DECL_PATTERN = "<!DOCTYPE [^>]*>";
089: static String XML_DECL_PATTERN = "<\\?xml [^>]*\\?>";
090:
091: static String MYFACES_TAG_CLASS = "org/apache/myfaces/taglib/core/ViewTag.class";
092: static String SUN_TAG_CLASS = "com/sun/faces/taglib/jsf_core/ViewTag.class";
093:
094: private static final Log log = LogFactory
095: .getLog(JspPageToDocument.class);
096:
097: public static void main(String[] args) {
098: try {
099: getInputAsString(transform(new FileReader(args[0])));
100: } catch (Exception e) {
101: e.printStackTrace();
102: }
103:
104: }
105:
106: /**
107: * @param input
108: * @return String
109: */
110: public static String transform(String input) {
111: String result = input;
112:
113: Pattern jspDeclarationPattern = Pattern.compile(
114: JSP_DECL_PATTERN, Pattern.DOTALL);
115: Matcher jspDeclarationMatcher = jspDeclarationPattern
116: .matcher(result);
117: if (!jspDeclarationMatcher.find()) {
118: //no JSP declarations, must be a JSP Document, not a page
119: return (preprocessJspDocument(result));
120: }
121:
122: result = performStaticInclude(STATIC_INCLUDE_PATTERN, input);
123:
124: result = toLowerHTML(result);
125:
126: Pattern jspTaglibPattern = Pattern.compile(JSP_TAGLIB_PATTERN,
127: Pattern.DOTALL);
128: Pattern openTagPattern = Pattern.compile(OPEN_TAG_PATTERN,
129: Pattern.DOTALL);
130: Pattern attributePattern = Pattern.compile(ATTRIBUTE_PATTERN,
131: Pattern.DOTALL);
132: Pattern prefixPattern = Pattern.compile(PREFIX_PATTERN,
133: Pattern.DOTALL);
134: Pattern uriPattern = Pattern.compile(URI_PATTERN,
135: Pattern.DOTALL);
136:
137: Hashtable declarationsTable = new Hashtable();
138: Matcher jspTaglibMatcher = jspTaglibPattern.matcher(result);
139: declarationsTable.put("xmlns:jsp", "jsp");
140: declarationsTable.put("xmlns:icefaces",
141: "http://www.icesoft.com/icefaces");
142:
143: while (jspTaglibMatcher.find()) {
144: String attributes = jspTaglibMatcher.group(1);
145: Matcher prefixMatcher = prefixPattern.matcher(attributes);
146: Matcher uriMatcher = uriPattern.matcher(attributes);
147: prefixMatcher.find();
148: uriMatcher.find();
149: String prefix = prefixMatcher.group(1);
150: String url = uriMatcher.group(1);
151: declarationsTable.put("xmlns:" + prefix, url);
152: }
153:
154: Matcher openTagMatcher = openTagPattern.matcher(result);
155: openTagMatcher.find();
156: String tag = openTagMatcher.group(1);
157: String attributes = openTagMatcher.group(2);
158:
159: Matcher attributeMatcher = attributePattern.matcher(attributes);
160: while (attributeMatcher.find()) {
161: String name = attributeMatcher.group(1);
162: String value = attributeMatcher.group(2);
163: declarationsTable.put(name, value);
164: }
165:
166: Enumeration declarations = declarationsTable.keys();
167: String namespaceDeclarations = "";
168: while (declarations.hasMoreElements()) {
169: String prefix = (String) declarations.nextElement();
170: String url = (String) declarationsTable.get(prefix);
171: namespaceDeclarations += prefix + "=\"" + url + "\" ";
172: }
173:
174: jspDeclarationMatcher = jspDeclarationPattern.matcher(result);
175: result = jspDeclarationMatcher.replaceAll("");
176:
177: //ensure single root tag for all JSPs as per bug 361
178: result = "<icefaces:root " + namespaceDeclarations + ">"
179: + result + "</icefaces:root>";
180:
181: Pattern jspIncludePattern = Pattern
182: .compile(JSP_INCLUDE_PATTERN);
183: Matcher jspIncludeMatcher = jspIncludePattern.matcher(result);
184: StringBuffer jspIncludeBuf = new StringBuffer();
185: while (jspIncludeMatcher.find()) {
186: String args = jspIncludeMatcher.group(1);
187: jspIncludeMatcher.appendReplacement(jspIncludeBuf,
188: "<icefaces:include" + args
189: + " isDynamic=\"#{true}\" />");
190: }
191: jspIncludeMatcher.appendTail(jspIncludeBuf);
192: result = jspIncludeBuf.toString();
193:
194: //Fix HTML
195: result = toSingletonTag(P_TAG_PATTERN, result);
196: result = toSingletonTag(LINK_TAG_PATTERN, result);
197: result = toSingletonTag(META_TAG_PATTERN, result);
198: result = toSingletonTag(IMG_TAG_PATTERN, result);
199: result = toSingletonTag(INPUT_TAG_PATTERN, result);
200:
201: Pattern brTagPattern = Pattern.compile(BR_TAG_PATTERN);
202: Matcher brTagMatcher = brTagPattern.matcher(result);
203: result = brTagMatcher.replaceAll("<br/>");
204:
205: Pattern hrTagPattern = Pattern.compile(HR_TAG_PATTERN);
206: Matcher hrTagMatcher = hrTagPattern.matcher(result);
207: result = hrTagMatcher.replaceAll("<hr/>");
208:
209: Pattern nbspEntityPattern = Pattern
210: .compile(NBSP_ENTITY_PATTERN);
211: Matcher nbspEntityMatcher = nbspEntityPattern.matcher(result);
212: // result = nbspEntityMatcher.replaceAll(" ");
213: result = nbspEntityMatcher.replaceAll("&nbsp");
214:
215: Pattern copyEntityPattern = Pattern
216: .compile(COPY_ENTITY_PATTERN);
217: Matcher copyEntityMatcher = copyEntityPattern.matcher(result);
218: result = copyEntityMatcher.replaceAll("©");
219:
220: Pattern docDeclPattern = Pattern.compile(DOC_DECL_PATTERN);
221: Matcher docDeclMatcher = docDeclPattern.matcher(result);
222: result = docDeclMatcher.replaceAll("");
223:
224: result = unescapeBackslash(result);
225:
226: return result;
227: }
228:
229: static String preprocessJspDocument(String input) {
230: String result = input;
231: result = performStaticInclude(STATIC_INCLUDE_DIRECTIVE_PATTERN,
232: input);
233: return result;
234: }
235:
236: /**
237: * @param input
238: * @return Reader
239: */
240: public static Reader preprocessJspDocument(Reader input) {
241: String inputString = getInputAsString(input);
242: String outputString = preprocessJspDocument(inputString);
243: return new StringReader(outputString);
244: }
245:
246: static String performStaticInclude(String includePatternString,
247: String input) {
248: String result = input;
249: boolean matchFound = true;
250:
251: Pattern staticIncludePattern = Pattern
252: .compile(includePatternString);
253: StringBuffer staticIncludeBuf;
254:
255: while (matchFound) {
256: matchFound = false;
257: staticIncludeBuf = new StringBuffer();
258: Matcher staticIncludeMatcher = staticIncludePattern
259: .matcher(result);
260: while (staticIncludeMatcher.find()) {
261: matchFound = true;
262: String args = staticIncludeMatcher.group(1);
263: try {
264: if (!args.startsWith("/")) {
265: String servletPath = FacesContext
266: .getCurrentInstance()
267: .getExternalContext()
268: .getRequestServletPath();
269: String workingDir = servletPath.substring(0,
270: servletPath.lastIndexOf("/"));
271: args = workingDir + "/" + args;
272: }
273: String includeString = getInputAsString(new InputStreamReader(
274: FacesContext.getCurrentInstance()
275: .getExternalContext().getResource(
276: args).openConnection()
277: .getInputStream()));
278: //Strip xml declarations from included files
279: Pattern xmlDeclPattern = Pattern
280: .compile(XML_DECL_PATTERN);
281: Matcher xmlDeclMatcher = xmlDeclPattern
282: .matcher(includeString);
283: includeString = xmlDeclMatcher.replaceAll("");
284:
285: staticIncludeMatcher.appendReplacement(
286: staticIncludeBuf,
287: escapeBackreference(includeString));
288: } catch (Exception e) {
289: //an error occurred, just remove the include
290: staticIncludeMatcher.appendReplacement(
291: staticIncludeBuf, "");
292: if (log.isErrorEnabled()) {
293: log.error("static include failed to include "
294: + args, e);
295: }
296: }
297: }
298: staticIncludeMatcher.appendTail(staticIncludeBuf);
299: result = staticIncludeBuf.toString();
300: }
301: return result;
302: }
303:
304: static String toSingletonTag(String pattern, String input) {
305: Pattern tagPattern = Pattern.compile(pattern, Pattern.DOTALL);
306: Matcher tagMatcher = tagPattern.matcher(input);
307: StringBuffer tagBuf = new StringBuffer();
308: while (tagMatcher.find()) {
309: String tagName = tagMatcher.group(1);
310: String attributes = tagMatcher.group(2);
311: tagMatcher.appendReplacement(tagBuf,
312: escapeBackreference("<" + tagName + attributes
313: + "/>"));
314: }
315: tagMatcher.appendTail(tagBuf);
316: return tagBuf.toString();
317: }
318:
319: /**
320: * @param input
321: * @return String
322: */
323: public static String toLowerHTML(String input) {
324: Pattern genericPattern = Pattern.compile(GENERIC_TAG_PATTERN);
325: Matcher genericMatcher = genericPattern.matcher(input);
326: StringBuffer inputBuffer = new StringBuffer();
327:
328: while (genericMatcher.find()) {
329: String openBracket = genericMatcher.group(1);
330: String slash = genericMatcher.group(2);
331: String tagName = genericMatcher.group(3);
332: String trailing = genericMatcher.group(4);
333: if (!"HTML".equals(tagName)) {
334: tagName = tagName.toLowerCase();
335: }
336: genericMatcher.appendReplacement(inputBuffer,
337: escapeBackreference(openBracket + slash + tagName
338: + trailing));
339: }
340: genericMatcher.appendTail(inputBuffer);
341:
342: return inputBuffer.toString();
343: }
344:
345: /**
346: * @param input
347: * @return String
348: */
349: public static String escapeBackreference(String input) {
350: String result = input;
351:
352: Pattern slashPattern = Pattern.compile("\\\\");
353: Matcher slashMatcher = slashPattern.matcher(result);
354: result = slashMatcher.replaceAll("\\\\\\\\");
355:
356: Pattern dollarPattern = Pattern.compile("\\$");
357: Matcher dollarMatcher = dollarPattern.matcher(result);
358: result = dollarMatcher.replaceAll("\\\\\\$");
359:
360: return result;
361: }
362:
363: /**
364: * @param input
365: * @return String
366: */
367: public static String unescapeBackslash(String input) {
368: String result = input;
369:
370: Pattern slashPattern = Pattern.compile("\\\\\\\\");
371: Matcher slashMatcher = slashPattern.matcher(result);
372: result = slashMatcher.replaceAll("\\\\");
373:
374: return result;
375:
376: }
377:
378: /**
379: * @param input
380: * @return String
381: */
382: public static Reader transform(Reader input) {
383: String inputString = getInputAsString(input);
384: String outputString = transform(inputString);
385:
386: if (log.isTraceEnabled()) {
387: log.trace(outputString);
388: }
389:
390: return new StringReader(outputString);
391: }
392:
393: /**
394: * @param context
395: * @param namespaceURL
396: * @return InputStream
397: * @throws IOException
398: */
399: public static InputStream getTldInputStream(
400: ExternalContext context, String namespaceURL)
401: throws IOException {
402:
403: // InputStream in = null;
404: JarFile jarFile = null;
405: String[] location = null;
406:
407: namespaceURL = String.valueOf(namespaceURL);
408:
409: // "jsp" is only a placeholder for standard JSP tags that are
410: // not supported, so just return null
411:
412: if ("jsp".equals(namespaceURL)) {
413: return null;
414: }
415:
416: if ("http://java.sun.com/JSP/Page".equals(namespaceURL)) {
417: return null;
418: }
419:
420: // TldLocationsCache may fail esp. with SecurityException on SUN app server
421: TldLocationsCache tldCache = new TldLocationsCache(context);
422: try {
423: location = tldCache.getLocation(namespaceURL);
424: } catch (Exception e) {
425: if (log.isDebugEnabled()) {
426: log.debug(e.getMessage(), e);
427: }
428: }
429:
430: if (null == location) {
431: if (namespaceURL.startsWith("/")
432: && namespaceURL.endsWith(".tld")) {
433: location = new String[] { namespaceURL };
434: }
435: }
436:
437: if (null == location) {
438: location = scanJars(context, namespaceURL);
439: }
440:
441: if (null == location) {
442: //look for myfaces-impl.jar
443: URL tagURL = JspPageToDocument.class.getClassLoader()
444: .getResource(MYFACES_TAG_CLASS);
445: if (null != tagURL) {
446: location = scanJar((JarURLConnection) tagURL
447: .openConnection(), namespaceURL);
448: }
449: }
450:
451: if (null == location) {
452: //look for myfaces-impl.jar
453: URL tagURL = JspPageToDocument.class.getClassLoader()
454: .getResource(SUN_TAG_CLASS);
455: if (null != tagURL) {
456:
457: // Bug 876
458: // Not all app servers (ie WebLogic 8.1) return
459: // an actual JarURLConnection.
460: URLConnection conn = tagURL.openConnection();
461: if (conn instanceof JarURLConnection) {
462: location = scanJar((JarURLConnection) conn,
463: namespaceURL);
464: }
465: }
466: }
467:
468: if (null == location) {
469: try {
470: // scan WebSphere dirs for JSF jars
471: String separator = System.getProperty("path.separator");
472: String wsDirs = System.getProperty("ws.ext.dirs");
473:
474: String[] dirs = null;
475: if (null != wsDirs) {
476: dirs = wsDirs.split(separator);
477: } else {
478: dirs = new String[] {};
479: }
480: Iterator theDirs = Arrays.asList(dirs).iterator();
481: while (theDirs.hasNext()) {
482: String dir = (String) theDirs.next();
483: try {
484: location = scanJars(dir, namespaceURL);
485: } catch (Exception e) {
486: //catch all possible exceptions including runtime exception
487: //so that the rest of jars still can be scanned.
488: }
489: if (null != location) {
490: break;
491: }
492: }
493: } catch (Exception e) {
494: if (log.isDebugEnabled()) {
495: log.debug(e.getMessage(), e);
496: }
497: }
498: }
499:
500: if (null == location) {
501: String msg = "Can't find TLD for location ["
502: + namespaceURL
503: + "]. JAR containing the TLD may not be in the classpath";
504: log.error(msg);
505: return null;
506: } else {
507: if (log.isTraceEnabled()) {
508: for (int i = 0; i < location.length; i++) {
509: log.trace("Found TLD location for " + namespaceURL
510: + " = " + location[i]);
511: }
512: }
513: }
514:
515: if (!location[0].endsWith("jar")) {
516: return context.getResourceAsStream(location[0]);
517: } else {
518: // Tag library is packaged in JAR file
519: URL jarFileUrl = new URL("jar:" + location[0] + "!/");
520: JarURLConnection conn = (JarURLConnection) jarFileUrl
521: .openConnection();
522: conn.setUseCaches(false);
523: conn.connect();
524: jarFile = conn.getJarFile();
525: ZipEntry jarEntry = jarFile.getEntry(location[1]);
526: return jarFile.getInputStream(jarEntry);
527: }
528:
529: }
530:
531: /**
532: * @param source
533: * @return InputStream
534: */
535: public static InputStream stripDoctype(InputStream source) {
536: String result = getInputAsString(new InputStreamReader(source));
537:
538: Pattern docDeclPattern = Pattern.compile(DOC_DECL_PATTERN);
539: Matcher docDeclMatcher = docDeclPattern.matcher(result);
540: result = docDeclMatcher.replaceAll("");
541:
542: return new StringBufferInputStream(result);
543: }
544:
545: static String getInputAsString(Reader in) {
546: char[] buf = new char[1024];
547: StringWriter out = new StringWriter();
548:
549: try {
550: int l = 1;
551: while (l > 0) {
552: l = in.read(buf);
553: if (l > 0) {
554: out.write(buf, 0, l);
555: }
556: }
557: } catch (IOException e) {
558: if (log.isWarnEnabled()) {
559: log.warn(e.getMessage(), e);
560: }
561: }
562: return out.toString();
563:
564: }
565:
566: /*
567: * Scan all jars under WEB-INF/lib/
568: */
569: static String[] scanJars(ExternalContext context,
570: String namespaceURL) {
571: try {
572: String[] location = null;
573: Iterator paths = context.getResourcePaths("/WEB-INF/lib/")
574: .iterator();
575: while (paths.hasNext()) {
576: String path = (String) paths.next();
577: if (!path.endsWith(".jar")) {
578: continue;
579: }
580: path = context.getResource(path).toString();
581:
582: JarURLConnection connection = (JarURLConnection) new URL(
583: "jar:" + path + "!/").openConnection();
584: location = scanJar(connection, namespaceURL);
585: if (null != location) {
586: return location;
587: }
588: }
589: } catch (Exception e) {
590: if (log.isWarnEnabled()) {
591: log.warn("Jar scanning under /WEB_INF/lib failed "
592: + e.getMessage(), e);
593: }
594: }
595:
596: return null;
597: }
598:
599: /*
600: * Scan all jars under the given path
601: */
602: static String[] scanJars(String dir, String namespaceURL)
603: throws IOException {
604: String[] location = null;
605: Iterator paths = Arrays.asList(new File(dir).listFiles())
606: .iterator();
607: while (paths.hasNext()) {
608: String path = ((File) paths.next()).getCanonicalPath();
609: if (!path.endsWith(".jar")) {
610: continue;
611: }
612:
613: URL url = new URL("jar:file:" + path + "!/");
614: JarURLConnection connection = (JarURLConnection) url
615: .openConnection();
616: location = scanJar(connection, namespaceURL);
617: if (null != location) {
618: return location;
619: }
620: }
621:
622: return null;
623: }
624:
625: static String[] scanJar(JarURLConnection conn, String namespaceURL)
626: throws IOException {
627: JarFile jarFile = null;
628: String resourcePath = conn.getJarFileURL().toString();
629:
630: if (log.isTraceEnabled()) {
631: log.trace("Fallback Scanning Jar " + resourcePath);
632: }
633:
634: jarFile = conn.getJarFile();
635: Enumeration entries = jarFile.entries();
636: while (entries.hasMoreElements()) {
637: try {
638: JarEntry entry = (JarEntry) entries.nextElement();
639: String name = entry.getName();
640: if (!name.startsWith("META-INF/")) {
641: continue;
642: }
643: if (!name.endsWith(".tld")) {
644: continue;
645: }
646: InputStream stream = jarFile.getInputStream(entry);
647: try {
648: String uri = getUriFromTld(resourcePath, stream);
649: if ((uri != null) && (uri.equals(namespaceURL))) {
650: return (new String[] { resourcePath, name });
651: }
652: } catch (JasperException jpe) {
653: if (log.isDebugEnabled()) {
654: log.debug(jpe.getMessage(), jpe);
655: }
656: } finally {
657: if (stream != null) {
658: stream.close();
659: }
660: }
661: } catch (Throwable t) {
662: if (log.isDebugEnabled()) {
663: log.debug(t.getMessage(), t);
664: }
665: }
666: }
667:
668: return null;
669: }
670:
671: /*
672: * Returns the value of the uri element of the given TLD, or null if the
673: * given TLD does not contain any such element.
674: */
675: static String getUriFromTld(String resourcePath, InputStream in)
676: throws JasperException {
677:
678: // Parse the tag library descriptor at the specified resource path
679: TreeNode tld = new ParserUtils().parseXMLDocument(resourcePath,
680: in);
681: TreeNode uri = tld.findChild("uri");
682: if (uri != null) {
683: String body = uri.getBody();
684: if (body != null) {
685: return body;
686: }
687: }
688:
689: return null;
690: }
691:
692: }
|