001    ///////////////////////////////////////////////////////////////////////////////
002    // Copyright (c) 2006, Frank S. Nestel, All Rights Reserved.
003    //
004    // This library is free software; you can redistribute it and/or
005    // modify it under the terms of the GNU Lesser General Public
006    // License as published by the Free Software Foundation; either
007    // version 2.1 of the License, or (at your option) any later version.
008    //
009    // This library is distributed in the hope that it will be useful,
010    // but WITHOUT ANY WARRANTY; without even the implied warranty of
011    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012    // GNU General Public License for more details.
013    //
014    // You should have received a copy of the GNU Lesser General Public
015    // License along with this program; if not, write to the Free Software
016    // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
017    ///////////////////////////////////////////////////////////////////////////////
018    
019    package de.spieleck.app.turn.rendering;
020    
021    import java.io.File;
022    import java.io.IOException;
023    import java.io.FileOutputStream;
024    import java.io.OutputStream;
025    import java.io.InputStream;
026    import java.io.FileInputStream;
027    import java.net.URL;
028    import java.util.LinkedHashMap;
029    import java.util.LinkedHashSet;
030    import java.util.Iterator;
031    import java.util.Locale;
032    import java.util.ResourceBundle;
033    import java.util.Date;
034    import java.util.Arrays;
035    import java.text.MessageFormat;
036    import java.text.DateFormat;
037    import java.text.SimpleDateFormat;
038    
039    import javax.xml.transform.*;
040    import javax.xml.transform.stream.*;
041    
042    import org.apache.log4j.Logger;
043    
044    import de.spieleck.app.turn.LineSource;
045    import de.spieleck.app.turn.Util;
046    
047    /**
048     * A RenderMode which generates elaborate HTML output.
049     * <br />
050     * The output contains various lists of intermediate and final results
051     * and rankings. It contains an overviewpage of all the page generated
052     * by either the renderer or the {@link de.spieleck.app.turn.Run} facade.
053     * <br />
054     * Rendering is done by a nice quick method: Data is exported to xml
055     * by the XStream Library and postprocessed to HTML by XSLT. To be
056     * lightening fast this class keeps track of timestamps, to avoid 
057     * regeneration of unchanged data.
058     *
059     * <p><a href="$URL: https://svn.sourceforge.net/svnroot/jtourney/src/de/spieleck/app/turn/rendering/RenderHtml.java $">$URL: https://svn.sourceforge.net/svnroot/jtourney/src/de/spieleck/app/turn/rendering/RenderHtml.java $</a></p>
060     *
061     * @author Frank S. Nestel
062     * @author $Author: nestefan $
063     * @version $Revision: 2 $ $Date: 2006-03-20 14:33:27 +0100 (Mo, 20 Mrz 2006) $ $Author: nestefan $
064     */
065    public class RenderHtml
066        extends BaseRender
067    {
068        private final static Logger L = Logger.getLogger(RenderHtml.class);
069    
070        public final static DateFormat DF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
071    
072        public final static String CSS_NAME = "turn.css";
073    
074        public final static String INDEX_STYLE = "index";
075        public final static String INDEX_NAME = "index.html";
076        public final static String INDEX_XML = "index.xml";
077    
078        private static TransformerFactory tFac = TransformerFactory.newInstance();
079    
080        private final static Object NOTHING = new Object();
081    
082        private final LinkedHashMap templatesHash = new LinkedHashMap();
083    
084        private final LinkedHashMap params = new LinkedHashMap();
085    
086        private LinkedHashSet sources = new LinkedHashSet();
087    
088        private LinkedHashMap renderedPages = new LinkedHashMap();
089    
090        private boolean needIndex = false;
091    
092        private boolean removeXML = false;
093    
094        private Locale locale = Locale.getDefault();
095    
096        private ResourceBundle texts;
097    
098        public void init(LineSource ls)
099            throws IOException
100        {
101            String line;
102            while ( ( line = ls.line() ) != null )
103            {
104                int i = line.indexOf("=");
105                if ( i > 0 )
106                {
107                    String key = line.substring(0,i);
108                    String value = line.substring(i+1);
109                    params.put(key, value);
110                    L.debug("!set: "+key+"="+value);
111                    if ( "locale".equals(key) )
112                        locale = new Locale(value);
113                }
114                else if ( "removeXML".equals(line) )
115                {
116                    L.debug("removeXML set!");
117                    removeXML = true;
118                }
119            }
120    /*
121     * So nice
122    java.net.URL url = getClass().getResource("texts.properties");
123    System.err.println(url);
124    java.util.PropertyResourceBundle b = new java.util.PropertyResourceBundle(url.openStream());
125    System.err.println(b);
126    */
127            try
128            {
129                // Ain't gonna work texts = ResourceBundle.getBundle("texts", locale);
130                // texts = ResourceBundle.getBundle("texts", locale, this.getClass().getClassLoader());
131                texts = ResourceBundle.getBundle("de.spieleck.app.turn.rendering.texts", locale);
132            }
133            catch ( Exception e )
134            {
135                L.error("ResourceBundle-Problem", e);
136            }
137        }
138    
139        public void addSource(String fName)
140        {
141            L.debug("addSource: "+fName);
142            sources.add(fName);
143        }
144    
145        public void render(long timestamp, Object o, String name,
146                                                String style, int round)
147            throws IOException
148        {
149            L.debug("render: "+name+" ("+style+") round="+round);
150            String xmlName = name+".xml";
151            File xmlFile = new File(getRootOut(), xmlName );
152            String htmlName = name+".html";
153            File htmlFile = new File(getRootOut(), htmlName );
154            String title = MessageFormat.format(texts.getString(style),
155                                        new Object[] { new Integer(round) });
156            String[] extraInfo;
157            String timeStr = timestamp == -1 ? "" : DF.format(timestamp);
158            if ( removeXML )
159                extraInfo = new String[] {title, style, htmlName, timeStr,
160                                            getRoot().getName()};
161            else
162                extraInfo = new String[] {title, style, htmlName, timeStr,
163                                            getRoot().getName(), xmlName};
164            L.debug("extraInfo="+Arrays.asList(extraInfo));
165            if ( timestamp != -1 
166                && htmlFile.exists()
167                && htmlFile.lastModified() >= timestamp )
168            {
169                if ( htmlFile.exists() )
170                    renderedPages.put(name, extraInfo);
171                L.info("Skip output of <"+title+">.");
172                return;
173            }
174            LinkedHashMap output = new LinkedHashMap();
175            output.put("style", style);
176            output.put("title", title);
177            output.put("name", name);
178            output.put("root", getRootOut().getCanonicalPath());
179            output.put("base", getRoot().getName());
180            output.put("data", o);
181            XStreamBuilder.toXML(output, xmlFile);
182            Transformer trans = obtainTransformer(style);
183            if ( trans != null )
184            {
185                StreamSource s1 = new StreamSource(xmlFile);
186                Result out = new StreamResult(new FileOutputStream(htmlFile));
187                try
188                {
189                    trans.transform(s1, out);
190                    if ( removeXML )
191                        xmlFile.delete();
192                    if ( !renderedPages.containsKey(name) )
193                    {
194                        needIndex = true;
195                        renderedPages.put(name, extraInfo);
196                    }
197                }
198                catch ( TransformerException tex )
199                {
200                    L.error("Transforming problem \""+style+"\": "+".");
201                    tex.printStackTrace();
202                }
203            }
204        }
205    
206        public void endSession()
207            throws IOException
208        {
209            check4css();
210            if ( needIndex )
211                renderIndex();
212        }
213    
214        protected void check4css()
215            throws IOException
216        {
217            Util.check4file(getClass(), getRootOut(), CSS_NAME, getRoot(), "");
218        }
219    
220        protected void renderIndex()
221            throws IOException
222        {
223            File indexFile = new File(getRootOut(), INDEX_NAME);
224            File xmlFile = new File(getRootOut(), INDEX_XML);
225            XStreamBuilder.toXML(new Object[] { sources, renderedPages},
226                                        xmlFile);
227            Transformer trans = obtainTransformer(INDEX_STYLE);
228            if ( trans != null )
229            {
230                StreamSource s1 = new StreamSource(xmlFile);
231                Result out = new StreamResult(new FileOutputStream(indexFile));
232                try
233                {
234                    trans.transform(s1, out);
235                    if ( removeXML )
236                        xmlFile.delete();
237                }
238                catch ( TransformerException tex )
239                {
240                    L.error("Transforming problem \""+INDEX_STYLE+"\": "+".");
241                    tex.printStackTrace();
242                }
243            }
244        }
245    
246        protected Transformer obtainTransformer(String style)
247        {
248            synchronized(templatesHash)
249            {
250                Object o = templatesHash.get(style);
251                if ( o == NOTHING )
252                    return null;
253                Templates templates = (Templates) o;
254                String styleName = style+".xsl";
255                try
256                {
257                    if ( templates == null )
258                    {
259                        Source in;
260                        // XSLs in the tournament directory can override
261                        // built in ones.
262                        File fi = new File(getRoot(), styleName);
263                        if ( fi.exists() )
264                        {
265                            in = new StreamSource(fi);
266                        }
267                        else
268                        {
269                            Class clazz= getClass();
270                            URL url = clazz.getResource(styleName);
271                            in = new StreamSource(
272                              clazz.getResourceAsStream(styleName),url.toString());
273                        }
274                        templates = tFac.newTemplates(in);
275                        templatesHash.put(style, templates);
276                    }
277                    Transformer tf = templates.newTransformer();
278                    Iterator iter = params.keySet().iterator();
279                    while ( iter.hasNext()) 
280                    {
281                        String key = (String) iter.next();
282                        tf.setParameter(key, params.get(key));
283                    }
284                    return tf;
285                }
286                catch ( Exception ex )
287                {
288                    L.error("Transformer problem \""+style+"\".", ex);
289                    templatesHash.put(style, NOTHING);
290                    return null;
291                }
292            }
293        }
294    }
295