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