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 }