001: /*
002: * Please read the License Agreement and other supplemental license terms, if
003: * any, accompanying this software package before using any of the software
004: * provided. If you do not agree to the terms of the License Agreement or any
005: * other supplemental terms, please promptly destroy or return the software to
006: * Sun Microsystems, Inc.
007: *
008: * "CONFIDENTIAL AND PROPRIETARY" Copyright \251 2003 Sun Microsystems, Inc.
009: * All rights reserved.
010: */
011:
012: package com.sun.portal.providers.xml;
013:
014: import com.iplanet.am.util.FileLookup;
015: import com.iplanet.am.util.FileLookupException;
016:
017: import com.sun.portal.providers.urlscraper.URLScraperProvider;
018: import com.sun.portal.providers.ProviderException;
019: import com.sun.portal.providers.context.ProviderContext;
020: import com.sun.portal.providers.context.ProviderContextException;
021: import com.sun.portal.ubt.UBTLogManager;
022: import com.sun.portal.log.common.PortalLogger;
023:
024: import javax.xml.transform.Transformer;
025: import javax.xml.transform.TransformerFactory;
026: import javax.xml.transform.TransformerException;
027: import javax.xml.transform.TransformerConfigurationException;
028: import javax.xml.transform.dom.DOMSource;
029:
030: import javax.xml.transform.stream.StreamSource;
031: import javax.xml.transform.stream.StreamResult;
032: import javax.xml.parsers.DocumentBuilder;
033: import javax.xml.parsers.DocumentBuilderFactory;
034: import org.xml.sax.InputSource;
035: import org.w3c.dom.Document;
036:
037: import java.io.File;
038: import java.io.IOException;
039: import java.io.FileNotFoundException;
040: import java.io.PrintWriter;
041: import java.io.BufferedWriter;
042: import java.io.BufferedReader;
043: import java.io.StringReader;
044: import java.io.FileReader;
045: import java.io.StringWriter;
046: import java.io.OutputStreamWriter;
047: import java.io.UnsupportedEncodingException;
048:
049: import java.util.Map;
050: import java.util.HashMap;
051: import java.util.ResourceBundle;
052: import java.util.StringTokenizer;
053: import java.util.logging.Logger;
054: import java.util.logging.Level;
055: import java.util.logging.LogRecord;
056:
057: import java.net.MalformedURLException;
058: import java.net.URL;
059:
060: import javax.servlet.http.HttpServletRequest;
061: import javax.servlet.http.HttpServletResponse;
062:
063: import org.w3c.dom.Document;
064:
065: /**
066: * <P> A XML Provider is used to convert and display the XML file according to
067: * the specified style sheet.
068: *
069: * <P> XMLProvider is an extension of the existing URLScraperProvider. It uses
070: * the URLScraperProvider to fetch the XML contents when the url is of type
071: * <b> http://url or https://url </b> and if the url is of type <b> file:////
072: * </b>, the XMLProvider manually reads the contents of the specified file .
073: *
074: * <P> The XMLProvider can transform generic XML content to specific markups
075: * using an XSLT engine. It allows creating multiple markup channels from a
076: * single XML source (local or over http/https)
077: *
078: * <P> XML Provider requires two arguments to do the transformation.
079: * <UL>
080: * <LI> The XML file that needs to the transformed
081: * <LI> The stylesheet to be used to do the transformation.
082: * </UL>
083: * <P> The XMLProvider-based channel has the following configurable attributes:
084: * <UL>
085: * <LI> url - the XML attribute used to store the path to the XML source
086: * <LI> xslFileName - the XML attribute used to store the path to the XSL
087: * style sheet.
088: * </UL>
089: *
090: * <P> The path to the XML content can be specified as HTTP,HTTPs or file URL.
091: * If the path is HTTP or HTTPs the provider uses URLScraper provider to fetch
092: * the contents. If the path is a file URL, the provider reads the file into a
093: * StringBuffer.
094: *
095: * <P> The value for the XSL filename to use for transformation is stored as
096: * a value of the string attribute as indicated above. The user can either
097: * specify the complete path (including the file name) or can specify just the
098: * file name in which case it will be picked from the default directory. While
099: * specifying a value for this property there should not be any use of the
100: * protocols like "file://" or "http://" or "https://"
101: *
102: *
103: * <P>If both the XML file and the XSL file are present, the XMLProvider does
104: * the transformation using the XSLT engine. The generated contents are
105: * displayed in the channel. In order to do the conversion the XMLProvider uses
106: * the JAXP1.1 jar files.
107: * <P> NOTE: <CODE>getEdit()</CODE> and <CODE>processEdit()</CODE> methods
108: * are not implemented in the XMLProvider.
109: ***/
110:
111: public class XMLProvider extends URLScraperProvider {
112:
113: private static Logger logger = PortalLogger
114: .getLogger(XMLProvider.class);
115: private ResourceBundle bundle = null;
116: private static Map xslTransformerMap = new HashMap(10);
117: private static final String EMPTY_XML_URL = "urlnotspecified";
118: private static final String EMPTY_XSL_FILENAME = "emptyXslFileName";
119: private static final String XML_FETCH_ERROR = "xmlFetchError";
120: private static final String XML_TRANSFOMING_ERROR = "xmlTransformingError";
121:
122: private static final String OLD_TRANSFORMER_FACTORY = "org.apache.xalan.xsltc.trax.TransformerFactoryImpl";
123: private static final String DEFAULT_TRANSFORMER_FACTORY = "com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl";
124:
125: /**
126: * <P> Gets the XSL file object.
127: * @returns the XSL file object
128: * @exception ProviderException if the xsl url specified in the storage
129: * is not a valid url.
130: */
131: protected File getXSL(String xslFileName) throws ProviderException {
132:
133: File xslFile = null;
134:
135: logger.log(Level.FINEST, "PSCR_CSPPX0001", xslFileName);
136: if ((xslFileName == null) || (xslFileName.equals(""))) {
137: return null;
138: } else
139: logger.log(Level.FINEST, "PSCR_CSPPX0001", xslFileName);
140:
141: /*********************************************************
142: * This block of code is not portable to win2k and is being rewritten
143: // Check if it is an URL
144: if (xslFileName.indexOf(':') == -1){
145: // So it's either abs. pathname or just xsl filename.
146: // In the 2nd case we will use the FileLookup API to
147: // get the xsl file.
148:
149: if((xslFileName.charAt(0)) == '/') {
150: xslFile = super.getFile(xslFileName);
151: } else {
152: // Now get the xsl file
153: try {
154: xslFile = getProviderContext().getTemplatePath(getName(), xslFileName);
155: } catch (ProviderContextException pce ) {
156: throw new ProviderException( "XMLProvider.getXSL(): ", pce );
157: }
158: if ((xslFile != null) && (getProviderContext().isDebugMessageEnabled())) {
159: getProviderContext().debugMessage("XMLProvider.getXSL: the file:" + xslFile.getName());
160: }
161: }
162: return xslFile;
163: } else {
164: throw new ProviderException("XMLProvider.getXSL(): Unsupported URL type ");
165: }
166: *********************************************************/
167:
168: //Assume abs. pathname
169: xslFile = super .getFile(xslFileName);
170: if (xslFile == null) {
171: //Now assume that it is just xsl filename.
172: //we will use the FileLookup API to
173: // get the xsl file
174: try {
175: xslFile = getProviderContext().getTemplatePath(
176: getName(), xslFileName);
177: } catch (ProviderContextException pce) {
178: throw new ProviderException("XMLProvider.getXSL(): ",
179: pce);
180: }
181: }
182: if ((xslFile != null)) {
183: logger.log(Level.FINEST, "PSCR_CSPPX0003", xslFile
184: .getName());
185: }
186: if (xslFile == null) {
187: try {
188: URL xslFileUrl = new URL(xslFileName);
189: throw new ProviderException(
190: "XMLProvider.getXSL(): Unsupported URL type :"
191: + xslFileUrl.getProtocol());
192: } catch (MalformedURLException mex) {
193: }
194: }
195: return xslFile;
196: }
197:
198: /**
199: * <P><P>Gets the XML file as a string buffer.
200: *
201: * <P>This method calls the <code>getHttpContent</code> in class
202: * URLScraperProvider to get the XML file content as a StringBuffer
203: * if the XML URL specified is a http or https url.
204: *
205: * @param req An HttpServletRequest that contains information related
206: * to this request for content.
207: * @param res An HttpServletResponse that allows the provider to
208: * influence the overall response for the desktop page
209: * (besides generating the content).
210: * @return the XML file contents as a buffer
211: * @exception ProviderException if the xml URL specified in the storage
212: * is not a valid URL.
213: * @see com.sun.portal.providers.urlscraper.URLScraperProvider#getHttpContent
214: */
215: protected StringBuffer getXML(HttpServletRequest req,
216: HttpServletResponse res) throws ProviderException {
217:
218: String xmlURL = getURL();
219:
220: StringBuffer content = new StringBuffer();
221: String proto = null;
222: try {
223: proto = xmlURL.substring(0, xmlURL.indexOf(':'));
224: } catch (IndexOutOfBoundsException iobe) {
225: }
226:
227: if (proto != null && proto.equalsIgnoreCase("file")) {
228: String PathName = null;
229: try {
230: PathName = xmlURL.substring(xmlURL.indexOf('/'));
231: } catch (IndexOutOfBoundsException iobe) {
232: if (logger.isLoggable(Level.FINE)) {
233: LogRecord record = new LogRecord(Level.FINE,
234: "PSCR_CSPPX0004");
235: record.setLoggerName(logger.getName());
236: record.setParameters(new Object[] { xmlURL });
237: record.setThrown(iobe);
238: logger.log(record);
239: }
240: throw new ProviderException(
241: "XMLProvider.getXML():Getting XML Content failed. "
242: + " Unsupported URL type : " + xmlURL,
243: iobe);
244: }
245: try {
246: content = getFileAsBuffer(PathName);
247: } catch (UnsupportedEncodingException ue) {
248: if (logger.isLoggable(Level.FINE)) {
249: LogRecord record = new LogRecord(Level.FINE,
250: "PSCR_CSPPX0005");
251: record.setLoggerName(logger.getName());
252: record.setParameters(new Object[] { PathName });
253: record.setThrown(ue);
254: logger.log(record);
255: }
256: throw new ProviderException(
257: "XMLProvider.getXML():Getting XML Content failed. "
258: + " UnsupportedEncoding specified : "
259: + PathName, ue);
260: } catch (IOException ioe) {
261: if (logger.isLoggable(Level.FINE)) {
262: LogRecord record = new LogRecord(Level.FINE,
263: "PSCR_CSPPX0006");
264: record.setLoggerName(logger.getName());
265: record.setParameters(new Object[] { PathName });
266: record.setThrown(ioe);
267: logger.log(record);
268: }
269: throw new ProviderException(
270: "XMLProvider.getXML():Getting XML Content failed. "
271: + " IOException received : " + PathName,
272: ioe);
273: }
274: } else {
275:
276: try {
277: content = UBTLogManager.getInstance().isUBTEnabled() ? getHttpContent(
278: req, res, xmlURL, true)
279: : getHttpContent(req, res, xmlURL, false);
280: } catch (InterruptedException ie) {
281: if (logger.isLoggable(Level.FINE)) {
282: LogRecord record = new LogRecord(Level.FINE,
283: "PSCR_CSPPX0007");
284: record.setLoggerName(logger.getName());
285: record.setParameters(new Object[] { xmlURL });
286: record.setThrown(ie);
287: logger.log(record);
288: }
289: throw new ProviderException(
290: "XMLProvider.getXML():Getting XML Content failed"
291: + " fetcher did not finish : " + xmlURL,
292: ie);
293:
294: } catch (MalformedURLException mue) {
295: if (logger.isLoggable(Level.FINE)) {
296: LogRecord record = new LogRecord(Level.FINE,
297: "PSCR_CSPPX0008");
298: record.setLoggerName(logger.getName());
299: record.setParameters(new Object[] { xmlURL });
300: record.setThrown(mue);
301: logger.log(record);
302: }
303: throw new ProviderException(
304: "XMLProvider.getXML():Getting XML Content failed"
305: + " Malformed URL : " + xmlURL, mue);
306: }
307: }
308: return content;
309: }
310:
311: /*
312: * <P> Method that gets the XML File and the XSL file and which does the
313: * actual transformation
314: *
315: * @param XMLFile - XML file to read
316: * @param xslFile - XSL stylesheet for conversion
317: *
318: * @return StringBuffer containing the transformed XML data
319: *
320: */
321:
322: private synchronized StringBuffer doTransform(StringBuffer XMLFile,
323: String xslFileName) throws ProviderException {
324:
325: StringBuffer XformedContent = null;
326: Transformer xslTransformer = null;
327:
328: try {
329: xslTransformer = getXslTransformer(xslFileName);
330: } catch (TransformerConfigurationException tce) {
331: if (logger.isLoggable(Level.INFO)) {
332: LogRecord record = new LogRecord(Level.FINE,
333: "PSCR_CSPPX0009");
334: record.setLoggerName(logger.getName());
335: record.setParameters(new Object[] { xslFileName });
336: record.setThrown(tce);
337: logger.log(record);
338: }
339: } catch (TransformerException te) {
340: if (logger.isLoggable(Level.INFO)) {
341: LogRecord record = new LogRecord(Level.FINE,
342: "PSCR_CSPPX0009");
343: record.setLoggerName(logger.getName());
344: record.setParameters(new Object[] { xslFileName });
345: record.setThrown(te);
346: logger.log(record);
347: }
348: }
349:
350: if (xslTransformer != null) {
351:
352: InputSource xmlSource = new InputSource(new StringReader(
353: XMLFile.toString()));
354:
355: StringWriter contentWriter = new StringWriter();
356: StreamResult contentResult = new StreamResult(contentWriter);
357:
358: try {
359: synchronized (xslTransformer) {
360: DocumentBuilder builder = (DocumentBuilderFactory
361: .newInstance()).newDocumentBuilder();
362: Document dom = builder.parse(xmlSource);
363: xslTransformer.transform(new DOMSource(dom),
364: contentResult);
365: }
366: } catch (TransformerException te) {
367: if (logger.isLoggable(Level.SEVERE)) {
368: LogRecord record = new LogRecord(Level.FINE,
369: "PSCR_CSPPX0010");
370: record.setLoggerName(logger.getName());
371: record.setParameters(new Object[] { XMLFile,
372: xslFileName });
373: record.setThrown(te);
374: logger.log(record);
375: }
376: throw new ProviderException(
377: "XMLProvider.doTransform():"
378: + "Error in transforming xml : "
379: + XMLFile + " using XSL file : "
380: + xslFileName);
381: } catch (Exception e) { //making it generic for TransformerException,ParseConfigurationException,SAXException
382: if (logger.isLoggable(Level.SEVERE)) {
383: LogRecord record = new LogRecord(Level.FINE,
384: "PSCR_CSPPX0010");
385: record.setParameters(new Object[] { XMLFile,
386: xslFileName });
387: record.setThrown(e);
388: logger.log(record);
389: }
390: throw new ProviderException(
391: "XMLProvider.doTransform():"
392: + "Error in transforming xml : "
393: + XMLFile + " using XSL file : "
394: + xslFileName);
395: }
396: XformedContent = contentWriter.getBuffer();
397: } else {
398: logger.log(Level.FINE, "PSCR_CSPPX0011", new Object[] {
399: XMLFile, xslFileName });
400: throw new ProviderException(
401: "XMLProvider.doTransform():"
402: + " Error in getting XSL transformer to transform xml : "
403: + XMLFile + " using XSL file : "
404: + xslFileName);
405: }
406: return XformedContent;
407: }
408:
409: /**
410: * <P><P>Gets and displays the XML file contents after converting the
411: * xml file according to the specified XSL stylesheet
412: *
413: * @param req An HttpServletRequest that contains information related
414: * to this request for content.
415: * @param res An HttpServletResponse that allows the provider to
416: * influence the overall response for the desktop page
417: * (besides generating the content).
418: * @return Transformed XML contents.
419: * @exception ProviderException
420: */
421: public StringBuffer getContent(HttpServletRequest req,
422: HttpServletResponse res) throws ProviderException {
423: StringBuffer content = new StringBuffer();
424: bundle = getResourceBundle();
425:
426: String xmlURL = getURL();
427:
428: if ((xmlURL == null) || (xmlURL.equals(""))) {
429: content = content.append(bundle.getString(EMPTY_XML_URL));
430: return content;
431: }
432:
433: String xslFileName = getStringProperty("xslFileName");
434: File xslFile = null;
435: String xslFilePath = null;
436: try {
437: xslFile = getXSL(xslFileName);
438: } catch (ProviderException xe) {
439: if (logger.isLoggable(Level.INFO)) {
440: LogRecord record = new LogRecord(Level.FINE,
441: "PSCR_CSPPX0012");
442: record.setLoggerName(logger.getName());
443: record.setParameters(new Object[] { xslFileName });
444: record.setThrown(xe);
445: logger.log(record);
446: }
447:
448: }
449: if (xslFile != null) {
450: xslFilePath = xslFile.getPath();
451: }
452:
453: if ((xslFilePath == null) || (xslFilePath.equals(""))) {
454: content = content.append(bundle
455: .getString(EMPTY_XSL_FILENAME));
456: return content;
457: }
458:
459: // Get the XML File
460: StringBuffer Xmlfile = new StringBuffer();
461: try {
462: Xmlfile = getXML(req, res);
463: } catch (ProviderException xe) {
464: logger.log(Level.INFO, "PSCR_CSPPX0013", xe);
465: content = content.append(bundle.getString(XML_FETCH_ERROR));
466: return content;
467: }
468: if (Xmlfile == null) {
469: logger.log(Level.INFO, "PSCR_CSPPX0013");
470: content = content.append(bundle.getString(XML_FETCH_ERROR));
471: return content;
472: } else {
473: logger.log(Level.FINER, "PSCR_CSPPX0014");
474: }
475: try {
476: content = doTransform(Xmlfile, xslFilePath);
477: } catch (ProviderException p) {
478: if (logger.isLoggable(Level.INFO)) {
479: LogRecord record = new LogRecord(Level.FINE,
480: "PSCR_CSPPX0015");
481: record.setLoggerName(logger.getName());
482: record.setParameters(new Object[] { xslFileName });
483: record.setThrown(p);
484: logger.log(record);
485: }
486: content = content.append(bundle
487: .getString(XML_TRANSFOMING_ERROR));
488: }
489: return content;
490: }
491:
492: /**
493: * Gets the charset from content
494: *
495: * This method determines the charset from the
496: * XML Header
497: * @param contentBytes Bytes from the scraped content
498: * @return String charset or null if charset cannot be determined
499: */
500: protected String getContentEncodingFromContentBytes(
501: byte[] contentBytes) {
502: String charset = null;
503: /* The character encoding info was not found in the inputEncoding
504: * property. We have to parse through the content portion to
505: * figure it out. It may be specified in the xml header
506: * as the following;
507: *
508: * <?xml version="1.0" encoding="utf-8" standalone="no"?>
509: * ...
510: */
511: int count = 1000;
512: int len = contentBytes.length;
513: if (len < count) {
514: count = len;
515: }
516: String contentString = new String(contentBytes, 0, count);
517: String str = contentString.toLowerCase();
518: int start = str.indexOf("<?xml");
519: if (start != -1) {
520: int end = str.indexOf(">", start);
521: if (end != -1) {
522: String headerstr = contentString.substring(start, end);
523: String header = headerstr.toLowerCase();
524: int encoding = header.indexOf("encoding=");
525: if (encoding != -1) {
526: int startencoding = header.indexOf("\"", encoding);
527: int endencoding = header.indexOf("\"",
528: startencoding + 1);
529: charset = headerstr.substring(startencoding + 1,
530: endencoding);
531: }
532: }
533: }
534: return charset;
535: }
536:
537: private Transformer getXslTransformer(String xslFileName)
538: throws TransformerConfigurationException,
539: TransformerException {
540:
541: ProviderContext pc = getProviderContext();
542:
543: String transformerFactoryClassName = DEFAULT_TRANSFORMER_FACTORY;
544:
545: try {
546: if (existsStringProperty("transformerFactoryClassName")) {
547: transformerFactoryClassName = getStringProperty("transformerFactoryClassName");
548: if (transformerFactoryClassName == null
549: || transformerFactoryClassName.equals("")) {
550: transformerFactoryClassName = DEFAULT_TRANSFORMER_FACTORY;
551: }
552: }
553: } catch (ProviderException pe) {
554: logger.log(Level.INFO, "PSCR_CSPPX0016", pe);
555: }
556: // For backward compatibility with JES2
557: if (!isTransformerAvailable(transformerFactoryClassName)) {
558: transformerFactoryClassName = OLD_TRANSFORMER_FACTORY;
559: }
560: StringBuffer sb = new StringBuffer(xslFileName);
561: sb.append("|");
562: sb.append(transformerFactoryClassName);
563: String cacheKey = sb.toString();
564:
565: // check if the Transformer object is already in cache
566: CacheEntry cacheEntry = (CacheEntry) xslTransformerMap
567: .get(cacheKey);
568: if (cacheEntry == null) {
569: // there is no cached Transformer for this xslFileName
570: synchronized (XMLProvider.class) {
571: cacheEntry = (CacheEntry) xslTransformerMap
572: .get(cacheKey);
573: if (cacheEntry == null) {
574: File xslFile = null;
575: cacheEntry = new CacheEntry();
576: try {
577: xslFile = getXSL(xslFileName);
578: if (xslFile == null) {
579: logger.log(Level.INFO, "PSCR_CSPPX0017",
580: xslFileName);
581: return null;
582: }
583: } catch (ProviderException xpe) {
584: if (logger.isLoggable(Level.INFO)) {
585: LogRecord record = new LogRecord(
586: Level.FINE, "PSCR_CSPPX0018");
587: record.setLoggerName(logger.getName());
588: record
589: .setParameters(new Object[] { xslFileName });
590: record.setThrown(xpe);
591: logger.log(record);
592: }
593: return null;
594: }
595: // set xslFile in CachedEntry and initialize lastModTime to -1
596: // for this newly created CachedEntry
597: cacheEntry.xslFile = xslFile;
598: cacheEntry.lastModTime = -1;
599: xslTransformerMap.put(cacheKey, cacheEntry);
600: }
601: }
602: }
603:
604: if ((cacheEntry.lastModTime == -1)
605: || (cacheEntry.lastModTime < cacheEntry.xslFile
606: .lastModified())) {
607: // 1) the cached Transformer object is newly created OR
608: // 2) the cached Transformer object is outdated
609: TransformerFactory factory = null;
610:
611: try {
612: Class transformerFactoryClass = Class
613: .forName(transformerFactoryClassName);
614: factory = (TransformerFactory) transformerFactoryClass
615: .newInstance();
616: } catch (ClassCastException cce) {
617: logger.log(Level.INFO, "PSCR_CSPPX0002", cce);
618: return null;
619: } catch (ClassNotFoundException cnfe) {
620: logger.log(Level.INFO, "PSCR_CSPPX0002", cnfe);
621: return null;
622: } catch (InstantiationException ie) {
623: logger.log(Level.INFO, "PSCR_CSPPX0002", ie);
624: return null;
625: } catch (IllegalAccessException iae) {
626: logger.log(Level.INFO, "PSCR_CSPPX0002", iae);
627: return null;
628: }
629:
630: cacheEntry.xslTransformer = factory
631: .newTransformer(new StreamSource(cacheEntry.xslFile));
632: // reset the lastModTime of the CachedEntry
633: cacheEntry.lastModTime = cacheEntry.xslFile.lastModified();
634: }
635:
636: return cacheEntry.xslTransformer;
637: }
638:
639: /**
640: * This Inner Class representing an entry in the xslTransformerMap Cache
641: */
642: class CacheEntry {
643:
644: private Transformer xslTransformer;
645: private File xslFile;
646: private long lastModTime;
647:
648: /**
649: * Constructor for an class representing an entry in the cache
650: */
651: public CacheEntry() {
652: }
653: }
654:
655: private boolean isTransformerAvailable(String transformerName) {
656: boolean result = true;
657: try {
658: Class.forName(transformerName);
659: } catch (ClassNotFoundException e) {
660: result = false;
661: }
662: return result;
663: }
664:
665: }
|