package pl.matisoft.soy.template;
import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.context.support.ServletContextResource;
import pl.matisoft.soy.config.SoyViewConfigDefaults;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import javax.servlet.ServletContext;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created with IntelliJ IDEA.
* User: mati
* Date: 20/06/2013
* Time: 19:58
*
* An implementation that will recursively search (and resolve)
* for soy files based on provided templatesLocation path
*/
@ParametersAreNonnullByDefault
@ThreadSafe
public class DefaultTemplateFilesResolver implements TemplateFilesResolver, ServletContextAware, InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(DefaultTemplateFilesResolver.class);
/** spring resource that points to a root path, in which soy templates are located */
private Resource templatesLocation = null;
private boolean recursive = true;
private boolean hotReloadMode = SoyViewConfigDefaults.DEFAULT_HOT_RELOAD_MODE;
/** a thread safe cache for resolved templates, no need to worry of ddos attack */
/** friendly */ CopyOnWriteArrayList<URL> cachedFiles = new CopyOnWriteArrayList<URL>();
private String filesExtension = SoyViewConfigDefaults.DEFAULT_FILES_EXTENSION;
@Inject
private ServletContext servletContext;
public DefaultTemplateFilesResolver() {
}
@Override
public void afterPropertiesSet() throws Exception {
if (templatesLocation == null) {
templatesLocation = new ServletContextResource(servletContext, SoyViewConfigDefaults.DEFAULT_TEMPLATE_FILES_PATH);
}
}
@Override
public Collection<URL> resolve() throws IOException {
Preconditions.checkNotNull(templatesLocation, "templatesLocation cannot be null!");
if (hotReloadMode) {
final List<URL> files = toFiles(templatesLocation);
logger.debug("Debug on - resolved files:" + files.size());
return files;
}
//no debug
synchronized (cachedFiles) {
if (cachedFiles.isEmpty()) {
final List<URL> files = toFiles(templatesLocation);
logger.debug("templates location:" + templatesLocation);
logger.debug("Using cache resolve, debug off, urls:" + files.size());
cachedFiles.addAll(files);
}
}
return cachedFiles;
}
@Override
public Optional<URL> resolve(final @Nullable String templateFileName) throws IOException {
if (templateFileName == null) {
return Optional.absent();
}
final Collection<URL> files = resolve();
final URL templateFile = Iterables.find(files, new Predicate<URL>() {
@Override
public boolean apply(final URL url) {
final String fileName = url.getFile();
final File file = new File(fileName);
return file.toURI().toString().endsWith(normalizeTemplateName(templateFileName));
}
}, null);
return Optional.fromNullable(templateFile);
}
private String normalizeTemplateName(final String templateFileName) {
String normalizedTemplateName = templateFileName;
if (!templateFileName.endsWith(dotWithExtension())) {
normalizedTemplateName = templateFileName + dotWithExtension();
}
return normalizedTemplateName;
}
private List<URL> toFiles(final Resource templatesLocation) {
final List<URL> templateFiles = Lists.newArrayList();
try {
File baseDirectory = templatesLocation.getFile();
if (baseDirectory.isDirectory()) {
templateFiles.addAll(findSoyFiles(baseDirectory, recursive));
} else {
throw new IllegalArgumentException("Soy template base directory:" + templatesLocation + "' is not a directory");
}
} catch (final IOException e) {
throw new IllegalArgumentException("Soy template base directory '" + templatesLocation + "' does not exist", e);
}
return templateFiles;
}
protected List<URL> findSoyFiles(final File baseDirectory, final boolean recursive) throws MalformedURLException {
final List<URL> soyFiles = new ArrayList<URL>();
findSoyFiles(soyFiles, baseDirectory, recursive);
return soyFiles;
}
protected void findSoyFiles(final List<URL> soyFiles, final File baseDirectory, final boolean recursive) throws MalformedURLException {
final File[] files = baseDirectory.listFiles();
if (files != null) {
for (final File file : files) {
if (file.isFile()) {
if (file.getName().endsWith(dotWithExtension())) {
soyFiles.add(file.toURI().toURL());
}
} else if (file.isDirectory() && recursive) {
findSoyFiles(soyFiles, file, recursive);
}
}
} else {
throw new IllegalArgumentException("Unable to retrieve contents of:" + baseDirectory);
}
}
private String dotWithExtension() {
return "." + filesExtension;
}
public void setTemplatesLocation(Resource templatesLocation) {
this.templatesLocation = templatesLocation;
}
public void setRecursive(boolean recursive) {
this.recursive = recursive;
}
public void setHotReloadMode(boolean hotReloadMode) {
this.hotReloadMode = hotReloadMode;
}
public Resource getTemplatesLocation() {
return templatesLocation;
}
public boolean isRecursive() {
return recursive;
}
public boolean isHotReloadMode() {
return hotReloadMode;
}
public String getFilesExtension() {
return filesExtension;
}
public void setFilesExtension(String filesExtension) {
this.filesExtension = filesExtension;
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}