package org.webpieces.templatingdev.impl;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.apache.commons.io.IOUtils;
import org.webpieces.templating.api.HtmlTagLookup;
import org.webpieces.templating.api.RouterLookup;
import org.webpieces.templating.api.Template;
import org.webpieces.templating.impl.ProdTemplateService;
import org.webpieces.templating.impl.TemplateImpl;
import org.webpieces.templatingdev.api.TemplateCompileConfig;
import org.webpieces.util.file.VirtualFile;
import org.webpieces.util.file.VirtualFileClasspath;
public class DevTemplateService extends ProdTemplateService {
private HtmlToJavaClassCompiler compiler;
private TemplateCompileConfig config;
private HtmlTagLookup htmlTagLookup;
private ThreadLocal<OurGroovyClassLoader> currentCl = new ThreadLocal<>();
@Inject
public DevTemplateService(
RouterLookup urlLookup,
HtmlTagLookup htmlTagLookup,
HtmlToJavaClassCompiler compiler,
TemplateCompileConfig config) {
super(urlLookup, htmlTagLookup);
this.htmlTagLookup = htmlTagLookup;
this.compiler = compiler;
this.config = config;
}
@Override
public void loadAndRunTemplate(String templatePath, StringWriter out, Map<String, Object> pageArgs) {
//TODO: big nit and for fun, we should look into recreating OurGroovyClassLoader ONLY when
//the html files have changed. to do this, I think we would have to save a Holder object with the first
//classloader and only swap him on changes to the html files instead of on every request. I think if we
//do that, the ThreadLocal may go away as well. This may need some manual testing and we may have to add
// more change files during test like we did for the controller testing in TestDevRefreshPageWithNoRestarting.java
//prod knows nothing about groovy or the Groovy classloader so we keep it that way
//by setting the classloader used on the thread. This is NOT A recursive function like the below.
currentCl.set(new OurGroovyClassLoader());
try {
super.loadAndRunTemplate(templatePath, out, pageArgs);
} finally {
currentCl.set(null);
}
}
protected Template loadTemplate(String fullTemplatePath, String fullClassName) {
//this is a recursive function. Run TestFieldTag.java to see
try {
return loadTemplateImpl(currentCl.get(), fullTemplatePath, fullClassName);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private Template loadTemplateImpl(OurGroovyClassLoader cl, String fullTemplatePath, String templateFullClassName) throws IOException, ClassNotFoundException {
if(config.isMustReadClassFromFileSystem()) {
//shortcut for tests that use PlatformOverridesForTest to ensure we test the production groovy class files AND
//it ensures we give code coverage numbers on those class files as well.
Class<?> compiledTemplate = DevTemplateService.class.getClassLoader().loadClass(templateFullClassName);
return new TemplateImpl(urlLookup, htmlTagLookup, compiledTemplate);
}
VirtualFile theResource = null;
List<VirtualFile> srcPaths = config.getSrcPaths();
for(VirtualFile f : srcPaths) {
VirtualFile child = f.child(fullTemplatePath.substring(1));//take off the leading '/' so it is a relative path
if(child.exists()) {
theResource = child;
break;
}
}
if(theResource == null)
theResource = new VirtualFileClasspath(fullTemplatePath, DevTemplateService.class);
if(!theResource.exists())
throw new FileNotFoundException("resource="+fullTemplatePath+" was not found in classpath");
try(InputStream resource = theResource.openInputStream()) {
String viewSource = IOUtils.toString(resource, config.getFileEncoding().name());
Class<?> compiledTemplate = createTemplate(cl, templateFullClassName, viewSource);
return new TemplateImpl(urlLookup, htmlTagLookup, compiledTemplate);
}
}
private Class<?> createTemplate(OurGroovyClassLoader cl, String fullClassName, String source) throws ClassNotFoundException {
if(!cl.isClassDefined(fullClassName))
compiler.compile(cl, fullClassName, source);
return cl.loadClass(fullClassName);
}
}