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        @Override
081            public URI getUri() {
082            return uri;
083        }
084    
085        public long getLastModified() {
086                    return lastModified;
087            }
088        
089        protected long readLastModified() {
090            long readLastModified = -1L;
091            if ("file".equals(uri.getScheme())) {
092                    readLastModified = new File(uri).lastModified();
093                    if (readLastModified == 0L)
094                            readLastModified = -1L;
095            }
096            else
097                    readLastModified = Long.MAX_VALUE;
098            return readLastModified;
099        }
100        
101        public boolean isOutdated() {
102            long newLastModified = readLastModified();
103            return script != null && (newLastModified == -1 || lastModified < newLastModified);
104        }
105    
106            @Override
107            public boolean isBase() {
108                    return base;
109            }
110    
111            @Override
112            public String getMarkup() {
113            try {
114                return URIUtil.getContentAsString(uri);
115            } catch (Exception e) {
116                return "Could not load uri content: " + uri + " (" + e.toString() + ")";
117            }
118        }
119    
120        @Override
121            public String getSource() {
122            return source;
123        }
124    
125        @Override
126            public void compile() throws IOException, TemplateParsingException, TemplateCompilationException {
127            reset();
128            
129            InputStream is = null;
130            try {
131                is = URIUtil.getInputStream(uri, getClass().getClassLoader());
132                
133                lastModified = readLastModified();
134    
135                Reader reader = new BufferedReader(new InputStreamReader(is, charset));
136    
137                Parser parser = new Parser();
138                List<Token> tokens = parser.parse(reader);
139    
140                GroovyRenderer renderer = new GroovyRenderer();
141                source = renderer.renderSource(tokens);
142    
143                // Warning: the classloader for javax annotations is defined in BuilderParentClassLoader
144                // in the case of the Eclipse builder
145                GroovyShell shell = new GroovyShell(Thread.currentThread().getContextClassLoader());
146                script = shell.parse(source);
147            } catch (ParseException e) {
148                throw new TemplateParsingException(this, "Could not parse template: " + uri, e);
149            } catch (CompilationFailedException e) {
150                throw new TemplateCompilationException(this, "Could not compile template: " + uri, e);
151            } finally {
152                    if (script == null)
153                            reset(false);
154                
155                    if (is != null)
156                    is.close();
157            }
158        }
159    
160        @Override
161            public void execute(Map<String, Object> bindings, Writer out)
162            throws IOException, TemplateParsingException, TemplateCompilationException, TemplateExecutionException {
163            
164            if (script == null)
165                    compile();
166            
167            try {
168                Binding binding = (bindings == null ? new Binding() : new Binding(bindings));
169                Script scriptInstance = InvokerHelper.createScript(script.getClass(), binding);
170                scriptInstance.setProperty("out", out);
171                scriptInstance.run();
172                out.flush();
173            } catch (IOException e) {
174                throw e;
175            } catch (Exception e) {
176                throw new TemplateExecutionException(this, "Could not execute template: " + uri, e);
177            }
178        }
179    
180        @Override
181        public boolean equals(Object obj) {
182            if (obj == this)
183                return true;
184            if (!(obj instanceof GroovyTemplate))
185                return false;
186            return uri.equals(((GroovyTemplate)obj).uri);
187        }
188        
189        @Override
190        public int hashCode() {
191            return uri.hashCode();
192        }
193    }