package org.fenixedu.bennu.portal.servlet;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.fenixedu.bennu.alerts.Alert;
import org.fenixedu.bennu.core.security.Authenticate;
import org.fenixedu.bennu.core.util.CoreConfiguration;
import org.fenixedu.bennu.portal.BennuPortalConfiguration;
import org.fenixedu.bennu.portal.domain.MenuFunctionality;
import org.fenixedu.bennu.portal.domain.MenuItem;
import org.fenixedu.bennu.portal.domain.PortalConfiguration;
import org.fenixedu.commons.i18n.I18N;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.PebbleEngine.Builder;
import com.mitchellbosecke.pebble.error.LoaderException;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.loader.ClasspathLoader;
import com.mitchellbosecke.pebble.template.PebbleTemplate;
/**
* Filter that injects layout for {@link MenuFunctionality}s that require it.
*
* @author João Carvalho (joao.pedro.carvalho@tecnico.ulisboa.pt)
*
*/
@WebFilter("/*")
public class PortalLayoutInjector implements Filter {
private static final Logger logger = LoggerFactory.getLogger(PortalLayoutInjector.class);
private static final String SKIP_LAYOUT_INJECTION = "$$SKIP_LAYOUT_INJECTION$$";
private PebbleEngine engine;
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
final ServletContext servletContext = filterConfig.getServletContext();
this.engine = new Builder().extension(new PortalExtension(servletContext)).loader(new ClasspathLoader() {
@Override
public Reader getReader(String templateName) throws LoaderException {
// Try loading the specified template...
InputStream stream = servletContext.getResourceAsStream("/themes/" + templateName + ".html");
if (stream != null) {
return new InputStreamReader(stream, StandardCharsets.UTF_8);
} else {
// ... fallback to default if it doesn't exist
logger.warn("Could not find template named {}, falling back to default!", templateName);
return new InputStreamReader(servletContext.getResourceAsStream("/themes/"
+ PortalConfiguration.getInstance().getTheme() + "/default.html"), StandardCharsets.UTF_8);
}
}
}).cacheActive(!BennuPortalConfiguration.getConfiguration().themeDevelopmentMode()).build();
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// Wrap the response so it may be later rewritten if necessary
PortalResponseWrapper wrapper = new PortalResponseWrapper(response);
chain.doFilter(request, wrapper);
Alert.flush(request, response);
MenuFunctionality functionality = BennuPortalDispatcher.getSelectedFunctionality(request);
if (functionality != null && wrapper.hasData() && request.getAttribute(SKIP_LAYOUT_INJECTION) == null) {
PortalBackend backend = PortalBackendRegistry.getPortalBackend(functionality.getProvider());
if (backend.requiresServerSideLayout()) {
String body = wrapper.getContent();
try {
PortalConfiguration config = PortalConfiguration.getInstance();
Map<String, Object> ctx = new HashMap<>();
List<MenuItem> path = functionality.getPathFromRoot();
ctx.put("loggedUser", Authenticate.getUser());
ctx.put("body", body);
ctx.put("functionality", functionality);
ctx.put("config", config);
ctx.put("topLevelMenu", config.getMenu().getUserMenuStream());
ctx.put("contextPath", request.getContextPath());
ctx.put("devMode", CoreConfiguration.getConfiguration().developmentMode());
ctx.put("pathFromRoot", path);
ctx.put("selectedTopLevel", path.get(0));
ctx.put("locales", CoreConfiguration.supportedLocales());
ctx.put("currentLocale", I18N.getLocale());
ctx.put("alerts", Alert.getAlertsAsJson(request, response));
PebbleTemplate template = engine.getTemplate(config.getTheme() + "/" + functionality.resolveLayout());
template.evaluate(response.getWriter(), ctx, I18N.getLocale());
} catch (PebbleException e) {
throw new ServletException("Could not render template!", e);
}
} else {
wrapper.flushBuffer();
}
} else {
wrapper.flushBuffer();
}
}
/**
* Requests that layout injection be skipped on the given request
*
* @param request
* The request for which layouting should be skipped
* @throws NullPointerException
* If the given {@code request} is {@code null}
*/
public static void skipLayoutOn(HttpServletRequest request) {
request.setAttribute(SKIP_LAYOUT_INJECTION, SKIP_LAYOUT_INJECTION);
}
@Override
public void destroy() {
}
}