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    }