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}