/*
* TemplateManager.java
*
* This work is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
*
* This work is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Copyright (c) 2004 Per Cederberg. All rights reserved.
*/
package org.liquidsite.app.template;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.Date;
import freemarker.cache.MruCacheStorage;
import freemarker.cache.TemplateLoader;
import freemarker.core.Configurable;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import org.liquidsite.core.content.Content;
import org.liquidsite.core.content.ContentException;
import org.liquidsite.core.content.ContentManager;
import org.liquidsite.core.content.ContentPage;
import org.liquidsite.core.content.ContentSecurityException;
import org.liquidsite.core.content.ContentTemplate;
import org.liquidsite.core.content.Domain;
import org.liquidsite.core.content.User;
import org.liquidsite.util.log.Log;
/**
* A simple FreeMarker template manager. This class provides static
* methods that simplify the creation of templates.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
public class TemplateManager {
/**
* The class logger.
*/
static final Log LOG = new Log(TemplateManager.class);
/**
* The build version to return.
*/
private static String buildVersion = null;
/**
* The build date to return.
*/
private static String buildDate = null;
/**
* The file template configuration.
*/
private static Configuration fileConfig = null;
/**
* The page template configuration.
*/
private static Configuration pageConfig = null;
/**
* The page template loader.
*/
private static PageLoader pageLoader = new PageLoader();
/**
* Initializes the template manager.
*
* @param baseDir the application base directory
* @param version the application version number
* @param date the application version date
*
* @throws TemplateException if the template base directory
* couldn't be read properly
*/
public static void initialize(File baseDir,
String version,
String date)
throws TemplateException {
buildVersion = version;
buildDate = date;
fileConfig = new Configuration();
fileConfig.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
fileConfig.setStrictSyntaxMode(true);
fileConfig.setDefaultEncoding("UTF-8");
fileConfig.setLocalizedLookup(false);
try {
fileConfig.setSetting(Configurable.OUTPUT_ENCODING_KEY, "UTF-8");
} catch (freemarker.template.TemplateException e) {
LOG.error(e.getMessage());
throw new TemplateException("couldn't set output encoding: " +
e.getMessage());
}
try {
fileConfig.setDirectoryForTemplateLoading(baseDir);
} catch (IOException e) {
LOG.error(e.getMessage());
throw new TemplateException("couldn't read " + baseDir);
}
pageConfig = new Configuration();
pageConfig.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER);
pageConfig.setStrictSyntaxMode(true);
pageConfig.setDefaultEncoding("UTF-8");
pageConfig.setLocalizedLookup(false);
try {
pageConfig.setSetting(Configurable.OUTPUT_ENCODING_KEY, "UTF-8");
} catch (freemarker.template.TemplateException e) {
LOG.error(e.getMessage());
throw new TemplateException("couldn't set output encoding: " +
e.getMessage());
}
pageConfig.setTemplateLoader(pageLoader);
pageConfig.setCacheStorage(new MruCacheStorage(0, 100));
pageConfig.setTemplateUpdateDelay(0);
}
/**
* Returns the application build version.
*
* @return the application build version
*/
public static String getBuildVersion() {
return buildVersion;
}
/**
* Returns the application build date.
*
* @return the application build date
*/
public static String getBuildDate() {
return buildDate;
}
/**
* Returns a template from the file system. The file name must be
* specified relative to the base directory used in the
* initialize() call.
*
* @param path the relative file name
*
* @return the template found
*
* @throws TemplateException if the template couldn't be read
* correctly
*/
public static Template getFileTemplate(String path)
throws TemplateException {
try {
return new Template(fileConfig.getTemplate(path));
} catch (IOException e) {
LOG.error(e.getMessage());
throw new TemplateException("couldn't read " + path, e);
}
}
/**
* Returns a template from a content page.
*
* @param user the user performing the operation
* @param page the content page
*
* @return the template found
*
* @throws TemplateException if the template couldn't be read
* correctly
*/
public static Template getPageTemplate(User user, ContentPage page)
throws TemplateException {
Template template;
String name;
String message;
try {
pageLoader.setUser(user);
pageLoader.setPage(page);
if (page.getContentManager().isAdmin()) {
name = "$preview/" + page.getId() + "/root";
} else {
name = "$normal/" + page.getId() + "/root";
}
template = new Template(pageConfig.getTemplate(name));
return template;
} catch (IOException e) {
message = "couldn't read page " + page.getId();
LOG.error(message + ": " + e.getMessage());
throw new TemplateException(message, e);
}
}
/**
* A page template loader. This class provides the page elements
* to the FreeMarker template engine. Before a template is loaded
* with this loader, the corresponding content page must be set
* with the setPage() method. This class is thread-safe, as it
* uses a thread local storage for the pages.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
private static class PageLoader implements TemplateLoader {
/**
* The content pages to load. This is a thread local variable
* as the loader needs to be thread-safe.
*/
private ThreadLocal pages = new ThreadLocal();
/**
* The user performing the operation. This is a thread local
* variable as the loader needs to be thread-safe.
*/
private ThreadLocal users = new ThreadLocal();
/**
* Creates a new page template loader.
*/
public PageLoader() {
// No further initialization needed
}
/**
* Returns the content page to load (for the calling thread).
*
* @return the content page to load
*/
public ContentPage getPage() {
return (ContentPage) pages.get();
}
/**
* Sets the content page to load (for the calling thread).
* This method should be called once before loading the
* template.
*
* @param page the content page to load, or null
*/
public void setPage(ContentPage page) {
pages.set(page);
}
/**
* Returns the user performing the operation (for the calling
* thread).
*
* @return the user performing the operation
*/
public User getUser() {
return (User) users.get();
}
/**
* Sets the the user performing the operation (for the
* calling thread). This method should be called once before
* loading the template.
*
* @param user the user performing the operation
*/
public void setUser(User user) {
users.set(user);
}
/**
* Returns the page element data. The template name specified
* either uniquely identifies a root template and page element,
* or ends with a page element name.
*
* @param name the template name
*
* @return the page element data, or
* null if not found
*
* @throws IOException if the page element couldn't be read
* properly
*/
public Object findTemplateSource(String name) throws IOException {
ContentPage page = getPage();
String elemName;
String message;
// Check for missing page
LOG.trace("template lookup for page element " + name);
if (page == null) {
message = "page not found for page element " + name;
LOG.error(message);
throw new IOException(message);
}
// Extract page element data
try {
if (name.startsWith("$")) {
elemName = name.substring(name.lastIndexOf("/") + 1);
return page.getElement(getUser(), elemName);
} else {
return findRootTemplateSource(page.getContentManager(),
name);
}
} catch (ContentException e) {
message = "while reading page element " + name +
": " + e.getMessage();
LOG.error(message);
throw new IOException(e.getMessage());
} catch (ContentSecurityException e) {
message = "while reading page element " + name +
": " + e.getMessage();
LOG.error(message);
throw new IOException(e.getMessage());
}
}
/**
* Returns the root template element data. The template name
* specified must uniquely identify a root template and page
* element,
*
* @param manager the content manager
* @param name the template name
*
* @return the template source element found, or
* null if not found
*
* @throws ContentException if the template element couldn't
* be read properly
* @throws ContentSecurityException if the anonymous user
* didn't have access to the template
*/
public Object findRootTemplateSource(ContentManager manager,
String name)
throws ContentException, ContentSecurityException {
Domain root = manager.getDomain(null, "ROOT");
ContentTemplate template = null;
Content content;
String elemName;
int pos;
while (name.indexOf("/") > 0) {
pos = name.indexOf("/");
elemName = name.substring(0, pos);
name = name.substring(pos + 1);
if (template == null) {
content = manager.getContentChild(null, root, elemName);
} else {
content = manager.getContentChild(null, template, elemName);
}
if (content instanceof ContentTemplate) {
template = (ContentTemplate) content;
} else {
throw new ContentException("failed to locate template " +
elemName + " in ROOT domain");
}
}
if (template == null) {
throw new ContentException("invalid template import " + name +
" does not refer to page element");
}
if (template.getElement(manager, name) == null) {
return null;
} else {
return new TemplateSource(template, name);
}
}
/**
* Returns the last modification time for a template. This
* method always returns the current system time for all
* template sources except for root templates.
*
* @param source the template source
*
* @return the current system time
*/
public long getLastModified(Object source) {
ContentManager manager;
TemplateSource template;
Date date = null;
if (source instanceof TemplateSource) {
template = (TemplateSource) source;
manager = getPage().getContentManager();
try {
date = template.getLastModified(manager);
} catch (ContentException ignore) {
// Defaults to not use cache
} catch (ContentSecurityException ignore) {
// Defaults to not use cache
}
}
if (date == null) {
return System.currentTimeMillis();
} else {
return date.getTime();
}
}
/**
* Returns a reader for a template.
*
* @param source the template source
* @param encoding the suggested encoding (ignored)
*
* @return a reader for the page element data
*
* @throws IOException if a template couldn't be accessed
* properly
*/
public Reader getReader(Object source, String encoding)
throws IOException {
TemplateSource template;
String data;
try {
if (source == null) {
data = "";
} else if (source instanceof TemplateSource) {
template = (TemplateSource) source;
data = template.getData(getPage().getContentManager());
} else {
data = source.toString();
}
return new StringReader(data);
} catch (ContentException e) {
throw new IOException(e.getMessage());
} catch (ContentSecurityException e) {
throw new IOException(e.getMessage());
}
}
/**
* Closes the FreeMarker template. This method does nothing.
*
* @param source the template source
*/
public void closeTemplateSource(Object source) {
// Nothing to do here
}
}
/**
* A template source element. This class is only a container for
* a template page element.
*
* @author Per Cederberg, <per at percederberg dot net>
* @version 1.0
*/
private static class TemplateSource {
/**
* The unique template content id.
*/
private int id;
/**
* The template page element name.
*/
private String name;
/**
* Creates a new template element.
*
* @param template the content template
* @param name the page element name
*/
public TemplateSource(ContentTemplate template, String name) {
this.id = template.getId();
this.name = name;
}
/**
* Compares this object with another one. This method will
* only return true if the other object points to the same
* template source.
*
* @param obj the object to compare with
*
* @return true if the objects are equal, or
* false otherwise
*/
public boolean equals(Object obj) {
if (obj instanceof TemplateSource) {
return id == ((TemplateSource) obj).id &&
name.equals(((TemplateSource) obj).name);
} else {
return false;
}
}
/**
* Returns the template element last modification date.
*
* @param manager the content manager
*
* @return the last modification date, or
* null if the element shouldn't be cached
*
* @throws ContentException if the template element couldn't
* be read properly
* @throws ContentSecurityException if the anonymous user
* didn't have access to the template
*/
public Date getLastModified(ContentManager manager)
throws ContentException, ContentSecurityException {
Content content;
Date date = null;
content = manager.getContent(null, id);
while (content != null) {
if (!content.isLatestRevision() ||
!content.isPublishedRevision()) {
return null;
}
if (date == null ||
date.compareTo(content.getModifiedDate()) < 0) {
date = content.getModifiedDate();
}
content = content.getParent(manager);
}
return date;
}
/**
* Returns the template element data.
*
* @param manager the content manager
*
* @return the template element data
*
* @throws ContentException if the template element couldn't
* be read properly
* @throws ContentSecurityException if the anonymous user
* didn't have access to the template
*/
public String getData(ContentManager manager)
throws ContentException, ContentSecurityException {
Content content;
content = manager.getContent(null, id);
if (content instanceof ContentTemplate) {
return ((ContentTemplate) content).getElement(manager, name);
} else {
return "";
}
}
}
}