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