001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2014 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 */
022package org.granite.generator.gsp;
023
024import groovy.lang.Binding;
025import groovy.lang.GroovyShell;
026import groovy.lang.Script;
027
028import java.io.BufferedReader;
029import java.io.File;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.InputStreamReader;
033import java.io.Reader;
034import java.io.Writer;
035import java.net.URI;
036import java.nio.charset.Charset;
037import java.util.List;
038import java.util.Map;
039
040import org.codehaus.groovy.control.CompilationFailedException;
041import org.codehaus.groovy.runtime.InvokerHelper;
042import org.granite.generator.Template;
043import org.granite.generator.exception.TemplateCompilationException;
044import org.granite.generator.exception.TemplateExecutionException;
045import org.granite.generator.exception.TemplateParsingException;
046import org.granite.generator.gsp.token.Token;
047import org.granite.util.URIUtil;
048
049/**
050 * @author Franck WOLFF
051 */
052public class GroovyTemplate implements Template {
053
054    private final URI uri;
055    private final boolean base;
056    private final Charset charset;
057
058    private String source = null;
059    private Script script = null;
060    private long lastModified = -1L;
061
062    GroovyTemplate(URI uri, boolean base, Charset charset) {
063        if (uri == null || charset == null)
064            throw new IllegalArgumentException("uri and charset cannot be null");
065
066        this.uri = uri;
067        this.base = base;
068        this.charset = charset;
069    }
070    
071    protected void reset(boolean cleanSource) {
072        if (cleanSource)
073                source = null;
074        script = null;
075        lastModified = -1L;
076    }
077    protected void reset() {
078        reset(true);
079    }
080
081    @Override
082        public URI getUri() {
083        return uri;
084    }
085
086    public long getLastModified() {
087                return lastModified;
088        }
089    
090    protected long readLastModified() {
091        long readLastModified = -1L;
092        if ("file".equals(uri.getScheme())) {
093                readLastModified = new File(uri).lastModified();
094                if (readLastModified == 0L)
095                        readLastModified = -1L;
096        }
097        else
098                readLastModified = Long.MAX_VALUE;
099        return readLastModified;
100    }
101    
102    public boolean isOutdated() {
103        long newLastModified = readLastModified();
104        return script != null && (newLastModified == -1 || lastModified < newLastModified);
105    }
106
107        @Override
108        public boolean isBase() {
109                return base;
110        }
111
112        @Override
113        public String getMarkup() {
114        try {
115            return URIUtil.getContentAsString(uri);
116        } catch (Exception e) {
117            return "Could not load uri content: " + uri + " (" + e.toString() + ")";
118        }
119    }
120
121    @Override
122        public String getSource() {
123        return source;
124    }
125
126    @Override
127        public void compile() throws IOException, TemplateParsingException, TemplateCompilationException {
128        reset();
129        
130        InputStream is = null;
131        try {
132            is = URIUtil.getInputStream(uri, getClass().getClassLoader());
133            
134            lastModified = readLastModified();
135
136            Reader reader = new BufferedReader(new InputStreamReader(is, charset));
137
138            Parser parser = new Parser();
139            List<Token> tokens = parser.parse(reader);
140
141            GroovyRenderer renderer = new GroovyRenderer();
142            source = renderer.renderSource(tokens);
143
144            // Warning: the classloader for javax annotations is defined in BuilderParentClassLoader
145            // in the case of the Eclipse generator
146            GroovyShell shell = new GroovyShell(Thread.currentThread().getContextClassLoader());
147            script = shell.parse(source);
148        } catch (ParseException e) {
149            throw new TemplateParsingException(this, "Could not parse template: " + uri, e);
150        } catch (CompilationFailedException e) {
151            throw new TemplateCompilationException(this, "Could not compile template: " + uri, e);
152        } finally {
153                if (script == null)
154                        reset(false);
155            
156                if (is != null)
157                is.close();
158        }
159    }
160
161    @Override
162        public void execute(Map<String, Object> bindings, Writer out)
163        throws IOException, TemplateParsingException, TemplateCompilationException, TemplateExecutionException {
164        
165        if (script == null)
166                compile();
167        
168        try {
169            Binding binding = (bindings == null ? new Binding() : new Binding(bindings));
170            Script scriptInstance = InvokerHelper.createScript(script.getClass(), binding);
171            scriptInstance.setProperty("out", out);
172            scriptInstance.run();
173            out.flush();
174        } catch (IOException e) {
175            throw e;
176        } catch (Exception e) {
177            throw new TemplateExecutionException(this, "Could not execute template: " + uri, e);
178        }
179    }
180
181    @Override
182    public boolean equals(Object obj) {
183        if (obj == this)
184            return true;
185        if (!(obj instanceof GroovyTemplate))
186            return false;
187        return uri.equals(((GroovyTemplate)obj).uri);
188    }
189    
190    @Override
191    public int hashCode() {
192        return uri.hashCode();
193    }
194}