/** * Copyright © 2014 Instituto Superior Técnico * * This file is part of FenixEdu CMS. * * FenixEdu CMS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * FenixEdu CMS 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FenixEdu CMS. If not, see <http://www.gnu.org/licenses/>. */ package org.fenixedu.cms.routing; import com.google.common.base.Strings; import com.google.common.cache.CacheBuilder; import com.mitchellbosecke.pebble.PebbleEngine; import com.mitchellbosecke.pebble.error.LoaderException; import com.mitchellbosecke.pebble.error.PebbleException; import com.mitchellbosecke.pebble.loader.ClasspathLoader; import com.mitchellbosecke.pebble.template.PebbleTemplate; import org.fenixedu.bennu.core.security.Authenticate; import org.fenixedu.bennu.core.util.CoreConfiguration; import org.fenixedu.bennu.portal.domain.PortalConfiguration; import org.fenixedu.cms.CMSConfigurationManager; import org.fenixedu.cms.domain.CMSTheme; import org.fenixedu.cms.domain.Menu; import org.fenixedu.cms.domain.Page; import org.fenixedu.cms.domain.Site; import org.fenixedu.cms.domain.component.Component; import org.fenixedu.cms.domain.wraps.UserWrap; import org.fenixedu.cms.exceptions.ResourceNotFoundException; import org.fenixedu.cms.rendering.CMSExtensions; import org.fenixedu.cms.rendering.TemplateContext; import org.fenixedu.commons.i18n.I18N; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.stream.Collectors; /** * Created by diutsu on 01/03/16. */ public class CMSRenderer { public interface RenderingPageHandler extends BiConsumer<Page, TemplateContext>{} private static final Logger logger = LoggerFactory.getLogger(CMSRenderer.class); private static final Set<RenderingPageHandler> HANDLERS = new HashSet<>(); private final PebbleEngine engine = new PebbleEngine(new ClasspathLoader() { @Override public Reader getReader(String templateName) throws LoaderException { String[] parts = templateName.split("/", 2); if (parts.length != 2) { throw new IllegalArgumentException("Not a valid name: " + templateName); } CMSTheme theme = CMSTheme.forType(parts[0]); if (theme == null) { throw new IllegalArgumentException("Theme " + parts[0] + " not found!"); } byte[] bytes = theme.contentForPath(parts[1]); if (bytes == null) { throw new IllegalArgumentException("Theme " + parts[0] + " does not contain resource '" + parts[1] + '"'); } return new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8); } }); public CMSRenderer() { engine.addExtension(new CMSExtensions()); if (CMSConfigurationManager.isInThemeDevelopmentMode()) { engine.setTemplateCache(null); logger.info("CMS Theme Development Mode enabled!"); } else { engine.setTemplateCache(CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build()); } } void renderCMSPage(final HttpServletRequest req, HttpServletResponse res, Site sites, String pageSlug) throws ServletException, IOException, PebbleException { if (pageSlug.startsWith("/")) { pageSlug = pageSlug.substring(1); } String[] parts = pageSlug.split("/"); String pageName = parts[0]; Page page; if (Strings.isNullOrEmpty(pageName) && sites.getInitialPage() != null) { page = sites.getInitialPage(); } else { page = sites.getPagesSet().stream().filter(p -> pageName.equals(p.getSlug())).findAny().orElse(null); } if (page == null || page.getTemplate() == null) { errorPage(req, res, sites, 404); } else if (!page.getPublished() || !page.getCanViewGroup().isMember(Authenticate.getUser())) { if (Authenticate.isLogged()) { errorPage(req, res, sites, 403); } else { errorPage(req, res, sites, 401); } } else { try { renderPage(req, pageSlug, res, sites, page, parts); } catch (ResourceNotFoundException e) { errorPage(req, res, sites, 404); } } } private void renderPage(final HttpServletRequest req, String reqPagePath, HttpServletResponse res, Site site, Page page, String[] requestContext) throws PebbleException, IOException { TemplateContext global = new TemplateContext(); global.setRequestContext(requestContext); for (String key : req.getParameterMap().keySet()) { global.put(key, req.getParameter(key)); } global.put("page", makePageWrapper(page)); populateSiteInfo(req, page, site, global); List<TemplateContext> components = new ArrayList<TemplateContext>(); for (Component component : page.getComponentsSet()) { TemplateContext local = new TemplateContext(); component.handle(page, local, global); components.add(local); } global.put("components", components); for (RenderingPageHandler handler : HANDLERS){ handler.accept(page,global); } CMSTheme theme = site.getTheme(); PebbleTemplate compiledTemplate = engine.getTemplate(theme.getType() + "/" + page.getTemplate().getFilePath()); res.setStatus(200); res.setContentType("text/html"); compiledTemplate.evaluate(res.getWriter(), global); } private void populateSiteInfo(final HttpServletRequest req, Page page, Site site, TemplateContext context) { context.put("request", makeRequestWrapper(req)); context.put("app", makeAppWrapper()); context.put("site", site.makeWrap()); context.put("menus", makeMenuWrapper(site, page)); context.put("staticDir", site.getStaticDirectory()); context.put("devMode", CoreConfiguration.getConfiguration().developmentMode()); } private List<Object> makeMenuWrapper(Site site, Page page) { ArrayList<Object> result = site.getOrderedMenusSet().stream().map(menu -> page == null ? menu.makeWrap() : menu.makeWrap(page)) .collect(Collectors.toCollection(ArrayList::new)); return result; } private Map<String, Object> makeAppWrapper() { HashMap<String, Object> result = new HashMap<String, Object>(); PortalConfiguration configuration = PortalConfiguration.getInstance(); result.put("title", configuration.getApplicationTitle()); result.put("subtitle", configuration.getApplicationSubTitle()); result.put("copyright", configuration.getApplicationCopyright()); result.put("support", configuration.getSupportEmailAddress()); result.put("locale", I18N.getLocale()); result.put("supportedLocales", CoreConfiguration.supportedLocales()); return result; } private Map<String, Object> makePageWrapper(Page page) { HashMap<String, Object> result = new HashMap<String, Object>(); result.put("name", page.getName()); result.put("user", new UserWrap(page.getCreatedBy())); result.put("createdBy", new UserWrap(page.getCreatedBy())); result.put("creationDate", page.getCreationDate()); return result; } private Map<String, Object> makeRequestWrapper(HttpServletRequest req) { HashMap<String, Object> result = new HashMap<String, Object>(); result.put("user", new UserWrap(Authenticate.getUser())); result.put("method", req.getMethod()); result.put("protocol", req.getProtocol()); result.put("url", getFullURL(req)); result.put("contentType", req.getContentType()); result.put("contextPath", req.getContextPath()); return result; } void errorPage(final HttpServletRequest req, HttpServletResponse res, Site site, int errorCode) throws ServletException, IOException { CMSTheme cmsTheme = site.getTheme(); if (cmsTheme != null && cmsTheme.definesPath(errorCode + ".html")) { try { PebbleTemplate compiledTemplate = engine.getTemplate(cmsTheme.getType() + "/" + errorCode + ".html"); TemplateContext global = new TemplateContext(); populateSiteInfo(req, null, site, global); res.setStatus(errorCode); res.setContentType("text/html"); compiledTemplate.evaluate(res.getWriter(), global); } catch (PebbleException e) { throw new ServletException("Could not render error page for " + errorCode); } } else { res.sendError(errorCode, req.getRequestURI()); } } private static String getFullURL(HttpServletRequest request) { StringBuffer requestURL = request.getRequestURL(); String queryString = request.getQueryString(); if (queryString != null) { requestURL.append('?').append(queryString); } return requestURL.toString(); } public void invalidateEntry(String key) { engine.getTemplateCache().invalidate(key); } public static void addHandler(RenderingPageHandler handler){ HANDLERS.add(handler); } public static void removeHandler(RenderingPageHandler handler){ HANDLERS.remove(handler); } }