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.convert.ConvertingService; 037 import biz.hammurapi.eval.ExpandingFilterWriter; 038 import biz.hammurapi.xml.dom.DomSerializable; 039 040 041 /** 042 * Finds class stylesheet and transforms given 043 * object using the stylesheet. 044 * @author Pavel Vlasov 045 * @revision $Revision$ 046 */ 047 public class ClassTransformerFactory { 048 private static class Key { 049 String className; 050 String profile; 051 Locale locale; 052 053 public boolean equals(Object otherTemplateKey) { 054 if (this==otherTemplateKey) { 055 return true; 056 } 057 058 if (otherTemplateKey instanceof Key) { 059 Key otk=(Key) otherTemplateKey; 060 return className.equals(otk.className) 061 && (profile==null ? otk.profile==null : profile.equals(otk.profile)) 062 && (locale==null ? otk.locale==null : locale.equals(otk.locale)); 063 } 064 065 return false; 066 } 067 068 public int hashCode() { 069 return className.hashCode() ^ (profile==null ? 0 : profile.hashCode()) ^ (locale==null ? 0 : locale.hashCode()); 070 } 071 072 /** 073 * @param className 074 * @param profile 075 * @param locale 076 */ 077 public Key(String className, String profile, Locale locale) { 078 super(); 079 this.className = className; 080 this.profile = profile; 081 this.locale = locale; 082 } 083 } 084 085 private Map templates=new HashMap(); 086 private Map contexts=new HashMap(); 087 private TransformerFactory factory = TransformerFactory.newInstance(); 088 089 private boolean cacheExpandedTemplates; 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 204 AbstractCollection src = new AbstractCollection() { 205 206 public int size() { 207 return ((Collection) object).size(); 208 } 209 210 public Iterator iterator() { 211 return new Iterator() { 212 Iterator master=((Collection) object).iterator(); 213 214 public void remove() { 215 master.remove(); 216 } 217 218 public boolean hasNext() { 219 return master.hasNext(); 220 } 221 222 public Object next() { 223 Object ret=master.next(); 224 if (clazz[0]==null && ret!=null) { 225 clazz[0]=ret.getClass(); 226 } 227 return ret; 228 } 229 }; 230 } 231 232 }; 233 DomSerializable ds = (DomSerializable) ConvertingService.convert(src, DomSerializable.class); 234 ds.toDom(root); 235 236 if (clazz[0]==null) { 237 clazz[0]=ClassTransformerFactory.class; 238 } 239 240 if (profile==null) { 241 profile="list"; 242 } 243 } else { 244 clazz[0]=object.getClass(); 245 DomSerializable ds = (DomSerializable) ConvertingService.convert(object, DomSerializable.class); 246 ds.toDom(root); 247 } 248 249 Templates template=findTemplate(clazz[0], profile, actualLocale, expandContext); 250 if (template==null && object instanceof Collection) { 251 template=findTemplate(ClassTransformerFactory.class, profile, actualLocale, expandContext); 252 } 253 254 Transformer transformer = template==null ? factory.newTransformer() : template.newTransformer(); 255 256 final URIResolver originalUriResolver = factory.getURIResolver(); 257 final String finalProfile=profile; 258 transformer.setURIResolver(new URIResolver() { 259 260 public Source resolve(String uri, String base) throws TransformerException { 261 String resourceUriPrefix = biz.hammurapi.config.DomConfigFactory.RESOURCE_PREFIX; 262 if (uri.equals(resourceUriPrefix+"super")) { 263 final InputStream[] superResource={null}; 264 final Class[] superClass={null}; 265 new ClassHierarchyVisitable(clazz[0]).accept(new Visitor() { 266 267 public boolean visit(Object target) { 268 if (target!=clazz[0]) { 269 superClass[0]=(Class) target; 270 271 if (templateResolver!=null) { 272 superResource[0]=templateResolver.resolve(superClass[0], finalProfile, actualLocale); 273 } 274 275 if (superResource!=null) { 276 return false; 277 } 278 279 for (int i=0; i<4; i++) { 280 String variant=superClass[0].getName().replace('.','/'); 281 if (finalProfile!=null) { 282 variant+=finalProfile; 283 } 284 285 switch (i) { 286 case 0: 287 variant+=actualLocale; 288 break; 289 case 1: 290 variant+="_"+actualLocale.getLanguage(); 291 if (actualLocale.getCountry().length()!=0) { 292 variant+="_"+actualLocale.getCountry(); 293 } 294 break; 295 case 2: 296 variant+="_"+actualLocale.getLanguage(); 297 break; 298 case 3: 299 break; 300 } 301 302 variant+=".xsl"; 303 304 305 superResource[0]=clazz[0].getClassLoader().getResourceAsStream(variant); 306 if (superResource[0]!=null) { 307 return false; 308 } 309 } 310 } 311 312 return true; 313 } 314 315 }); 316 317 if (superResource[0]==null) { 318 throw new TransformerException("Cannot resolve: "+uri); 319 } 320 321 try { 322 return new StreamSource( 323 ExpandingFilterWriter.expand( 324 new InputStreamReader(superResource[0]), 325 new Context() { 326 327 public Object get(String name) { 328 Object ret = expandContext==null ? null : expandContext.get(name); 329 final Context classContext=findContext(superClass[0], finalProfile, actualLocale); 330 if (ret==null && classContext!=null) { 331 ret=classContext.get(name); 332 } 333 return ret; 334 } 335 })); 336 } catch (IOException e) { 337 throw new TransformerException(e); 338 } 339 } else if (uri.startsWith(resourceUriPrefix)) { 340 try { 341 final Class resourceClass = clazz[0].getClassLoader().loadClass(uri.substring(resourceUriPrefix.length())); 342 ClassResourceLoader crl=new ClassResourceLoader(resourceClass); 343 InputStream is=crl.getResourceAsStream(finalProfile, actualLocale, "xsl"); 344 if (is==null) { 345 throw new TransformerException("Cannot resolve: "+uri); 346 } 347 return new StreamSource( 348 ExpandingFilterWriter.expand( 349 new InputStreamReader(is), 350 new Context() { 351 352 public Object get(String name) { 353 Object ret = expandContext==null ? null : expandContext.get(name); 354 final Context classContext=findContext(resourceClass, finalProfile, actualLocale); 355 if (ret==null && classContext!=null) { 356 ret=classContext.get(name); 357 } 358 return ret; 359 } 360 })); 361 362 } catch (ClassNotFoundException e) { 363 throw new TransformerException(e); 364 } catch (IOException e) { 365 throw new TransformerException(e); 366 } 367 } 368 369 return originalUriResolver.resolve(uri, base); 370 } 371 372 }); 373 374 if (parameters!=null) { 375 Iterator it=parameters.entrySet().iterator(); 376 while (it.hasNext()) { 377 Map.Entry entry=(Map.Entry) it.next(); 378 transformer.setParameter((String) entry.getKey(), entry.getValue()); 379 } 380 } 381 382 transformer.transform(new DOMSource(doc), out); 383 } 384 385 public ClassTransformerFactory(ClassTemplateResolver templateResolver, boolean cacheExpandedTemplates) { 386 this.templateResolver=templateResolver; 387 this.cacheExpandedTemplates=cacheExpandedTemplates; 388 } 389 }