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