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    }