001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/metaobj/tags/sakai_2-4-1/metaobj-util/tool-lib/src/java/org/sakaiproject/spring/util/SpringTool.java $
003: * $Id: SpringTool.java 14230 2006-09-05 18:02:51Z chmaurer@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2004, 2005, 2006 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.spring.util;
021:
022: import org.apache.commons.logging.Log;
023: import org.apache.commons.logging.LogFactory;
024: import org.sakaiproject.tool.api.ActiveTool;
025: import org.sakaiproject.tool.api.Tool;
026: import org.sakaiproject.tool.api.ToolException;
027: import org.sakaiproject.tool.api.ToolSession;
028: import org.sakaiproject.tool.cover.ActiveToolManager;
029: import org.sakaiproject.tool.cover.SessionManager;
030: import org.sakaiproject.util.Web;
031:
032: import javax.servlet.RequestDispatcher;
033: import javax.servlet.ServletConfig;
034: import javax.servlet.ServletException;
035: import javax.servlet.http.HttpServlet;
036: import javax.servlet.http.HttpServletRequest;
037: import javax.servlet.http.HttpServletResponse;
038: import java.io.IOException;
039: import java.util.Enumeration;
040:
041: public class SpringTool extends HttpServlet {
042: /**
043: * Our log (commons).
044: */
045: private static Log M_log = LogFactory.getLog(SpringTool.class);
046:
047: private static final String HELPER_EXT = ".helper";
048:
049: /**
050: * The file extension to get to JSF.
051: */
052: protected static final String JSF_EXT = ".osp";
053:
054: /**
055: * Session attribute to hold the last view visited.
056: */
057: public static final String LAST_VIEW_VISITED = "sakai.jsf.tool.last.view.visited";
058:
059: // TODO: Note, these two values must match those in jsf-app's SakaiViewHandler
060:
061: /**
062: * Request attribute we set to help the return URL know what extension we (or jsf) add (does not need to be in the URL.
063: */
064: public static final String URL_EXT = "sakai.jsf.tool.URL.ext";
065:
066: /**
067: * Request attribute we set to help the return URL know what path we add (does not need to be in the URL.
068: */
069: public static final String URL_PATH = "sakai.jsf.tool.URL.path";
070:
071: /**
072: * The default target, as configured.
073: */
074: protected String m_default = null;
075:
076: /**
077: * if true, we preserve the last visit per placement / user, and use it if we get a request with no path.
078: */
079: protected boolean m_defaultToLastView = true;
080:
081: /**
082: * The folder to the jsf files, as configured. Does not end with a "/".
083: */
084: protected String m_path = null;
085:
086: private static final String HELPER_SESSION_PREFIX = "session.";
087:
088: /**
089: * 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.
090: *
091: * @return The servlet info path target computed for the case of empty actual path.
092: */
093: protected String computeDefaultTarget(boolean lastVisited) {
094: // setup for the default view as configured
095: String target = "/" + m_default;
096:
097: // if we are doing lastVisit and there's a last-visited view, for this tool placement / user, use that
098: if (lastVisited) {
099: ToolSession session = SessionManager
100: .getCurrentToolSession();
101: String last = (String) session
102: .getAttribute(LAST_VIEW_VISITED);
103: if (last != null) {
104: target = last;
105: }
106: }
107:
108: return target;
109: }
110:
111: protected String computeDefaultTarget() {
112: return computeDefaultTarget(m_defaultToLastView);
113: }
114:
115: /**
116: * Shutdown the servlet.
117: */
118: public void destroy() {
119: M_log.info("destroy");
120:
121: super .destroy();
122: }
123:
124: /**
125: * Respond to requests.
126: *
127: * @param req The servlet request.
128: * @param res The servlet response.
129: * @throws ServletException
130: * @throws IOException
131: */
132: protected void dispatch(HttpServletRequest req,
133: HttpServletResponse res) throws ServletException,
134: IOException {
135: // NOTE: this is a simple path dispatching, taking the path as the view id = jsp file name for the view,
136: // with default used if no path and a path prefix as configured.
137: // TODO: need to allow other sorts of dispatching, such as pulling out drill-down ids and making them
138: // available to the JSF
139:
140: // build up the target that will be dispatched to
141: String target = req.getPathInfo();
142:
143: // see if we have a helper request
144: if (sendToHelper(req, res, target)) {
145: return;
146: }
147:
148: // see if we have a resource request - i.e. a path with an extension, and one that is not the JSF_EXT
149: if (isResourceRequest(target)) {
150: // get a dispatcher to the path
151: RequestDispatcher resourceDispatcher = getServletContext()
152: .getRequestDispatcher(target);
153: if (resourceDispatcher != null) {
154: resourceDispatcher.forward(req, res);
155: return;
156: }
157: }
158:
159: if ("Title".equals(req.getParameter("panel"))) {
160: // This allows only one Title JSF for each tool
161: target = "/title.osp";
162: }
163:
164: else {
165: ToolSession session = SessionManager
166: .getCurrentToolSession();
167:
168: if (target == null || "/".equals(target)) {
169: if (!m_defaultToLastView) {
170: // make sure tool session is clean
171: session.clearAttributes();
172: }
173:
174: target = computeDefaultTarget();
175:
176: // make sure it's a valid path
177: if (!target.startsWith("/")) {
178: target = "/" + target;
179: }
180:
181: // now that we've messed with the URL, send a redirect to make it official
182: res.sendRedirect(Web.returnUrl(req, target));
183: return;
184: }
185:
186: // see if we want to change the specifically requested view
187: String newTarget = redirectRequestedTarget(target);
188:
189: // make sure it's a valid path
190: if (!newTarget.startsWith("/")) {
191: newTarget = "/" + newTarget;
192: }
193:
194: if (!newTarget.equals(target)) {
195: // now that we've messed with the URL, send a redirect to make it official
196: res.sendRedirect(Web.returnUrl(req, newTarget));
197: return;
198: }
199: target = newTarget;
200:
201: // store this
202: session.setAttribute(LAST_VIEW_VISITED, target);
203: }
204:
205: // add the configured folder root and extension (if missing)
206: target = m_path + target;
207:
208: // add the default JSF extension (if we have no extension)
209: int lastSlash = target.lastIndexOf("/");
210: int lastDot = target.lastIndexOf(".");
211: if (lastDot < 0 || lastDot < lastSlash) {
212: target += JSF_EXT;
213: }
214:
215: // set the information that can be removed from return URLs
216: req.setAttribute(URL_PATH, m_path);
217: req.setAttribute(URL_EXT, ".jsp");
218:
219: // set the sakai request object wrappers to provide the native, not Sakai set up, URL information
220: // - this assures that the FacesServlet can dispatch to the proper view based on the path info
221: req.setAttribute(Tool.NATIVE_URL, Tool.NATIVE_URL);
222:
223: // TODO: Should setting the HTTP headers be moved up to the portal level as well?
224: res.setContentType("text/html; charset=UTF-8");
225: res.addDateHeader("Expires", System.currentTimeMillis()
226: - (1000L * 60L * 60L * 24L * 365L));
227: res.addDateHeader("Last-Modified", System.currentTimeMillis());
228: res
229: .addHeader("Cache-Control",
230: "no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0");
231: res.addHeader("Pragma", "no-cache");
232:
233: // dispatch to the target
234: M_log.debug("dispatching path: " + req.getPathInfo() + " to: "
235: + target + " context: "
236: + getServletContext().getServletContextName());
237: RequestDispatcher dispatcher = getServletContext()
238: .getRequestDispatcher(target);
239: dispatcher.forward(req, res);
240:
241: // restore the request object
242: req.removeAttribute(Tool.NATIVE_URL);
243: req.removeAttribute(URL_PATH);
244: req.removeAttribute(URL_EXT);
245: }
246:
247: protected boolean sendToHelper(HttpServletRequest req,
248: HttpServletResponse res, String target)
249: throws ToolException {
250: String path = req.getPathInfo();
251: if (path == null) {
252: path = "/";
253: }
254:
255: // 0 parts means the path was just "/", otherwise parts[0] = "", parts[1] = item id, parts[2] if present is "edit"...
256: String[] parts = path.split("/");
257:
258: if (parts.length < 2) {
259: return false;
260: }
261:
262: if (!parts[1].endsWith(HELPER_EXT)) {
263: return false;
264: }
265:
266: ToolSession toolSession = SessionManager
267: .getCurrentToolSession();
268:
269: Enumeration params = req.getParameterNames();
270: while (params.hasMoreElements()) {
271: String paramName = (String) params.nextElement();
272: if (paramName.startsWith(HELPER_SESSION_PREFIX)) {
273: String attributeName = paramName
274: .substring(HELPER_SESSION_PREFIX.length());
275: toolSession.setAttribute(attributeName, req
276: .getParameter(paramName));
277: }
278: }
279:
280: // calc helper id
281: int posEnd = parts[1].lastIndexOf(".");
282:
283: String helperId = target.substring(1, posEnd + 1);
284: ActiveTool helperTool = ActiveToolManager
285: .getActiveTool(helperId);
286:
287: if (toolSession.getAttribute(helperTool.getId()
288: + Tool.HELPER_DONE_URL) == null) {
289: toolSession
290: .setAttribute(helperTool.getId()
291: + Tool.HELPER_DONE_URL, req
292: .getContextPath()
293: + req.getServletPath()
294: + computeDefaultTarget(true));
295: }
296:
297: String context = req.getContextPath() + req.getServletPath()
298: + Web.makePath(parts, 1, 2);
299: String toolPath = Web.makePath(parts, 2, parts.length);
300: helperTool.help(req, res, context, toolPath);
301:
302: return true; // was handled as helper call
303: }
304:
305: /**
306: * Respond to requests.
307: *
308: * @param req The servlet request.
309: * @param res The servlet response.
310: * @throws ServletException
311: * @throws IOException
312: */
313: protected void doGet(HttpServletRequest req, HttpServletResponse res)
314: throws ServletException, IOException {
315: dispatch(req, res);
316: }
317:
318: /**
319: * Respond to requests.
320: *
321: * @param req The servlet request.
322: * @param res The servlet response.
323: * @throws ServletException
324: * @throws IOException
325: */
326: protected void doPost(HttpServletRequest req,
327: HttpServletResponse res) throws ServletException,
328: IOException {
329: dispatch(req, res);
330: }
331:
332: /**
333: * Access the Servlet's information display.
334: *
335: * @return servlet information.
336: */
337: public String getServletInfo() {
338: return "Sakai JSF Tool Servlet";
339: }
340:
341: /**
342: * Initialize the servlet.
343: *
344: * @param config The servlet config.
345: * @throws ServletException
346: */
347: public void init(ServletConfig config) throws ServletException {
348: super .init(config);
349:
350: m_default = config.getInitParameter("default");
351: m_path = config.getInitParameter("path");
352: m_defaultToLastView = "true".equals(config
353: .getInitParameter("default.last.view"));
354:
355: // make sure there is no "/" at the end of the path
356: if (m_path != null && m_path.endsWith("/")) {
357: m_path = m_path.substring(0, m_path.length() - 1);
358: }
359:
360: M_log.info("init: default: " + m_default + " path: " + m_path);
361: }
362:
363: /**
364: * 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.
365: *
366: * @param path The path to check
367: * @return true if the path is a resource request, false if not.
368: */
369: protected boolean isResourceRequest(String path) {
370: // we need some path
371: if ((path == null) || (path.length() == 0)) {
372: return false;
373: }
374:
375: // we need a last dot
376: int pos = path.lastIndexOf(".");
377: if (pos == -1) {
378: return false;
379: }
380:
381: // 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)
382: String ext = path.substring(pos);
383: if (ext.indexOf("/") != -1) {
384: return false;
385: }
386:
387: // we need the ext to not be the JSF_EXT
388: if (ext.equals(JSF_EXT)) {
389: return false;
390: }
391:
392: // ok, it's a resource request
393: return true;
394: }
395:
396: /**
397: * Compute a new target (i.e. the servlet path info, not including folder root or jsf extension) if needed based on the requested target.
398: *
399: * @param target The servlet path info target requested.
400: * @return The target we will actually respond with.
401: */
402: protected String redirectRequestedTarget(String target) {
403: return target;
404: }
405:
406: }
407:
408: /**************************************************************************************************************************************************************************************************************************************************************
409: * $URL: https://source.sakaiproject.org/svn/metaobj/tags/sakai_2-4-1/metaobj-util/tool-lib/src/java/org/sakaiproject/spring/util/SpringTool.java $
410: *************************************************************************************************************************************************************************************************************************************************************/
|