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  "License");
7  * you may not use this file except in compliance with the License.
8  * 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, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 /*
19  * $Id: ApplyXSLT.java 470245 2006-11-02 06:34:33Z minchau $
20  */
21 package servlet;
22 
23 import java.io.*;
24 import java.util.*;
25 import javax.servlet.*;
26 import javax.servlet.http.*;
27 import java.net.URL;
28 import java.net.MalformedURLException;
29 import java.net.URLConnection;
30 import javax.xml.transform.OutputKeys;
31 
32 import org.apache.xalan.templates.Constants;
33 import org.apache.xalan.templates.StylesheetRoot;
34 // SAX2 Imports
35 import org.xml.sax.ContentHandler;
36 import org.xml.sax.SAXException;
37 import org.xml.sax.XMLReader;
38 import org.xml.sax.Locator;
39 import org.xml.sax.helpers.XMLReaderFactory;
40 import org.xml.sax.ext.DeclHandler;
41 import org.xml.sax.ext.LexicalHandler;
42 import org.xml.sax.SAXNotRecognizedException;
43 import org.xml.sax.SAXNotSupportedException;
44 
45 import org.w3c.dom.*;
46 import javax.xml.transform.*;
47 import javax.xml.transform.stream.*;
48 import org.apache.xalan.transformer.TransformerImpl;
49 import org.apache.xpath.objects.XObject;
50 import org.apache.xpath.objects.XString;
51 import org.apache.xalan.processor.*;
52 
53 import javax.xml.parsers.DocumentBuilder;
54 import javax.xml.parsers.DocumentBuilderFactory;
55 
56 import org.xml.sax.XMLReader;
57 import org.xml.sax.helpers.XMLReaderFactory;
58 import org.xml.sax.helpers.XMLFilterImpl;
59 
60 /*****************************************************************************************************
61  *
62  * ApplyXSLT supplies the basic
63  * functions for transforming XML data using XSL stylesheets.
64  *
65  * @author Spencer Shepard (sshepard@us.ibm.com)
66  * @author R. Adam King (rak@us.ibm.com)
67  * @author Tom Rowe (trowe@us.ibm.com)
68  * @author Don Leslie (donald_leslie@lotus.com)
69  *
70  *****************************************************************************************************/
71 
72 public class ApplyXSLT extends HttpServlet
73 {
74 
75   /**
76    * Operational parameters for this class.
77    * <p>Request-time values override init-time values which override class defaults.</p>
78    * @see #init
79    * @serial
80    */
81   protected ApplyXSLTProperties ourDefaultParameters = null;
82 
83   /**
84    * String representing the end of line characters for the System.
85    */
86   public final static String EOL = System.getProperty("line.separator");
87 
88   /**
89    * String representing the file separator characters for the System.
90    */
91   public final static String FS = System.getProperty("file.separator");
92 
93    /**
94    * String representing the current directory for properties files. See init().
95    */
96   public final static String ROOT = System.getProperty("server.root");
97   public static String CURRENTDIR;
98 
99   /**
100    * Initialize operational parameters from the configuration.
101    * @param config Configuration
102    * @exception ServletException Never thrown
103    */
init(ServletConfig config)104   public void init(ServletConfig config)
105     throws ServletException
106   {
107     super.init(config);
108     // If the server.root property --see above-- is null, use current working directory
109     // as default location for media.properties.
110     if (ROOT != null){
111       CURRENTDIR= getServletContext().getRealPath("/WEB-INF/classes/servlet/") + FS;
112 	  System.out.println ( CURRENTDIR);}
113     else
114       CURRENTDIR = System.getProperty("user.dir")+ FS;
115 
116 	setDefaultParameters(config);
117 
118     setMediaProps(config.getInitParameter("mediaURL"));
119   }
120 
121  /**
122    * Sets the default parameters for the servlet from the configuration.
123    * Also sets required system properties until we figure out why servlet
124    * sometimess fails to read properties from properties files.
125    * @param config Configuration
126    */
setDefaultParameters(ServletConfig config)127   protected void setDefaultParameters(ServletConfig config)
128   {
129     ourDefaultParameters = new DefaultApplyXSLTProperties(config);
130   }
131 
132   /**
133    *	Loads the media properties file specified by the given string.
134    * @param mediaURLstring Location of the media properties file.  Can be either a full URL or a path relative
135    * to the System's server.root /servlets directory.  If this parameter is null,
136    * server.root/servlets/media.properties will be used.
137    * @see ApplyXSL#CURRENTDIR
138    */
setMediaProps(String mediaURLstring)139   protected void setMediaProps(String mediaURLstring)
140   {
141     if (mediaURLstring != null)
142     {
143       URL url = null;
144       try
145       {
146         url = new URL(mediaURLstring);
147       }
148       catch (MalformedURLException mue1)
149       {
150         try
151         {
152           url = new URL("file", "", CURRENTDIR + mediaURLstring);
153         }
154         catch (MalformedURLException mue2)
155         {
156           writeLog("Unable to find the media properties file based on parameter 'mediaURL' = "
157                    + mediaURLstring, HttpServletResponse.SC_ACCEPTED, mue2);
158           url = null;
159         }
160       }
161       if (url != null)
162       {
163         try
164         {
165           ourMediaProps = new OrderedProps(url.openStream());
166         }
167         catch (IOException ioe1)
168         {
169           writeLog("Exception occurred while opening media properties file: " + mediaURLstring +
170                    ".  Media table may be invalid.", HttpServletResponse.SC_ACCEPTED, ioe1);
171         }
172       }
173     }
174     else
175     {
176       String defaultProp = CURRENTDIR + "media.properties";
177       try
178       {
179         ourMediaProps = new OrderedProps(new FileInputStream(defaultProp));
180       }
181       catch (IOException ioe2)
182       {
183         writeLog("Default media properties file " + defaultProp + " not found.",
184                  HttpServletResponse.SC_ACCEPTED, ioe2);
185       }
186     }
187   }
188 
getMedia(HttpServletRequest request)189   public String getMedia(HttpServletRequest request)
190   {
191     return ourMediaProps.getValue(request.getHeader(HEADER_NAME));
192   }
193 
194   // doPost removed for security reasons due to the possibility of sending
195   // unsecure XML and XSL XSLTInputSources through the request input stream
196 
197   /**
198    * HTTP Get method passed on to process().
199    * @param request The request
200    * @param response The response
201    * @see #process
202    * @exception ServletException Never thrown
203    * @exception IOException Never thrown
204    */
doGet(HttpServletRequest request, HttpServletResponse response)205   public void doGet (HttpServletRequest request,
206                      HttpServletResponse response)
207     throws ServletException, IOException
208   {
209     try
210     {
211       TransformerFactory tFactory = TransformerFactory.newInstance();
212       process(tFactory, request, response);
213     }
214     catch (Exception e)
215     {
216     }
217   }
218 
219   /**
220    * Coordinates applying an XSL stylesheet to XML data using operational parameters.
221    * <p>If successfully applied, the result tree will be streamed to the response object
222    * and the content type set according to the XSL stylesheet's &lt;xsl:output> element(s).</p>
223    * <p>If there is a problem in parsing the XML/XSL or if there is a problem in applying
224    * the XSL to the XML, an exception will be streamed to the response object.  The detail
225    * of the information returned in the response object will depend on whether we're
226    * running in debug mode or not.</p>
227    * @param processor implementation of TRaX processor
228    * @param request  May contain information relevant to creating XML and XSL XSLTInputSource's
229    * @param response Where to write the transformation result
230    * @see #getDocument
231    * @see #getStylesheet
232    * @see #getContentType
233    * @see #displayException
234    * @see #setStylesheetParams
235    * @exception ServletException Never thrown
236    * @exception IOException Never thrown
237    */
238 
process(TransformerFactory tFactory, HttpServletRequest request, HttpServletResponse response)239   public void process(TransformerFactory tFactory,
240 					  HttpServletRequest request,
241                       HttpServletResponse response)
242     throws ServletException, IOException, SAXException
243   {
244     boolean debug = ourDefaultParameters.isDebug(request);
245 
246     long time = 0;
247     if (debug)
248       time = System.currentTimeMillis();
249 
250     // Listener to be used for all reporting
251     ApplyXSLTListener listener = new ApplyXSLTListener();
252 	listener.out.println("debug is " + debug);
253 
254     StreamSource xmlSource = null;
255 	StreamSource xslSource = null;
256     try
257     {
258       if ((xmlSource = getDocument(request, listener)) == null)
259         throw new ApplyXSLTException("getDocument() returned null",
260                                      new NullPointerException(),
261                                      response.SC_NOT_FOUND);
262     }
263     catch (ApplyXSLTException axe)
264     {
265       axe.appendMessage(EOL + "getDocument() resulted in ApplyXSLTException" + EOL
266                         + listener.getMessage());
267       if (debug) writeLog(axe);
268       displayException(response, axe, debug);
269       xmlSource = null;
270     }
271     // creating XSL Stylesheet
272     if (xmlSource != null)
273 	{
274       try
275       {
276 	    if ((xslSource = getStylesheet(tFactory, request, xmlSource, listener)) == null)
277 		{
278           throw new ApplyXSLTException("getStylesheet() returned null",
279                                        new NullPointerException(),
280                                        response.SC_NOT_FOUND);
281         }
282         // For time being, must "reset" xmlSource after extracting stylesheet PI
283 		xmlSource = getDocument(request, listener);
284       }
285       catch (ApplyXSLTException axe)
286       {
287         axe.appendMessage(EOL + "getStylesheet() resulted in ApplyXSLTException" + EOL
288                           + listener.getMessage());
289         if (debug) writeLog(axe);
290         displayException(response, axe, debug);
291         xslSource = null;
292       }
293     // perform Transformation
294 
295     if ((xmlSource != null) && (xslSource != null))
296     {
297 	  try
298 	  {
299         listener.out.println("Performing transformation...");
300 
301         Templates templates = tFactory.newTemplates(xslSource);
302         Transformer transformer = templates.newTransformer();
303         {
304           try
305           {
306             String contentType = null;
307 			      contentType = getContentType(templates);
308             if (contentType != null);
309               response.setContentType(contentType);
310 
311 			      if (transformer instanceof TransformerImpl)
312 			      {
313 			        TransformerImpl transformerImpl = (TransformerImpl)transformer;
314               transformerImpl.setQuietConflictWarnings(ourDefaultParameters.isNoCW(request));
315 			      }
316 
317 			      setStylesheetParams(transformer, request);
318 	          transformer.transform(xmlSource, new StreamResult(response.getOutputStream()));
319 
320 			      if (debug)
321               writeLog(listener.getMessage(), response.SC_OK);
322           }
323           catch (Exception exc)
324           {
325             ApplyXSLTException axe = new ApplyXSLTException
326 				                     ("Exception occurred during Transformation:"
327                                           + EOL + listener.getMessage() + EOL
328                                           + exc.getMessage(),
329 									              exc,
330                                 response.SC_INTERNAL_SERVER_ERROR);
331             if (debug) writeLog(axe);
332             displayException(response, axe, debug);
333           }
334           finally
335           {
336             // transformer.reset();
337           } // end of try ... catch ... finally
338 		}
339 	  }
340       catch (/*org.xml.sax.SAX*/Exception saxExc)
341       {
342         ApplyXSLTException axe = new ApplyXSLTException
343 			                     ("Exception occurred during ctor/Transformation:"
344                                              + EOL + listener.getMessage() + EOL
345                                              + saxExc.getMessage(),
346 			                					  saxExc,
347                                   response.SC_INTERNAL_SERVER_ERROR);
348         if (debug) writeLog(axe);
349         displayException(response, axe, debug);
350       } // end of new try ... catch
351     } // end of if((stylesheetRoot != null) ...
352     if (debug)
353     {
354       time = System.currentTimeMillis() - time;
355       writeLog("  No Conflict Warnings = " + ourDefaultParameters.isNoCW(request) +
356                "  Transformation time: " + time + " ms", response.SC_OK);
357     }
358   }
359   }
360 
361   /**
362    * Returns an XML XSLTInputSource DOM.  Attempts will be make to create the DOM from the following
363    * sources:
364    * <ol>
365    * <li>A relative URL specified in the HTTP request's path information. This capability is intended
366    * for use by <b>servlet engines that map</b> some or all XML data to be processed at the server.</li>
367    * <li>A URL specified in the HTTP request's <code>URL=</code> parameter.  This capability
368    * is intended for <b>clients wishing to selectively process</b> XML data at the server.  For
369    * security reasons, this URL will be forced to the local IP host.</li>
370    * <li>The HTTP request's XML input stream. This capability is intended for use by chained servlets.</li>
371    * </ol>
372    * @param request May contain or point to the XML XSLTInputSource
373    * @param listener To record detailed parsing messages for possible return to requestor
374    * @return XML XSLTInputSource DOM, or null if the XSLTInputSource could not be parsed
375    * @exception ApplyXSLTException Thrown if exception occurs while handling request
376    */
getDocument(HttpServletRequest request, ApplyXSLTListener listener)377   protected StreamSource getDocument(HttpServletRequest request,
378                                      ApplyXSLTListener listener)
379     throws ApplyXSLTException
380   {
381     try
382     {
383       String xmlURL = null;
384       // document from PathInfo
385       if ((xmlURL = request.getPathInfo()) != null)
386       {
387         listener.out.println("Parsing XML Document from PathInfo: " + xmlURL);
388         return new StreamSource(new URL("http", ((DefaultApplyXSLTProperties)
389                                          ourDefaultParameters).getLocalHost(),
390                                          request.getServerPort(),
391                                          xmlURL.replace('\\', '/')).openStream());
392       }
393       // document from Request parameter
394       if ((xmlURL = ourDefaultParameters.getXMLurl(request)) != null)
395       {
396         listener.out.println("Parsing XML Document from request parameter: " + xmlURL);
397         return new StreamSource(new URL(xmlURL).openStream());
398       }
399       // document from chain
400       String contentType = request.getContentType();
401       if ((contentType != null) && contentType.startsWith("text/xml"))
402       {
403         listener.out.println("Parsing XML Document from request chain");
404         return new StreamSource(request.getInputStream());
405       }
406     }
407     catch (IOException ioe)
408     {
409       throw new ApplyXSLTException(ioe, HttpServletResponse.SC_NOT_FOUND);
410     }
411     catch (Exception e)
412     {
413       throw new ApplyXSLTException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
414     }
415     return null;
416   }
417 
418   /**
419    * Returns a Templates (StylesheetRoot) object.  Attempts will be make to create the Stylesheet
420    * from the followingsources:
421    * <ol>
422    * <li>A URL specified in the HTTP request's <code>xslURL=</code> parameter.  This capability
423    * is intended for clients wishing to selectively override the server algorithm for applying XSL
424    * stylesheets.  For security reasons, this URL will be forced to the local IP host.</li>
425    * <li>XML association.  XML documents may contain references to one or more stylesheets using
426    * <a HREF="http://www.w3.org/TR/1999/PR-xml-stylesheet-19990114">this</a> W3C proposed recommendation.
427    * If the XML document does contain such references, a best match will be chosen based on the browser
428    * type making the request and the default association.  This capability enables relationships to be
429    * defined between client capabilities and stylesheets capable of acting on these capabilities.</li>
430    * <li>A configured default stylesheet URL</li>
431    * </ol>
432    * @param request May contain or point to the XSL XSLTInputSource
433    * @param xmlSource  May point to the XSL XSLTInputSource
434    * @param listener To record detailed parsing messages for possible return to requestor
435    * @return XSL XSLTInputSource, or null if the request could not be parsed
436    * @see #makeDocument
437    * @see #getMedia
438    * @see #STYLESHEET_ATTRIBUTE
439    * @see #getXSLURLfromDoc
440    * @see #toAcceptLanguageConnection
441    * @exception ApplyXSLTException Thrown if exception occurs while handling request
442    */
getStylesheet(TransformerFactory tFactory, HttpServletRequest request, StreamSource xmlSource, ApplyXSLTListener listener)443   protected StreamSource getStylesheet(TransformerFactory tFactory,
444 				   		  			   HttpServletRequest request,
445                                        StreamSource xmlSource,
446                                        ApplyXSLTListener listener)
447     throws ApplyXSLTException
448   {
449     try
450     {
451       //stylesheet URL from request
452       String xslURL = ((DefaultApplyXSLTProperties) ourDefaultParameters).getXSLRequestURL(request);
453 
454       if (xslURL != null)
455         listener.out.println("Parsing XSL Stylesheet Document from request parameter: "
456                              + xslURL);
457       else
458       {
459         // find stylesheet from XML Document, Media tag preference
460         if (xmlSource != null){
461           listener.out.println("calling getXSLURLfromDoc and getMedia " + getMedia(request) );
462           xslURL = getXSLURLfromDoc(xmlSource, STYLESHEET_ATTRIBUTE, getMedia(request), tFactory);
463         }
464         if (xslURL != null)
465           listener.out.println("Parsing XSL Stylesheet Document from XML Document tag: " + xslURL);
466         else
467           // Configuration Default
468           if ((xslURL = ourDefaultParameters.getXSLurl(null)) != null)
469             listener.out.println("Parsing XSL Stylesheet Document from configuration: " + xslURL);
470       }
471       return new StreamSource(xslURL);
472     }
473     catch (IOException ioe)
474     {
475       throw new ApplyXSLTException(ioe, HttpServletResponse.SC_NOT_FOUND);
476     }
477     catch (Exception e)
478     {
479       throw new ApplyXSLTException(e, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
480     }
481   }
482 
483   /**
484    * Returns the response content type specified by the media-type and encoding attributes of
485    * the &lt;xsl:output> element(s) of the stylesheet.
486    * @param xslSourceRoot XSL Stylesheet to be examined for &lt;xsl:output> elements.
487    * @return The response content type (MIME type and charset) of the stylesheet output
488    * @see #process
489    */
getContentType(Templates templates)490   public String getContentType(Templates templates)
491   {
492     Properties oprops = templates.getOutputProperties();
493     String encoding = oprops.getProperty(OutputKeys.ENCODING);
494           String media = oprops.getProperty(OutputKeys.MEDIA_TYPE);
495           if (media != null)
496           {
497       if (encoding != null)
498         return media + "; charset=" + encoding;
499       return media;
500           }
501           else
502           {
503             String method = oprops.getProperty(OutputKeys.METHOD);
504             if (method.equals("html"))
505                     return "text/html";
506             else if (method.equals("text"))
507                     return "text/plain";
508             else
509                     return "text/xml";
510           }
511   }
512 
513 
514   /**
515    * Defines and sets select top-level XSL stylesheet variables from the HTTP request, which
516    * can be evaluated using &lt;xsl:param-variable&gt;.  The following variables will be
517    * automatically set:
518    * <dl>
519    * <dt><i>ParameterName</i></dt>
520    * <dd>Each non-reserved request parameter returned from request.getParameterNames().  If a
521    *     parameter contains more than a single value, only the first value is available.</dd>
522    * <dt>servlet-RemoteAddr</dt>
523    * <dd>Contains String output from request.getRemoteAddr(), which is the IP address
524    *     of the client machine.</dd>
525    * <dt>servlet-RemoteHost</dt>
526    * <dd>Contains String output from request.getRemoteHost(), which is the host name
527    *     of the client machine.</dd>
528    * <dt>servlet-RemoteUser</dt>
529    * <dd>Contains String output from request.getRemoteUser(), which was the user name
530    *     accepted by the server to grant access to this servlet.</dd>
531    * <dt>servlet-Request</dt>
532    * <dd>Contains the request object.</dd>
533    * </dl>
534    * @param xslprocessor Where to register parameters to be set
535    * @param request Provides access to all meaningful parameters to set
536    * @see #process
537    */
setStylesheetParams(Transformer transformer, HttpServletRequest request)538   public void setStylesheetParams(Transformer transformer, HttpServletRequest request)
539   {
540     Enumeration paramNames = request.getParameterNames();
541     while (paramNames.hasMoreElements())
542     {
543       String paramName = (String) paramNames.nextElement();
544       try
545       {
546         String[] paramVals = request.getParameterValues(paramName);
547         if (paramVals != null)
548             transformer.setParameter(paramName, new XString(paramVals[0]));
549 
550       }
551       catch (Exception e)
552       {
553       }
554     }
555     try
556     {
557       transformer.setParameter("servlet-RemoteAddr", new XString(request.getRemoteAddr()));
558 
559     }
560     catch (Exception e)
561     {
562     }
563     try
564     {
565       transformer.setParameter("servlet-RemoteHost", new XString(request.getRemoteHost()));
566 
567     }
568     catch (Exception e)
569     {
570     }
571     try
572     {
573       transformer.setParameter("servlet-RemoteUser", new XString(request.getRemoteUser()));
574 
575     }
576     catch (Exception e)
577     {
578     }
579   }
580 
581 
582   /**
583    * Writes the following information to the servlet log:
584    * <ol>
585    * <li>HTTP status code</li>
586    * <li>Message</li>
587    * <li>Stack trace</li>
588    * </ol>
589    * @param axe Contains valid HTTP status code, message, and stack trace (optional)
590    */
writeLog(ApplyXSLTException axe)591   protected void writeLog(ApplyXSLTException axe)
592   {
593     writeLog(axe.getMessage(), axe.getStatusCode(), axe.getException());
594   }
595 
596   /**
597    * Writes the following information to the servlet log:
598    * <ol>
599    * <li>HTTP status code</li>
600    * <li>Message</li>
601    * <li>Stack trace</li>
602    * </ol>
603    * @param msg Message to be logged
604    * @param statusCode Valid status code from javax.servlet.http.HttpServletResponse
605    * @param t Used to generate stack trace (may be =null to suppress stack trace)
606    */
writeLog(String msg, int statusCode, Throwable t)607   protected void writeLog(String msg, int statusCode, Throwable t)
608   {
609     if (t == null)
610       writeLog(msg, statusCode);
611     else
612     {
613       ByteArrayOutputStream bytes = new ByteArrayOutputStream();
614       PrintWriter writer = new PrintWriter(bytes, true);
615       System.out.println("Exception is " + t.getClass().getName());
616       t.printStackTrace(writer);
617       log("HTTP Status Code: " + statusCode + " - " + msg + EOL + bytes.toString());
618     }
619   }
620 
621   /**
622    * Writes the following information to the servlet log:
623    * <ol>
624    * <li>HTTP status code</li>
625    * <li>Message</li>
626    * </ol>
627    * @param msg Message to be logged
628    * @param statusCode Valid status code from javax.servlet.http.HttpServletResponse
629    */
writeLog(String msg, int statusCode)630   protected void writeLog(String msg, int statusCode)
631   {
632     log("HTTP Status Code: " + statusCode + " - " + msg);
633   }
634 
635   /**
636    * Invokes response.sendError setting an HTTP status code and optionally an error message
637    * as an HTML page.
638    * <p>If running in debug mode, also try to return a stack trace of the exception and
639    * and xml/xsl processor messages.</p>
640    * @param response Where to stream the exception to
641    * @param xse The wrapper which contains the exception and its HTTP status code
642    * @param debug Indicates whether to include stack trace, etc.
643    */
displayException(HttpServletResponse response, ApplyXSLTException xse, boolean debug)644   protected void displayException(HttpServletResponse response, ApplyXSLTException xse, boolean debug)
645   {
646     String mesg = xse.getMessage();
647     if (mesg == null)
648       mesg = "";
649     else mesg = "<B>" + mesg + "</B>";
650     StringTokenizer tokens = new StringTokenizer(mesg, EOL);
651     StringBuffer strBuf = new StringBuffer();
652     while (tokens.hasMoreTokens())
653       strBuf.append(tokens.nextToken() + EOL + "<BR>");
654     mesg = strBuf.toString();
655     if (debug)
656     {
657       ByteArrayOutputStream bytes = new ByteArrayOutputStream();
658       PrintWriter writer = new PrintWriter(bytes, true);
659       xse.getException().printStackTrace(writer);
660       mesg += " <PRE> " + bytes.toString() + " </PRE> ";
661     }
662     response.setContentType("text/html");
663     try
664     {
665       response.sendError(xse.getStatusCode(), mesg);
666     }
667     catch (IOException ioe)
668     {
669       System.err.println("IOException is occurring when sendError is called");
670     }
671   }
672 
673   /**
674    * Mapping of HTTP request's user-Agent values to stylesheet media= values.
675    * <p>This mapping is defined by a file pointed to by the operational parameter "mediaURL" which can
676    *  either contain a full URL or a path relative to the System's server.root /servlets directory.</p>
677    * @see #setMediaProps
678    * @see #getMedia
679    * @serial
680    */
681   protected OrderedProps ourMediaProps = null;
682 
683   /**
684    * Returns a connection which respects the Accept-Language header of the HTTP request.  This
685    * is useful when XSL files are internationalized for use with Web servers which respect this
686    * header.
687    * <p>For example, Apache 1.3.6 may be configured for multiviews.  Under this configuration,
688    * requests for http://myhost/index.html would return http://myhost/index.html.fr to French browsers
689    * and http://myhost/index.html.en to English browsers.</p>
690    * @param url Location to connect to
691    * @param request Could contain an Accept-Language header
692    * @return An Accept-Language-enabled URL connection
693    * @see #getStylesheet
694    */
toAcceptLanguageConnection(URL url, HttpServletRequest request)695   protected URLConnection toAcceptLanguageConnection(URL url, HttpServletRequest request)
696     throws Exception
697   {
698     URLConnection tempConnection = url.openConnection();
699     tempConnection.setRequestProperty("Accept-Language", request.getHeader("Accept-Language"));
700     return tempConnection;
701   }
702 
703 
704   /**
705    * Returns the XSL stylesheet URL associated with the specified XML document.  If multiple XSL
706    * stylesheets are associated with the XML document, preference will be given to the stylesheet
707    * which contains an attribute name/value pair that corresponds to the specified attributeName
708    * and attributeValue.
709    * @param xmlSource XML XSLTInputSource to be searched for associated XSL stylesheets
710    * @param attributeName  Attribute name to provide preferential matching
711    * @param attributeValue Attribute value to provide preferential matching
712    * @return The preferred XSL stylesheet URL, or null if no XSL stylesheet association is found
713    * @see #getStylesheet
714    */
getXSLURLfromDoc(StreamSource xmlSource, String attributeName, String attributeValue, TransformerFactory tFactory)715   public static String getXSLURLfromDoc(StreamSource xmlSource,
716                                         String attributeName,
717                                         String attributeValue,
718                                         TransformerFactory tFactory)
719   {
720     String tempURL = null, returnURL = null;
721     try
722     {
723 	  DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
724       DocumentBuilder docBuilder = dfactory.newDocumentBuilder();
725       Node sourceTree = docBuilder.parse(xmlSource.getInputStream());
726       for(Node child=sourceTree.getFirstChild(); null != child; child=child.getNextSibling())
727       {
728         if(Node.PROCESSING_INSTRUCTION_NODE == child.getNodeType())
729         {
730           ProcessingInstruction pi = (ProcessingInstruction)child;
731           if(pi.getNodeName().equals("xml-stylesheet"))
732           {
733             PIA pia = new PIA(pi);
734             if("text/xsl".equals(pia.getAttribute("type")))
735             {
736               tempURL = pia.getAttribute("href");
737               String attribute = pia.getAttribute(attributeName);
738               if ((attribute != null) && (attribute.indexOf(attributeValue) > -1))
739                 return tempURL;
740               if (!"yes".equals(pia.getAttribute("alternate")))
741                 returnURL = tempURL;
742             }
743           }
744         }
745       }
746     }
747     catch(Exception saxExc)
748     {
749     }
750     return returnURL;
751   }
752 
753  /**
754    * The attribute name in the <?xml-stylesheet> tag used in stylesheet selection.
755    */
756   protected static final String STYLESHEET_ATTRIBUTE = "media";
757 
758   /**
759    *	The HTTP Header used for matching the Stylesheet attribute via the
760    * media properties file selected.
761    */
762   protected static final String HEADER_NAME = "user-Agent";
763 }
764 
765 /**
766  *  Stores the keys and values from a file (similar to a properties file) and
767  *  can return the first value which has a key contained in its string.
768  *  File can have comment lines starting with '#" and for each line the entries are
769  *  separated by tabs and '=' char.
770  */
771 class OrderedProps
772 {
773 
774   /**
775    * Stores the Key and Values as an array of Strings
776    */
777   private Vector attVec = new Vector(15);
778 
779   /**
780    * Constructor.
781    * @param inputStream Stream containing the properties file.
782    * @exception IOException Thrown if unable to read from stream
783    */
OrderedProps(InputStream inputStream)784   OrderedProps(InputStream inputStream)
785     throws IOException
786   {
787     BufferedReader input  = new BufferedReader(new InputStreamReader(inputStream));
788     String currentLine, Key = null;
789     StringTokenizer currentTokens;
790     while ((currentLine = input.readLine()) != null)
791     {
792       currentTokens = new StringTokenizer(currentLine, "=\t\r\n");
793       if (currentTokens.hasMoreTokens()) Key = currentTokens.nextToken().trim();
794       if ((Key != null) && !Key.startsWith("#") && currentTokens.hasMoreTokens())
795       {
796         String temp[] = new String[2];
797         temp[0] = Key; temp[1] = currentTokens.nextToken().trim();
798         attVec.addElement(temp);
799       }
800     }
801   }
802 
803   /**
804    * Iterates through the Key list and returns the first value for whose
805    * key the given string contains.  Returns "unknown" if no key is contained
806    * in the string.
807    * @param s String being searched for a key.
808    * @return Value for key found in string, otherwise "unknown"
809    */
getValue(String s)810   String getValue(String s)
811   {
812     int i, j = attVec.size();
813     for (i = 0; i < j; i++)
814     {
815       String temp[] = (String[]) attVec.elementAt(i);
816       if (s.indexOf(temp[0]) > -1)
817         return temp[1];
818     }
819     return "unknown";
820   }
821 }
822 
823 /**
824  * Parses a processing instruction's (PI) attributes for easy retrieval.
825  */
826 class PIA
827 {
828 
829   private Hashtable piAttributes = null;
830 
831   /**
832    * Constructor.
833    * @param pi The processing instruction whose attributes are to be parsed
834    */
PIA(ProcessingInstruction pi)835   PIA(ProcessingInstruction pi)
836   {
837     piAttributes = new Hashtable();
838     StringTokenizer tokenizer = new StringTokenizer(pi.getNodeValue(), "=\"");
839     while(tokenizer.hasMoreTokens())
840     {
841       piAttributes.put(tokenizer.nextToken().trim(), tokenizer.nextToken().trim());
842     }
843   }
844 
845   /**
846    * Returns value of specified attribute.
847    *  @param name Attribute name
848    *  @return Attribute value, or null if the attribute name does not exist
849    */
getAttribute(String name)850   String getAttribute(String name)
851   {
852     return (String) piAttributes.get(name);
853   }
854 }
855