org.sakaiproject.jsf2.util.JsfTool Maven / Gradle / Ivy
/**********************************************************************************
Copyright (c) 2018 Apereo Foundation
Licensed under the Educational Community License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://opensource.org/licenses/ecl2
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
**********************************************************************************/
package org.sakaiproject.jsf2.util;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.sakaiproject.tool.api.ToolSession;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.tool.api.Tool;
import org.sakaiproject.util.Web;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.component.api.ServerConfigurationService;
/**
*
* Sakai Servlet to use for all JSF tools.
*
*/
@Slf4j
public class JsfTool extends HttpServlet
{
/** The file extension to get to JSF. */
protected static final String JSF_EXT = ".jsf";
protected static final String [] JSF_FACELETS_EXT = new String[] {".jsp",".xhtml",".jspx"};
/** Session attribute to hold the last view visited. */
public static final String LAST_VIEW_VISITED = "sakai.jsf.tool.last.view.visited";
// TODO: Note, these two values must match those in jsf-app's SakaiViewHandler
/** Request attribute we set to help the return URL know what extension we (or jsf) add (does not need to be in the URL. */
public static final String URL_EXT = "sakai.jsf.tool.URL.ext";
/** Request attribute we set to help the return URL know what path we add (does not need to be in the URL. */
public static final String URL_PATH = "sakai.jsf.tool.URL.path";
/** The default target, as configured. */
protected String m_default = null;
/** if true, we preserve the last visit per placement / user, and use it if we get a request with no path. */
protected boolean m_defaultToLastView = true;
/** The folder to the jsf files, as configured. Does not end with a "/". */
protected String m_path = null;
/**
* Compute a target (i.e. the servlet path info, not including folder root or jsf extension) for the case of the actual path being empty.
*
* @return The servlet info path target computed for the case of empty actual path.
*/
protected String computeDefaultTarget()
{
// setup for the default view as configured
String target = "/" + m_default;
// if we are doing lastVisit and there's a last-visited view, for this tool placement / user, use that
if (m_defaultToLastView)
{
ToolSession session = SessionManager.getCurrentToolSession();
String last = (String) session.getAttribute(LAST_VIEW_VISITED);
if (last != null)
{
target = last;
}
}
return target;
}
/**
* Shutdown the servlet.
*/
public void destroy()
{
log.info("destroy");
super.destroy();
}
/**
* Respond to requests.
*
* @param req
* The servlet request.
* @param res
* The servlet response.
* @throws ServletException.
* @throws IOException.
*/
protected void dispatch(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
// NOTE: this is a simple path dispatching, taking the path as the view id = jsp file name for the view,
// with default used if no path and a path prefix as configured.
// TODO: need to allow other sorts of dispatching, such as pulling out drill-down ids and making them
// available to the JSF
// build up the target that will be dispatched to
String target = req.getPathInfo();
// see if we have a resource request - i.e. a path with an extension, and one that is not the JSF_EXT
if (isResourceRequest(target))
{
// get a dispatcher to the path
RequestDispatcher resourceDispatcher = getServletContext().getRequestDispatcher(target);
if (resourceDispatcher != null)
{
resourceDispatcher.forward(req, res);
return;
}
}
if ("Title".equals(req.getParameter("panel")))
{
// This allows only one Title JSF for each tool
target = "/title.jsf";
}
else
{
ToolSession session = SessionManager.getCurrentToolSession();
if (target == null || "/".equals(target))
{
target = computeDefaultTarget();
// make sure it's a valid path
if (!target.startsWith("/"))
{
target = "/" + target;
}
// now that we've messed with the URL, send a redirect to make it official
res.sendRedirect(Web.returnUrl(req, target));
return;
}
// see if we want to change the specifically requested view
String newTarget = redirectRequestedTarget(target);
// make sure it's a valid path
if (!newTarget.startsWith("/"))
{
newTarget = "/" + newTarget;
}
if (!newTarget.equals(target))
{
// now that we've messed with the URL, send a redirect to make it official
res.sendRedirect(Web.returnUrl(req, newTarget));
return;
}
target = newTarget;
// store this
if (m_defaultToLastView)
{
session.setAttribute(LAST_VIEW_VISITED, target);
}
}
// add the configured folder root and extension (if missing)
target = m_path + target;
// add the default JSF extension (if we have no extension)
int lastSlash = target.lastIndexOf("/");
int lastDot = target.lastIndexOf(".");
if (lastDot < 0 || lastDot < lastSlash)
{
target += JSF_EXT;
}
// set the information that can be removed from return URLs
req.setAttribute(URL_PATH, m_path);
req.setAttribute(URL_EXT, JSF_FACELETS_EXT);
// set the sakai request object wrappers to provide the native, not Sakai set up, URL information
// - this assures that the FacesServlet can dispatch to the proper view based on the path info
req.setAttribute(Tool.NATIVE_URL, Tool.NATIVE_URL);
// TODO: Should setting the HTTP headers be moved up to the portal level as well?
res.setContentType("text/html; charset=UTF-8");
res.addDateHeader("Expires", System.currentTimeMillis() - (1000L * 60L * 60L * 24L * 365L));
res.addDateHeader("Last-Modified", System.currentTimeMillis());
res.addHeader("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
res.addHeader("Pragma", "no-cache");
// dispatch to the target
log.debug("dispatching path: " + req.getPathInfo() + " to: " + target + " context: "
+ getServletContext().getServletContextName());
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(target);
dispatcher.forward(req, res);
// restore the request object
req.removeAttribute(Tool.NATIVE_URL);
req.removeAttribute(URL_PATH);
req.removeAttribute(URL_EXT);
}
/**
* Respond to requests.
*
* @param req
* The servlet request.
* @param res
* The servlet response.
* @throws ServletException.
* @throws IOException.
*/
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
dispatch(req, res);
}
/**
* Respond to requests.
*
* @param req
* The servlet request.
* @param res
* The servlet response.
* @throws ServletException.
* @throws IOException.
*/
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException
{
dispatch(req, res);
}
/**
* Access the Servlet's information display.
*
* @return servlet information.
*/
public String getServletInfo()
{
return "Sakai JSF Tool Servlet";
}
/**
* Initialize the servlet.
*
* @param config
* The servlet config.
* @throws ServletException
*/
public void init(ServletConfig config) throws ServletException
{
super.init(config);
m_default = config.getInitParameter("default");
m_path = config.getInitParameter("path");
m_defaultToLastView = "true".equals(config.getInitParameter("default.last.view"));
// make sure there is no "/" at the end of the path
if (m_path != null && m_path.endsWith("/"))
{
m_path = m_path.substring(0, m_path.length() - 1);
}
log.info("Servlet {} configured with default: {} path: {}", config.getServletName(), m_default, m_path);
}
/**
* Recognize a path that is a resource request. It must have an "extension", i.e. a dot followed by characters that do not include a slash.
*
* @param path
* The path to check
* @return true if the path is a resource request, false if not.
*/
protected boolean isResourceRequest(String path)
{
// we need some path
if ((path == null) || (path.length() == 0)) return false;
// we need a last dot
int pos = path.lastIndexOf(".");
if (pos == -1) return false;
// we need that last dot to be the end of the path, not burried in the path somewhere (i.e. no more slashes after the last dot)
String ext = path.substring(pos);
if (ext.indexOf("/") != -1) return false;
// we need the ext to not be the JSF_EXT
if (ext.equals(JSF_EXT)) return false;
// ok, it's a resource request
return true;
}
/**
* Compute a new target (i.e. the servlet path info, not including folder root or jsf extension) if needed based on the requested target.
*
* @param target
* The servlet path info target requested.
* @return The target we will actually respond with.
*/
protected String redirectRequestedTarget(String target)
{
return target;
}
}