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