001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/jsf/tags/sakai_2-4-1/tool/src/java/org/sakaiproject/jsf/util/JsfTool.java $
003: * $Id: JsfTool.java 9278 2006-05-10 23:29:21Z ray@media.berkeley.edu $
004: **********************************************************************************
005: *
006: * Copyright (c) 2005 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.jsf.util;
021:
022: import java.io.IOException;
023:
024: import javax.servlet.RequestDispatcher;
025: import javax.servlet.ServletConfig;
026: import javax.servlet.ServletException;
027: import javax.servlet.http.HttpServlet;
028: import javax.servlet.http.HttpServletRequest;
029: import javax.servlet.http.HttpServletResponse;
030:
031: import org.apache.commons.logging.Log;
032: import org.apache.commons.logging.LogFactory;
033: import org.sakaiproject.tool.api.ToolSession;
034: import org.sakaiproject.tool.cover.SessionManager;
035: import org.sakaiproject.tool.api.Tool;
036: import org.sakaiproject.util.Web;
037:
038: /**
039: * <p>
040: * Sakai Servlet to use for all JSF tools.
041: * </p>
042: *
043: * @author University of Michigan, Sakai Software Development Team
044: * @version $Revision: 9278 $
045: */
046: public class JsfTool extends HttpServlet {
047: /** Our log (commons). */
048: private static Log M_log = LogFactory.getLog(JsfTool.class);
049:
050: /** The file extension to get to JSF. */
051: protected static final String JSF_EXT = ".jsf";
052:
053: /** Session attribute to hold the last view visited. */
054: public static final String LAST_VIEW_VISITED = "sakai.jsf.tool.last.view.visited";
055:
056: // TODO: Note, these two values must match those in jsf-app's SakaiViewHandler
057:
058: /** Request attribute we set to help the return URL know what extension we (or jsf) add (does not need to be in the URL. */
059: public static final String URL_EXT = "sakai.jsf.tool.URL.ext";
060:
061: /** Request attribute we set to help the return URL know what path we add (does not need to be in the URL. */
062: public static final String URL_PATH = "sakai.jsf.tool.URL.path";
063:
064: /** The default target, as configured. */
065: protected String m_default = null;
066:
067: /** if true, we preserve the last visit per placement / user, and use it if we get a request with no path. */
068: protected boolean m_defaultToLastView = true;
069:
070: /** The folder to the jsf files, as configured. Does not end with a "/". */
071: protected String m_path = null;
072:
073: /**
074: * 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.
075: *
076: * @return The servlet info path target computed for the case of empty actual path.
077: */
078: protected String computeDefaultTarget() {
079: // setup for the default view as configured
080: String target = "/" + m_default;
081:
082: // if we are doing lastVisit and there's a last-visited view, for this tool placement / user, use that
083: if (m_defaultToLastView) {
084: ToolSession session = SessionManager
085: .getCurrentToolSession();
086: String last = (String) session
087: .getAttribute(LAST_VIEW_VISITED);
088: if (last != null) {
089: target = last;
090: }
091: }
092:
093: return target;
094: }
095:
096: /**
097: * Shutdown the servlet.
098: */
099: public void destroy() {
100: M_log.info("destroy");
101:
102: super .destroy();
103: }
104:
105: /**
106: * Respond to requests.
107: *
108: * @param req
109: * The servlet request.
110: * @param res
111: * The servlet response.
112: * @throws ServletException.
113: * @throws IOException.
114: */
115: protected void dispatch(HttpServletRequest req,
116: HttpServletResponse res) throws ServletException,
117: IOException {
118: // NOTE: this is a simple path dispatching, taking the path as the view id = jsp file name for the view,
119: // with default used if no path and a path prefix as configured.
120: // TODO: need to allow other sorts of dispatching, such as pulling out drill-down ids and making them
121: // available to the JSF
122:
123: // build up the target that will be dispatched to
124: String target = req.getPathInfo();
125:
126: // see if we have a resource request - i.e. a path with an extension, and one that is not the JSF_EXT
127: if (isResourceRequest(target)) {
128: // get a dispatcher to the path
129: RequestDispatcher resourceDispatcher = getServletContext()
130: .getRequestDispatcher(target);
131: if (resourceDispatcher != null) {
132: resourceDispatcher.forward(req, res);
133: return;
134: }
135: }
136:
137: if ("Title".equals(req.getParameter("panel"))) {
138: // This allows only one Title JSF for each tool
139: target = "/title.jsf";
140: }
141:
142: else {
143: ToolSession session = SessionManager
144: .getCurrentToolSession();
145:
146: if (target == null || "/".equals(target)) {
147: target = computeDefaultTarget();
148:
149: // make sure it's a valid path
150: if (!target.startsWith("/")) {
151: target = "/" + target;
152: }
153:
154: // now that we've messed with the URL, send a redirect to make it official
155: res.sendRedirect(Web.returnUrl(req, target));
156: return;
157: }
158:
159: // see if we want to change the specifically requested view
160: String newTarget = redirectRequestedTarget(target);
161:
162: // make sure it's a valid path
163: if (!newTarget.startsWith("/")) {
164: newTarget = "/" + newTarget;
165: }
166:
167: if (!newTarget.equals(target)) {
168: // now that we've messed with the URL, send a redirect to make it official
169: res.sendRedirect(Web.returnUrl(req, newTarget));
170: return;
171: }
172: target = newTarget;
173:
174: // store this
175: if (m_defaultToLastView) {
176: session.setAttribute(LAST_VIEW_VISITED, target);
177: }
178: }
179:
180: // add the configured folder root and extension (if missing)
181: target = m_path + target;
182:
183: // add the default JSF extension (if we have no extension)
184: int lastSlash = target.lastIndexOf("/");
185: int lastDot = target.lastIndexOf(".");
186: if (lastDot < 0 || lastDot < lastSlash) {
187: target += JSF_EXT;
188: }
189:
190: // set the information that can be removed from return URLs
191: req.setAttribute(URL_PATH, m_path);
192: req.setAttribute(URL_EXT, ".jsp");
193:
194: // set the sakai request object wrappers to provide the native, not Sakai set up, URL information
195: // - this assures that the FacesServlet can dispatch to the proper view based on the path info
196: req.setAttribute(Tool.NATIVE_URL, Tool.NATIVE_URL);
197:
198: // TODO: Should setting the HTTP headers be moved up to the portal level as well?
199: res.setContentType("text/html; charset=UTF-8");
200: res.addDateHeader("Expires", System.currentTimeMillis()
201: - (1000L * 60L * 60L * 24L * 365L));
202: res.addDateHeader("Last-Modified", System.currentTimeMillis());
203: res
204: .addHeader("Cache-Control",
205: "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
206: res.addHeader("Pragma", "no-cache");
207:
208: // dispatch to the target
209: M_log.debug("dispatching path: " + req.getPathInfo() + " to: "
210: + target + " context: "
211: + getServletContext().getServletContextName());
212: RequestDispatcher dispatcher = getServletContext()
213: .getRequestDispatcher(target);
214: dispatcher.forward(req, res);
215:
216: // restore the request object
217: req.removeAttribute(Tool.NATIVE_URL);
218: req.removeAttribute(URL_PATH);
219: req.removeAttribute(URL_EXT);
220: }
221:
222: /**
223: * Respond to requests.
224: *
225: * @param req
226: * The servlet request.
227: * @param res
228: * The servlet response.
229: * @throws ServletException.
230: * @throws IOException.
231: */
232: protected void doGet(HttpServletRequest req, HttpServletResponse res)
233: throws ServletException, IOException {
234: dispatch(req, res);
235: }
236:
237: /**
238: * Respond to requests.
239: *
240: * @param req
241: * The servlet request.
242: * @param res
243: * The servlet response.
244: * @throws ServletException.
245: * @throws IOException.
246: */
247: protected void doPost(HttpServletRequest req,
248: HttpServletResponse res) throws ServletException,
249: IOException {
250: dispatch(req, res);
251: }
252:
253: /**
254: * Access the Servlet's information display.
255: *
256: * @return servlet information.
257: */
258: public String getServletInfo() {
259: return "Sakai JSF Tool Servlet";
260: }
261:
262: /**
263: * Initialize the servlet.
264: *
265: * @param config
266: * The servlet config.
267: * @throws ServletException
268: */
269: public void init(ServletConfig config) throws ServletException {
270: super .init(config);
271:
272: m_default = config.getInitParameter("default");
273: m_path = config.getInitParameter("path");
274: m_defaultToLastView = "true".equals(config
275: .getInitParameter("default.last.view"));
276:
277: // make sure there is no "/" at the end of the path
278: if (m_path != null && m_path.endsWith("/")) {
279: m_path = m_path.substring(0, m_path.length() - 1);
280: }
281:
282: M_log.info("init: default: " + m_default + " path: " + m_path);
283: }
284:
285: /**
286: * 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.
287: *
288: * @param path
289: * The path to check
290: * @return true if the path is a resource request, false if not.
291: */
292: protected boolean isResourceRequest(String path) {
293: // we need some path
294: if ((path == null) || (path.length() == 0))
295: return false;
296:
297: // we need a last dot
298: int pos = path.lastIndexOf(".");
299: if (pos == -1)
300: return false;
301:
302: // 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)
303: String ext = path.substring(pos);
304: if (ext.indexOf("/") != -1)
305: return false;
306:
307: // we need the ext to not be the JSF_EXT
308: if (ext.equals(JSF_EXT))
309: return false;
310:
311: // ok, it's a resource request
312: return true;
313: }
314:
315: /**
316: * Compute a new target (i.e. the servlet path info, not including folder root or jsf extension) if needed based on the requested target.
317: *
318: * @param target
319: * The servlet path info target requested.
320: * @return The target we will actually respond with.
321: */
322: protected String redirectRequestedTarget(String target) {
323: return target;
324: }
325:
326: }
|