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
023package org.granite.generator.gsp;
024
025import groovy.lang.Binding;
026import groovy.lang.GroovyShell;
027import groovy.lang.Script;
028
029import java.io.BufferedReader;
030import java.io.File;
031import java.io.IOException;
032import java.io.InputStream;
033import java.io.InputStreamReader;
034import java.io.Reader;
035import java.io.Writer;
036import java.net.URI;
037import java.nio.charset.Charset;
038import java.util.List;
039import java.util.Map;
040
041import org.codehaus.groovy.control.CompilationFailedException;
042import org.codehaus.groovy.runtime.InvokerHelper;
043import org.granite.generator.Template;
044import org.granite.generator.exception.TemplateCompilationException;
045import org.granite.generator.exception.TemplateExecutionException;
046import org.granite.generator.exception.TemplateParsingException;
047import org.granite.generator.gsp.token.Token;
048import org.granite.util.URIUtil;
049
050/**
051 * @author Franck WOLFF
052 */
053public 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}