001 /* 002 @license.text@ 003 */ 004 package biz.hammurapi.util; 005 006 import java.io.IOException; 007 import java.io.InputStream; 008 import java.io.InputStreamReader; 009 import java.util.AbstractCollection; 010 import java.util.Collection; 011 import java.util.HashMap; 012 import java.util.Iterator; 013 import java.util.Locale; 014 import java.util.Map; 015 import java.util.Properties; 016 017 import javax.xml.parsers.DocumentBuilderFactory; 018 import javax.xml.parsers.FactoryConfigurationError; 019 import javax.xml.parsers.ParserConfigurationException; 020 import javax.xml.transform.Result; 021 import javax.xml.transform.Source; 022 import javax.xml.transform.Templates; 023 import javax.xml.transform.Transformer; 024 import javax.xml.transform.TransformerConfigurationException; 025 import javax.xml.transform.TransformerException; 026 import javax.xml.transform.TransformerFactory; 027 import javax.xml.transform.TransformerFactoryConfigurationError; 028 import javax.xml.transform.URIResolver; 029 import javax.xml.transform.dom.DOMSource; 030 import javax.xml.transform.stream.StreamSource; 031 032 import org.w3c.dom.Document; 033 import org.w3c.dom.Element; 034 035 import biz.hammurapi.config.Context; 036 import biz.hammurapi.eval.ExpandingFilterWriter; 037 import biz.hammurapi.xml.dom.CompositeDomSerializer; 038 039 040 /** 041 * Finds class stylesheet and transforms given 042 * object using the stylesheet. 043 * @author Pavel Vlasov 044 * @revision $Revision$ 045 */ 046 public class ClassTransformerFactory { 047 private static class Key { 048 String className; 049 String profile; 050 Locale locale; 051 052 public boolean equals(Object otherTemplateKey) { 053 if (this==otherTemplateKey) { 054 return true; 055 } 056 057 if (otherTemplateKey instanceof Key) { 058 Key otk=(Key) otherTemplateKey; 059 return className.equals(otk.className) 060 && (profile==null ? otk.profile==null : profile.equals(otk.profile)) 061 && (locale==null ? otk.locale==null : locale.equals(otk.locale)); 062 } 063 064 return false; 065 } 066 067 public int hashCode() { 068 return className.hashCode() ^ (profile==null ? 0 : profile.hashCode()) ^ (locale==null ? 0 : locale.hashCode()); 069 } 070 071 /** 072 * @param className 073 * @param profile 074 * @param locale 075 */ 076 public Key(String className, String profile, Locale locale) { 077 super(); 078 this.className = className; 079 this.profile = profile; 080 this.locale = locale; 081 } 082 } 083 084 private Map templates=new HashMap(); 085 private Map contexts=new HashMap(); 086 private TransformerFactory factory = TransformerFactory.newInstance(); 087 088 private boolean cacheExpandedTemplates; 089 private CompositeDomSerializer domSerializer; 090 private ClassTemplateResolver templateResolver; 091 092 private Templates findTemplate(Class clazz, String profile, Locale locale, Context context) throws TransformerFactoryConfigurationError, TransformerConfigurationException, IOException { 093 if (context==null || cacheExpandedTemplates) { 094 synchronized (templates) { 095 Key key=new Key(clazz.getName(), profile, locale); 096 Templates ret=(Templates) templates.get(key); 097 if (ret==null && !templates.containsKey(key)) { 098 ret = loadTemplate(clazz, profile, locale, context); 099 templates.put(key, ret); 100 } 101 102 return ret; 103 } 104 } 105 return loadTemplate(clazz, profile, locale, context); 106 } 107 108 /** 109 * @param clazz 110 * @param profile 111 * @param locale 112 * @param context 113 * @return 114 * @throws TransformerConfigurationException 115 * @throws IOException 116 */ 117 private Templates loadTemplate(Class clazz, String profile, Locale locale, final Context context) throws TransformerConfigurationException, IOException { 118 InputStream is=null; 119 if (templateResolver!=null) { 120 is=templateResolver.resolve(clazz, profile, locale); 121 } 122 123 if (is==null) { 124 is=new ClassResourceLoader(clazz).getResourceAsStream(profile, locale, "xsl"); 125 } 126 127 if (is==null) { 128 return null; 129 } 130 final Context classContext=findContext(clazz, profile, locale); 131 if (classContext==null && context==null) { 132 return factory.newTemplates(new StreamSource(is)); 133 } 134 return factory.newTemplates( 135 new StreamSource( 136 ExpandingFilterWriter.expand( 137 new InputStreamReader(is), 138 new Context() { 139 140 public Object get(String name) { 141 Object ret = context==null ? null : context.get(name); 142 if (ret==null && classContext!=null) { 143 ret=classContext.get(name); 144 } 145 return ret; 146 } 147 }))); 148 } 149 150 private Context findContext(Class clazz, String profile, Locale locale) { 151 synchronized (contexts) { 152 Key key=new Key(clazz.getName(), profile, locale); 153 Context ret=(Context) contexts.get(key); 154 if (ret==null && !contexts.containsKey(key)) { 155 ret=new ClassResourceLoader(clazz).getContext(profile, locale, "ctx"); 156 contexts.put(key, ret); 157 } 158 159 return ret; 160 } 161 } 162 163 /** 164 * Transforms object using object's class stylesheet. Collections are treated differently - for them 165 * a stylesheet of the first element with profile 'list' is used if profile is null. If there are no 166 * elements in the collection or if a stylesheet is not found then ClassTransformerFactory!list stylesheet is used. 167 * @param object 168 * @param profile 169 * @param locale 170 * @param parameters 171 * @param out 172 * @throws TransformerException 173 * @throws FactoryConfigurationError 174 * @throws ParserConfigurationException 175 * @throws TransformerFactoryConfigurationError 176 * @throws IOException 177 * @throws TransformerConfigurationException 178 */ 179 public void transform( 180 final Object object, 181 String rootName, 182 String profile, 183 Locale locale, 184 final Context expandContext, 185 Properties parameters, 186 Result out) 187 throws 188 TransformerException, 189 ParserConfigurationException, 190 FactoryConfigurationError, 191 TransformerConfigurationException, 192 TransformerFactoryConfigurationError, 193 IOException { 194 195 final Class[] clazz={null}; 196 Document doc=DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 197 Element root=doc.createElement(rootName==null ? "root" : rootName); 198 doc.appendChild(root); 199 200 final Locale actualLocale = locale==null ? Locale.getDefault() : locale; 201 202 if (object instanceof Collection) { 203 domSerializer.toDomSerializable(new AbstractCollection() { 204 205 public int size() { 206 return ((Collection) object).size(); 207 } 208 209 public Iterator iterator() { 210 return new Iterator() { 211 Iterator master=((Collection) object).iterator(); 212 213 public void remove() { 214 master.remove(); 215 } 216 217 public boolean hasNext() { 218 return master.hasNext(); 219 } 220 221 public Object next() { 222 Object ret=master.next(); 223 if (clazz[0]==null && ret!=null) { 224 clazz[0]=ret.getClass(); 225 } 226 return ret; 227 } 228 }; 229 } 230 231 }).toDom(root); 232 233 if (clazz[0]==null) { 234 clazz[0]=ClassTransformerFactory.class; 235 } 236 237 if (profile==null) { 238 profile="list"; 239 } 240 } else { 241 clazz[0]=object.getClass(); 242 domSerializer.toDomSerializable(object).toDom(root); 243 } 244 245 Templates template=findTemplate(clazz[0], profile, actualLocale, expandContext); 246 if (template==null && object instanceof Collection) { 247 template=findTemplate(ClassTransformerFactory.class, profile, actualLocale, expandContext); 248 } 249 250 Transformer transformer = template==null ? factory.newTransformer() : template.newTransformer(); 251 252 final URIResolver originalUriResolver = factory.getURIResolver(); 253 final String finalProfile=profile; 254 transformer.setURIResolver(new URIResolver() { 255 256 public Source resolve(String uri, String base) throws TransformerException { 257 String resourceUriPrefix = "resource:"; 258 if (uri.equals(resourceUriPrefix+"super")) { 259 final InputStream[] superResource={null}; 260 final Class[] superClass={null}; 261 new ClassHierarchyVisitable(clazz[0]).accept(new Visitor() { 262 263 public boolean visit(Object target) { 264 if (target!=clazz[0]) { 265 superClass[0]=(Class) target; 266 267 if (templateResolver!=null) { 268 superResource[0]=templateResolver.resolve(superClass[0], finalProfile, actualLocale); 269 } 270 271 if (superResource!=null) { 272 return false; 273 } 274 275 for (int i=0; i<4; i++) { 276 String variant=superClass[0].getName().replace('.','/'); 277 if (finalProfile!=null) { 278 variant+=finalProfile; 279 } 280 281 switch (i) { 282 case 0: 283 variant+=actualLocale; 284 break; 285 case 1: 286 variant+="_"+actualLocale.getLanguage(); 287 if (actualLocale.getCountry().length()!=0) { 288 variant+="_"+actualLocale.getCountry(); 289 } 290 break; 291 case 2: 292 variant+="_"+actualLocale.getLanguage(); 293 break; 294 case 3: 295 break; 296 } 297 298 variant+=".xsl"; 299 300 301 superResource[0]=clazz[0].getClassLoader().getResourceAsStream(variant); 302 if (superResource[0]!=null) { 303 return false; 304 } 305 } 306 } 307 308 return true; 309 } 310 311 }); 312 313 if (superResource[0]==null) { 314 throw new TransformerException("Cannot resolve: "+uri); 315 } 316 317 try { 318 return new StreamSource( 319 ExpandingFilterWriter.expand( 320 new InputStreamReader(superResource[0]), 321 new Context() { 322 323 public Object get(String name) { 324 Object ret = expandContext==null ? null : expandContext.get(name); 325 final Context classContext=findContext(superClass[0], finalProfile, actualLocale); 326 if (ret==null && classContext!=null) { 327 ret=classContext.get(name); 328 } 329 return ret; 330 } 331 })); 332 } catch (IOException e) { 333 throw new TransformerException(e); 334 } 335 } else if (uri.startsWith(resourceUriPrefix)) { 336 try { 337 final Class resourceClass = clazz[0].getClassLoader().loadClass(uri.substring(resourceUriPrefix.length())); 338 ClassResourceLoader crl=new ClassResourceLoader(resourceClass); 339 InputStream is=crl.getResourceAsStream(finalProfile, actualLocale, "xsl"); 340 if (is==null) { 341 throw new TransformerException("Cannot resolve: "+uri); 342 } 343 return new StreamSource( 344 ExpandingFilterWriter.expand( 345 new InputStreamReader(is), 346 new Context() { 347 348 public Object get(String name) { 349 Object ret = expandContext==null ? null : expandContext.get(name); 350 final Context classContext=findContext(resourceClass, finalProfile, actualLocale); 351 if (ret==null && classContext!=null) { 352 ret=classContext.get(name); 353 } 354 return ret; 355 } 356 })); 357 358 } catch (ClassNotFoundException e) { 359 throw new TransformerException(e); 360 } catch (IOException e) { 361 throw new TransformerException(e); 362 } 363 } 364 365 return originalUriResolver.resolve(uri, base); 366 } 367 368 }); 369 370 if (parameters!=null) { 371 Iterator it=parameters.entrySet().iterator(); 372 while (it.hasNext()) { 373 Map.Entry entry=(Map.Entry) it.next(); 374 transformer.setParameter((String) entry.getKey(), entry.getValue()); 375 } 376 } 377 378 transformer.transform(new DOMSource(doc), out); 379 } 380 381 public ClassTransformerFactory(ClassTemplateResolver templateResolver, CompositeDomSerializer domSerializer, boolean cacheExpandedTemplates) { 382 this.templateResolver=templateResolver; 383 this.domSerializer = domSerializer==null ? CompositeDomSerializer.getThreadInstance() : domSerializer; 384 this.cacheExpandedTemplates=cacheExpandedTemplates; 385 } 386 }