001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    
023    package org.granite.generator.gsp;
024    
025    import groovy.lang.Binding;
026    import groovy.lang.GroovyShell;
027    import groovy.lang.Script;
028    
029    import java.io.BufferedReader;
030    import java.io.File;
031    import java.io.IOException;
032    import java.io.InputStream;
033    import java.io.InputStreamReader;
034    import java.io.Reader;
035    import java.io.Writer;
036    import java.net.URI;
037    import java.nio.charset.Charset;
038    import java.util.List;
039    import java.util.Map;
040    
041    import org.codehaus.groovy.control.CompilationFailedException;
042    import org.codehaus.groovy.runtime.InvokerHelper;
043    import org.granite.generator.Template;
044    import org.granite.generator.exception.TemplateCompilationException;
045    import org.granite.generator.exception.TemplateExecutionException;
046    import org.granite.generator.exception.TemplateParsingException;
047    import org.granite.generator.gsp.token.Token;
048    import org.granite.util.URIUtil;
049    
050    /**
051     * @author Franck WOLFF
052     */
053    public class GroovyTemplate implements Template {
054    
055        private final URI uri;
056        private final boolean base;
057        private final Charset charset;
058    
059        private String source = null;
060        private Script script = null;
061        private long lastModified = -1L;
062    
063        GroovyTemplate(URI uri, boolean base, Charset charset) {
064            if (uri == null || charset == null)
065                throw new IllegalArgumentException("uri and charset cannot be null");
066    
067            this.uri = uri;
068            this.base = base;
069            this.charset = charset;
070        }
071        
072        protected void reset(boolean cleanSource) {
073            if (cleanSource)
074                    source = null;
075            script = null;
076            lastModified = -1L;
077        }
078        protected void reset() {
079            reset(true);
080        }
081    
082        @Override
083            public URI getUri() {
084            return uri;
085        }
086    
087        public long getLastModified() {
088                    return lastModified;
089            }
090        
091        protected long readLastModified() {
092            long readLastModified = -1L;
093            if ("file".equals(uri.getScheme())) {
094                    readLastModified = new File(uri).lastModified();
095                    if (readLastModified == 0L)
096                            readLastModified = -1L;
097            }
098            else
099                    readLastModified = Long.MAX_VALUE;
100            return readLastModified;
101        }
102        
103        public boolean isOutdated() {
104            long newLastModified = readLastModified();
105            return script != null && (newLastModified == -1 || lastModified < newLastModified);
106        }
107    
108            @Override
109            public boolean isBase() {
110                    return base;
111            }
112    
113            @Override
114            public String getMarkup() {
115            try {
116                return URIUtil.getContentAsString(uri);
117            } catch (Exception e) {
118                return "Could not load uri content: " + uri + " (" + e.toString() + ")";
119            }
120        }
121    
122        @Override
123            public String getSource() {
124            return source;
125        }
126    
127        @Override
128            public void compile() throws IOException, TemplateParsingException, TemplateCompilationException {
129            reset();
130            
131            InputStream is = null;
132            try {
133                is = URIUtil.getInputStream(uri, getClass().getClassLoader());
134                
135                lastModified = readLastModified();
136    
137                Reader reader = new BufferedReader(new InputStreamReader(is, charset));
138    
139                Parser parser = new Parser();
140                List<Token> tokens = parser.parse(reader);
141    
142                GroovyRenderer renderer = new GroovyRenderer();
143                source = renderer.renderSource(tokens);
144    
145                // Warning: the classloader for javax annotations is defined in BuilderParentClassLoader
146                // in the case of the Eclipse generator
147                GroovyShell shell = new GroovyShell(Thread.currentThread().getContextClassLoader());
148                script = shell.parse(source);
149            } catch (ParseException e) {
150                throw new TemplateParsingException(this, "Could not parse template: " + uri, e);
151            } catch (CompilationFailedException e) {
152                throw new TemplateCompilationException(this, "Could not compile template: " + uri, e);
153            } finally {
154                    if (script == null)
155                            reset(false);
156                
157                    if (is != null)
158                    is.close();
159            }
160        }
161    
162        @Override
163            public void execute(Map<String, Object> bindings, Writer out)
164            throws IOException, TemplateParsingException, TemplateCompilationException, TemplateExecutionException {
165            
166            if (script == null)
167                    compile();
168            
169            try {
170                Binding binding = (bindings == null ? new Binding() : new Binding(bindings));
171                Script scriptInstance = InvokerHelper.createScript(script.getClass(), binding);
172                scriptInstance.setProperty("out", out);
173                scriptInstance.run();
174                out.flush();
175            } catch (IOException e) {
176                throw e;
177            } catch (Exception e) {
178                throw new TemplateExecutionException(this, "Could not execute template: " + uri, e);
179            }
180        }
181    
182        @Override
183        public boolean equals(Object obj) {
184            if (obj == this)
185                return true;
186            if (!(obj instanceof GroovyTemplate))
187                return false;
188            return uri.equals(((GroovyTemplate)obj).uri);
189        }
190        
191        @Override
192        public int hashCode() {
193            return uri.hashCode();
194        }
195    }