001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: XSLTProcessorApplet.java 1225408 2011-12-29 01:11:41Z mrglavas $
020     */
021    package org.apache.xalan.client;
022    
023    import java.applet.Applet;
024    import java.awt.Graphics;
025    import java.io.IOException;
026    import java.io.PrintWriter;
027    import java.io.StringReader;
028    import java.io.StringWriter;
029    import java.net.MalformedURLException;
030    import java.net.URL;
031    import java.util.Hashtable;
032    import java.util.Iterator;
033    import java.util.Map;
034    
035    import javax.xml.transform.Templates;
036    import javax.xml.transform.Transformer;
037    import javax.xml.transform.TransformerConfigurationException;
038    import javax.xml.transform.TransformerException;
039    import javax.xml.transform.TransformerFactory;
040    import javax.xml.transform.stream.StreamResult;
041    import javax.xml.transform.stream.StreamSource;
042    
043    import org.apache.xalan.res.XSLMessages;
044    import org.apache.xalan.res.XSLTErrorResources;
045    
046    /**
047     * Provides applet host for the XSLT processor. To perform transformations on an HTML client:
048     * <ol>
049     * <li>Use an &lt;applet&gt; tag to embed this applet in the HTML client.</li>
050     * <li>Use the DocumentURL and StyleURL PARAM tags or the {@link #setDocumentURL} and
051     * {@link #setStyleURL} methods to specify the XML source document and XSL stylesheet.</li>
052     * <li>Call the {@link #getHtmlText} method (or one of the transformToHtml() methods)
053     * to perform the transformation and return the result as a String.</li>
054     * </ol>
055     * 
056     * This class extends Applet which ultimately causes this class to implement Serializable.
057     * This is a serious restriction on this class. All fields that are not transient and not
058     * static are written-out/read-in during serialization. So even private fields essentially
059     * become part of the API. Developers need to take care when modifying fields.
060     * @xsl.usage general
061     */
062    public class XSLTProcessorApplet extends Applet
063    {
064    
065      /**
066       * The stylesheet processor.
067       * This field is now transient because a 
068       * javax.xml.transform.TransformerFactory from JAXP 
069       * makes no claims to be serializable.
070       */
071      transient TransformerFactory m_tfactory = null;
072    
073      /**
074       * @serial
075       */
076      private String m_styleURL;
077    
078      /**
079       * @serial
080       */
081      private String m_documentURL;
082    
083      // Parameter names.  To change a name of a parameter, you need only make
084      // a single change.  Simply modify the value of the parameter string below.
085      //--------------------------------------------------------------------------
086    
087      /**
088       * @serial
089       */
090      private final String PARAM_styleURL = "styleURL";
091    
092      /**
093       * @serial
094       */
095      private final String PARAM_documentURL = "documentURL";
096    
097    
098      // We'll keep the DOM trees around, so tell which trees
099      // are cached.
100    
101      /**
102       * @serial
103       */
104      private String m_styleURLOfCached = null;
105    
106      /**
107       * @serial
108       */
109      private String m_documentURLOfCached = null;
110    
111      /**
112       * Save this for use on the worker thread; may not be necessary.
113       * @serial
114       */
115      private URL m_codeBase = null;
116      
117      /**
118       * @serial
119       */
120      private String m_treeURL = null;
121    
122      /** 
123       * DocumentBase URL
124       * @serial       
125       */
126      private URL m_documentBase = null;
127    
128      /**
129       * Thread stuff for the trusted worker thread.
130       */
131      transient private Thread m_callThread = null;
132    
133      /**
134       */
135      transient private TrustedAgent m_trustedAgent = null;
136    
137      /**
138       * Thread for running TrustedAgent.
139       */
140      transient private Thread m_trustedWorker = null;
141    
142      /**
143       * Where the worker thread puts the HTML text.
144       */
145      transient private String m_htmlText = null;
146      
147      /**
148       * Where the worker thread puts the document/stylesheet text.
149       */
150      transient private String m_sourceText = null;
151      
152      /**
153       * Stylesheet attribute name and value that the caller can set.
154       */
155      transient private String m_nameOfIDAttrOfElemToModify = null;
156    
157      /**
158       */
159      transient private String m_elemIdToModify = null;
160    
161      /**
162       */
163      transient private String m_attrNameToSet = null;
164    
165      /**
166       */
167      transient private String m_attrValueToSet = null;
168    
169      /**
170       * The XSLTProcessorApplet constructor takes no arguments.
171       */
172      public XSLTProcessorApplet(){}
173    
174      /**
175       * Get basic information about the applet
176       * @return A String with the applet name and author.
177       */
178      public String getAppletInfo()
179      {
180        return "Name: XSLTProcessorApplet\r\n" + "Author: Scott Boag";
181      }
182    
183      /**
184       * Get descriptions of the applet parameters.
185       * @return A two-dimensional array of Strings with Name, Type, and Description
186       * for each parameter.
187       */
188      public String[][] getParameterInfo()
189      {
190    
191        String[][] info =
192        {
193          { PARAM_styleURL, "String", "URL to an XSL stylesheet" },
194          { PARAM_documentURL, "String", "URL to an XML document" },
195        };
196    
197        return info;
198      }
199    
200      /**
201       * Standard applet initialization.
202       */
203      public void init()
204      {
205    
206        // PARAMETER SUPPORT
207        //          The following code retrieves the value of each parameter
208        // specified with the <PARAM> tag and stores it in a member
209        // variable.
210        //----------------------------------------------------------------------
211        String param;
212    
213        // styleURL: Parameter description
214        //----------------------------------------------------------------------
215        param = getParameter(PARAM_styleURL);
216        
217        // stylesheet parameters
218        m_parameters = new Hashtable();
219    
220        if (param != null)
221          setStyleURL(param);
222    
223        // documentURL: Parameter description
224        //----------------------------------------------------------------------
225        param = getParameter(PARAM_documentURL);
226    
227        if (param != null)
228          setDocumentURL(param);
229    
230        m_codeBase = this.getCodeBase();
231        m_documentBase = this.getDocumentBase();
232    
233        // If you use a ResourceWizard-generated "control creator" class to
234        // arrange controls in your applet, you may want to call its
235        // CreateControls() method from within this method. Remove the following
236        // call to resize() before adding the call to CreateControls();
237        // CreateControls() does its own resizing.
238        //----------------------------------------------------------------------
239        resize(320, 240);
240      }
241      
242        /**
243       *  Automatically called when the HTML client containing the applet loads.
244       *  This method starts execution of the applet thread.
245       */
246      public void start()
247      {
248    
249        m_trustedAgent = new TrustedAgent();
250        Thread currentThread = Thread.currentThread();
251        m_trustedWorker = new Thread(currentThread.getThreadGroup(),
252                                     m_trustedAgent);
253        m_trustedWorker.start();
254        try
255        {
256          m_tfactory = TransformerFactory.newInstance();
257          this.showStatus("Causing Transformer and Parser to Load and JIT...");
258    
259          // Prime the pump so that subsequent transforms are faster.
260          StringReader xmlbuf = new StringReader("<?xml version='1.0'?><foo/>");
261          StringReader xslbuf = new StringReader(
262            "<?xml version='1.0'?><xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'><xsl:template match='foo'><out/></xsl:template></xsl:stylesheet>");
263          PrintWriter pw = new PrintWriter(new StringWriter());
264    
265          synchronized (m_tfactory)
266          {
267            Templates templates = m_tfactory.newTemplates(new StreamSource(xslbuf));
268            Transformer transformer = templates.newTransformer();
269            transformer.transform(new StreamSource(xmlbuf), new StreamResult(pw));
270          }
271          System.out.println("Primed the pump!");
272          this.showStatus("Ready to go!");
273        }
274        catch (Exception e)
275        {
276          this.showStatus("Could not prime the pump!");
277          System.out.println("Could not prime the pump!");
278          e.printStackTrace();
279        }
280      }
281    
282      /**
283       * Do not call; this applet contains no UI or visual components.
284       *
285       */
286      public void paint(Graphics g){}  
287      
288      /**
289       * Automatically called when the HTML page containing the applet is no longer
290       * on the screen. Stops execution of the applet thread.
291       */
292      public void stop()
293      {
294        if (null != m_trustedWorker)
295        {
296          m_trustedWorker.stop();
297    
298          // m_trustedWorker.destroy();
299          m_trustedWorker = null;
300        }
301    
302        m_styleURLOfCached = null;
303        m_documentURLOfCached = null;
304      }   
305      
306      /**
307       * Cleanup; called when applet is terminated and unloaded.
308       */
309      public void destroy()
310      {
311        if (null != m_trustedWorker)
312        {
313          m_trustedWorker.stop();
314    
315          // m_trustedWorker.destroy();
316          m_trustedWorker = null;
317        }
318        m_styleURLOfCached = null;
319        m_documentURLOfCached = null;
320      }
321    
322      /**
323       * Set the URL to the XSL stylesheet that will be used
324       * to transform the input XML.  No processing is done yet.
325       * @param urlString valid URL string for XSL stylesheet.
326       */
327      public void setStyleURL(String urlString)
328      {
329        m_styleURL = urlString;
330      }
331    
332      /**
333       * Set the URL to the XML document that will be transformed
334       * with the XSL stylesheet.  No processing is done yet.
335       * @param urlString valid URL string for XML document.
336       */
337      public void setDocumentURL(String urlString)
338      {
339        m_documentURL = urlString;
340      }
341    
342      /**
343       * The processor keeps a cache of the source and
344       * style trees, so call this method if they have changed
345       * or you want to do garbage collection.
346       */
347      public void freeCache()
348      {
349        m_styleURLOfCached = null;
350        m_documentURLOfCached = null;
351      }
352    
353      /**
354       * Set an attribute in the stylesheet, which gives the ability
355       * to have some dynamic selection control.
356       * @param nameOfIDAttrOfElemToModify The name of an attribute to search for a unique id.
357       * @param elemId The unique ID to look for.
358       * @param attrName Once the element is found, the name of the attribute to set.
359       * @param value The value to set the attribute to.
360       */
361      public void setStyleSheetAttribute(String nameOfIDAttrOfElemToModify,
362                                         String elemId, String attrName,
363                                         String value)
364      {
365        m_nameOfIDAttrOfElemToModify = nameOfIDAttrOfElemToModify;
366        m_elemIdToModify = elemId;
367        m_attrNameToSet = attrName;
368        m_attrValueToSet = value;
369      }
370    
371    
372      /** 
373       * Stylesheet parameter key/value pair stored in a hashtable
374       */
375      transient Hashtable m_parameters;  
376    
377      /**
378       * Submit a stylesheet parameter.
379       *
380       * @param key stylesheet parameter key
381       * @param expr the parameter expression to be submitted.
382       * @see javax.xml.transform.Transformer#setParameter(String,Object)
383       */
384      public void setStylesheetParam(String key, String expr)
385      {
386        m_parameters.put(key, expr);
387      }
388    
389      /**
390       * Given a String containing markup, escape the markup so it
391       * can be displayed in the browser.
392       *
393       * @param s String to escape
394       *
395       * The escaped string.
396       */
397      public String escapeString(String s)
398      {
399        StringBuffer sb = new StringBuffer();
400        int length = s.length();
401    
402        for (int i = 0; i < length; i++)
403        {
404          char ch = s.charAt(i);
405    
406          if ('<' == ch)
407          {
408            sb.append("&lt;");
409          }
410          else if ('>' == ch)
411          {
412            sb.append("&gt;");
413          }
414          else if ('&' == ch)
415          {
416            sb.append("&amp;");
417          }
418          else if (0xd800 <= ch && ch < 0xdc00)
419          {
420            // UTF-16 surrogate
421            int next;
422    
423            if (i + 1 >= length)
424            {
425              throw new RuntimeException(
426                XSLMessages.createMessage(
427                  XSLTErrorResources.ER_INVALID_UTF16_SURROGATE,
428                  new Object[]{ Integer.toHexString(ch) }));  //"Invalid UTF-16 surrogate detected: "
429    
430              //+Integer.toHexString(ch)+ " ?");
431            }
432            else
433            {
434              next = s.charAt(++i);
435    
436              if (!(0xdc00 <= next && next < 0xe000))
437                throw new RuntimeException(
438                  XSLMessages.createMessage(
439                    XSLTErrorResources.ER_INVALID_UTF16_SURROGATE,
440                    new Object[]{
441                      Integer.toHexString(ch) + " "
442                      + Integer.toHexString(next) }));  //"Invalid UTF-16 surrogate detected: "
443    
444              //+Integer.toHexString(ch)+" "+Integer.toHexString(next));
445              next = ((ch - 0xd800) << 10) + next - 0xdc00 + 0x00010000;
446            }
447            sb.append("&#x");
448            sb.append(Integer.toHexString(next));
449            sb.append(";");
450          }
451          else
452          {
453            sb.append(ch);
454          }
455        }
456        return sb.toString();
457      }
458    
459      /**
460       * Assuming the stylesheet URL and the input XML URL have been set,
461       * perform the transformation and return the result as a String.
462       *
463       * @return A string that contains the contents pointed to by the URL.
464       *
465       */
466      public String getHtmlText()
467      {
468        m_trustedAgent.m_getData = true;
469        m_callThread = Thread.currentThread();
470        try
471        {
472          synchronized (m_callThread)
473          {
474            m_callThread.wait();
475          }
476        }
477        catch (InterruptedException ie)
478        {
479          System.out.println(ie.getMessage());
480        }
481        return m_htmlText;
482      }
483    
484      /**
485       * Get an XML document (or stylesheet)
486       *
487       * @param treeURL valid URL string for the document.
488       *
489       * @return document
490       *
491       * @throws IOException
492       */
493      public String getTreeAsText(String treeURL) throws IOException
494      {
495        m_treeURL = treeURL;
496        m_trustedAgent.m_getData = true;
497        m_trustedAgent.m_getSource = true;
498        m_callThread = Thread.currentThread();
499        try
500        {
501          synchronized (m_callThread)
502          {
503            m_callThread.wait();
504          }
505        }
506        catch (InterruptedException ie)
507        {
508          System.out.println(ie.getMessage());
509        }
510        return m_sourceText;
511      }
512      
513      /**
514       * Use a Transformer to copy the source document
515       * to a StreamResult.
516       * 
517       * @return the document as a string
518       */
519      private String getSource() throws TransformerException
520      {
521        StringWriter osw = new StringWriter();
522        PrintWriter pw = new PrintWriter(osw, false);
523        String text = "";
524        try
525        {
526          URL docURL = new URL(m_documentBase, m_treeURL);
527          synchronized (m_tfactory)
528          {
529            Transformer transformer = m_tfactory.newTransformer();
530            StreamSource source = new StreamSource(docURL.toString());    
531            StreamResult result = new StreamResult(pw);
532            transformer.transform(source, result);
533            text = osw.toString();
534          }
535        }
536        catch (MalformedURLException e)
537        {
538          e.printStackTrace();
539          throw new RuntimeException(e.getMessage());
540        }      
541        catch (Exception any_error)
542        {
543          any_error.printStackTrace();
544        }
545        return text;
546      }
547    
548      /**
549       * Get the XML source Tree as a text string suitable
550       * for display in a browser.  Note that this is for display of the
551       * XML itself, not for rendering of HTML by the browser.
552       *
553       * @return XML source document as a string.
554       * @throws Exception thrown if tree can not be converted.
555       */
556      public String getSourceTreeAsText() throws Exception
557      {
558        return getTreeAsText(m_documentURL);
559      }
560    
561      /**
562       * Get the XSL style Tree as a text string suitable
563       * for display in a browser.  Note that this is for display of the
564       * XML itself, not for rendering of HTML by the browser.
565       *
566       * @return The XSL stylesheet as a string.
567       * @throws Exception thrown if tree can not be converted.
568       */
569      public String getStyleTreeAsText() throws Exception
570      {
571        return getTreeAsText(m_styleURL);
572      }
573    
574      /**
575       * Get the HTML result Tree as a text string suitable
576       * for display in a browser.  Note that this is for display of the
577       * XML itself, not for rendering of HTML by the browser.
578       *
579       * @return Transformation result as unmarked text.
580       * @throws Exception thrown if tree can not be converted.
581       */
582      public String getResultTreeAsText() throws Exception
583      {
584        return escapeString(getHtmlText());
585      }
586    
587      /**
588       * Process a document and a stylesheet and return
589       * the transformation result.  If one of these is null, the
590       * existing value (of a previous transformation) is not affected.
591       *
592       * @param doc URL string to XML document
593       * @param style URL string to XSL stylesheet
594       *
595       * @return HTML transformation result
596       */
597      public String transformToHtml(String doc, String style)
598      {
599    
600        if (null != doc)
601        {
602          m_documentURL = doc;
603        }
604    
605        if (null != style)
606        {
607          m_styleURL = style;
608        }
609    
610        return getHtmlText();
611      }
612    
613      /**
614       * Process a document and a stylesheet and return
615       * the transformation result. Use the xsl:stylesheet PI to find the
616       * document, if one exists.
617       *
618       * @param doc  URL string to XML document containing an xsl:stylesheet PI.
619       *
620       * @return HTML transformation result
621       */
622      public String transformToHtml(String doc)
623      {
624    
625        if (null != doc)
626        {
627          m_documentURL = doc;
628        }
629    
630        m_styleURL = null;
631    
632        return getHtmlText();
633      }
634    
635    
636      /**
637       * Process the transformation.
638       *
639       * @return The transformation result as a string.
640       *
641       * @throws TransformerException
642       */
643      private String processTransformation() throws TransformerException
644      {
645        String htmlData = null;
646        this.showStatus("Waiting for Transformer and Parser to finish loading and JITing...");
647        
648        synchronized (m_tfactory)
649        {
650         URL documentURL = null;
651          URL styleURL = null;
652          StringWriter osw = new StringWriter();
653          PrintWriter pw = new PrintWriter(osw, false);
654          StreamResult result = new StreamResult(pw);
655        
656          this.showStatus("Begin Transformation...");
657          try
658          {
659            documentURL = new URL(m_codeBase, m_documentURL);
660            StreamSource xmlSource = new StreamSource(documentURL.toString());
661    
662            styleURL = new URL(m_codeBase, m_styleURL);
663            StreamSource xslSource = new StreamSource(styleURL.toString());
664    
665            Transformer transformer = m_tfactory.newTransformer(xslSource);
666            
667            Iterator m_entries = m_parameters.entrySet().iterator();
668            while (m_entries.hasNext()) {
669                Map.Entry entry = (Map.Entry) m_entries.next();
670                Object key = entry.getKey();
671                Object expression = entry.getValue();
672                transformer.setParameter((String) key, expression);
673            }
674            transformer.transform(xmlSource, result);
675          }
676          catch (TransformerConfigurationException tfe)
677          {
678            tfe.printStackTrace();
679            throw new RuntimeException(tfe.getMessage());
680          }
681          catch (MalformedURLException e)
682          {
683            e.printStackTrace();
684            throw new RuntimeException(e.getMessage());
685          }
686              
687          this.showStatus("Transformation Done!");
688          htmlData = osw.toString();
689        }
690        return htmlData;
691      }
692    
693      /**
694       * This class maintains a worker thread that that is
695       * trusted and can do things like access data.  You need
696       * this because the thread that is called by the browser
697       * is not trusted and can't access data from the URLs.
698       */
699      class TrustedAgent implements Runnable
700      {
701    
702        /** 
703         * Specifies whether the worker thread should perform a transformation.
704         */
705        public boolean m_getData = false;
706    
707        /** 
708         * Specifies whether the worker thread should get an XML document or XSL stylesheet.
709         */
710        public boolean m_getSource = false;
711    
712        /**
713         * The real work is done from here.
714         *
715         */
716        public void run()
717        {
718          while (true)
719          {
720            Thread.yield();
721    
722            if (m_getData)  // Perform a transformation or get a document.
723            {
724              try
725              {
726                m_getData = false;
727                m_htmlText = null;
728                m_sourceText = null;
729                if (m_getSource)  // Get a document.
730                {
731                  m_getSource = false;
732                  m_sourceText = getSource();
733                }
734                else              // Perform a transformation.
735                  m_htmlText = processTransformation();
736              }
737              catch (Exception e)
738              {
739                e.printStackTrace();
740              }
741              finally
742              {
743                synchronized (m_callThread)
744                {
745                  m_callThread.notify();
746                }
747              }
748            }
749            else
750            {
751              try
752              {
753                Thread.sleep(50);
754              }
755              catch (InterruptedException ie)
756              {
757                ie.printStackTrace();
758              }
759            }
760          }
761        }
762      }
763      
764      // For compatiblity with old serialized objects
765      // We will change non-serialized fields and change methods
766      // and not have this break us.
767      private static final long serialVersionUID=4618876841979251422L;
768      
769      // For compatibility when de-serializing old objects
770      private void readObject(java.io.ObjectInputStream inStream) throws IOException, ClassNotFoundException 
771      {
772          inStream.defaultReadObject();
773          
774          // Needed assignment of non-serialized fields
775          
776          // A TransformerFactory is not guaranteed to be serializable, 
777          // so we create one here
778          m_tfactory = TransformerFactory.newInstance();
779      }      
780    }