001    package com.hammurapi.render;
002    
003    import java.io.File;
004    import java.io.FileWriter;
005    import java.io.IOException;
006    import java.io.InputStream;
007    import java.io.Writer;
008    import java.net.URL;
009    import java.util.HashMap;
010    import java.util.Locale;
011    import java.util.Map;
012    
013    import org.onemind.commons.java.util.FileUtils;
014    import org.onemind.jxp.CachedJxpPage;
015    import org.onemind.jxp.CachingPageSource;
016    import org.onemind.jxp.JxpContext;
017    import org.onemind.jxp.JxpPageSource;
018    import org.onemind.jxp.JxpProcessor;
019    
020    import com.hammurapi.common.ClassHierarchyVisitable;
021    import com.hammurapi.common.Context;
022    import com.hammurapi.common.IdentityManager;
023    import com.hammurapi.common.Visitor;
024    import com.hammurapi.convert.ConvertingService;
025    
026    /**
027     * JxpRenderer renders using JXP template engine and pages loaded by classloader.
028     * @author Pavel Vlasov
029     *
030     */
031    public class JxpRenderer implements WriterRenderer, FileRenderer, RenderingConstants {
032    
033            /**
034             * Loads rendering resource form toRender class loader.
035             * @author Pavel Vlasov
036             *
037             */
038            private JxpPageSource pageSource = new CachingPageSource() {
039    
040                    /**
041                     * {@inheritDoc}
042                     */
043                    private String getStreamName(String pageName) {
044                            return FileUtils.concatFilePath(DefaultRenderingPageSource.PATH_PREFIX, pageName);
045                    }
046    
047                    /**
048                     * {@inheritDoc}
049                     */
050    
051                    protected boolean isExpired(CachedJxpPage page) {
052                            return false;
053                    }
054    
055                    /**
056                     * {@inheritDoc}
057                     */
058                    protected boolean hasStream(String pageName) {
059                            String streamName = getStreamName(pageName);
060                            URL resource = toRender.getClass().getResource(streamName);
061                            return resource != null;
062                    }
063    
064                    /**
065                     * {@inheritDoc}
066                     */
067                    protected InputStream loadStream(CachedJxpPage page) throws IOException {
068                            return toRender.getClass().getResourceAsStream(getStreamName(page.getName()));
069                    }
070            };      
071            
072            private Object toRender;
073    
074            public JxpRenderer(Object obj) {
075                    this.toRender = obj;
076            }
077    
078            /**
079             * Renders to a file. 
080             * This method does not overwrite existing files if they were created after rendering has started.
081             * It prevents infinite loops in rendering.
082             */
083            @Override
084            public boolean render(File out, Map<String, Object> environment, Context context, String profile, Locale locale) throws RenderingException {
085                    if (environment!=null) {
086                            Object renderStart = environment.get(RENDER_START);
087                            if (renderStart instanceof Long) {
088                                    if (out.exists() && out.isFile() && out.lastModified()>=((Long) renderStart)) {
089                                            return false;
090                                    }
091                            }
092                    }
093                    
094                    try {
095                            Writer writer = new FileWriter(out);
096                            try {
097                                    return render(writer, environment, context, profile, locale, out.getParentFile());
098                            } finally {
099                                    writer.close();
100                            }
101                    } catch (Exception e) {
102                            throw new RenderingException(e);
103                    }
104            }
105                    
106            @Override
107            public boolean render(Writer out, Map<String, Object> environment, Context context, final String profile, final Locale locale, File outputDir)    throws RenderingException {
108                    final JxpPageSource ps = context.lookup(JxpPageSource.class);
109                    
110                    class SearchResult {
111                            String pageName;
112                            JxpPageSource pageSource;
113                            
114                            SearchResult(String pageName, JxpPageSource pageSource) {
115                                    super();
116                                    this.pageName = pageName;
117                                    this.pageSource = pageSource;
118                            }                                               
119                    }
120                    
121                    final SearchResult[] sr = {null};
122                    
123                    ClassHierarchyVisitable chv = new ClassHierarchyVisitable(toRender.getClass());
124                    
125                    chv.accept(new Visitor<Class<?>>() {
126    
127                            Locale actualLocale = locale==null ? Locale.getDefault() : locale;
128                            
129                            @Override
130                            public boolean visit(Class<?> target) {
131                                    for (int i=0; i<4; i++) {
132                                            String variant=target.getName().replace('.','/');
133                                            
134                                            if (profile!=null) {
135                                                    variant+="!"+profile;
136                                            }
137                                            
138                                            switch (i) {
139                                                    case 0:
140                                                            variant+="_"+actualLocale;
141                                                            break;
142                                                    case 1:
143                                                            variant+="_"+actualLocale.getLanguage();
144                                                            if (actualLocale.getCountry().length()!=0) {
145                                                                    variant+="_"+actualLocale.getCountry();
146                                                            }
147                                                            break;
148                                                    case 2:
149                                                            variant+="_"+actualLocale.getLanguage();
150                                                            break;
151                                                    case 3:
152                                                            break;                                                  
153                                            }
154                                            
155                                            variant+=".jxp";
156                                            
157                                            if (ps!=null && ps.hasJxpPage(variant)) {
158                                                    sr[0] = new SearchResult(variant, ps);
159                                                    return false;
160                                            }
161                                            
162                                            if (pageSource.hasJxpPage(variant)) {
163                                                    sr[0] = new SearchResult(variant, pageSource);
164                                                    return false;                                           
165                                            }
166                                    }
167                                    
168                                    return true;
169                            }
170                            
171                    });
172                    
173                    if (sr[0]==null) {
174                            return false;
175                    }
176                    
177                    JxpContext jxpContext = new JxpContext(sr[0].pageSource);
178                    JxpProcessor processor = new JxpProcessor(jxpContext);
179                    
180                    try {
181                            Map<String, Object> env = environment==null ? new HashMap<String, Object>() : new HashMap<String, Object>(environment);
182                            env.put(TO_RENDER, toRender);
183    //                      env.remove(TO_RENDER_TYPE_NAME); // Clean up.
184                            processor.process(sr[0].pageName, out, env);            
185                            postRender(env, context, profile, locale, outputDir);
186                            return true;
187                    } catch (Exception e) {
188                            throw new RenderingException(e);
189                    }
190            }
191    
192            /**
193             * This method is invoked after successful rendering. This implementation is tailored for report generator -
194             * it renders details and contents if outputDir environment entry is instance of File and profile is outline or
195             * outline_http
196             * @param environment
197             * @param context
198             * @param profile
199             * @param locale
200             * @throws RenderingException
201             */
202            protected void postRender(Map<String, Object> environment, Context context, final String profile, final Locale locale, File outputDir) throws RenderingException {
203                    if (outputDir!=null && (OUTLINE.equals(profile) || OUTLINE_HTTP.equals(profile))) {
204                            IdentityManager<?> identityManager = context.lookup(IdentityManager.class);
205                            Object id = identityManager.getIdentity(toRender);
206                            File detailsOut = new File(outputDir, "e"+id+".html");
207                            FileRenderer fileRenderer = ConvertingService.convert(toRender, FileRenderer.class);
208                            if (fileRenderer==null) {
209                                    render(detailsOut, environment, context, null, locale);
210                            } else {
211                                    fileRenderer.render(detailsOut, environment, context, null, locale);
212                            }
213                            
214                            boolean http = OUTLINE_HTTP.equals(profile);
215                            File contentsOut = new File(outputDir, "e"+id+"_contents.html");
216                            if (fileRenderer==null) {
217                                    render(contentsOut, environment, context, http ? CONTENTS_HTTP : CONTENTS, locale);
218                            } else {
219                                    fileRenderer.render(contentsOut, environment, context, http ? CONTENTS_HTTP : CONTENTS, locale);                                
220                            }
221                    }                               
222            }
223    }
224