001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/velocity/tags/sakai_2-4-1/tool/src/java/org/sakaiproject/cheftool/ToolServlet.java $
003: * $Id: ToolServlet.java 29059 2007-04-18 14:10:55Z ajpoland@iupui.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 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.cheftool;
021:
022: import java.io.IOException;
023: import java.lang.reflect.InvocationTargetException;
024: import java.lang.reflect.Method;
025: import java.util.Enumeration;
026:
027: import javax.servlet.ServletException;
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.cheftool.api.Alert;
034: import org.sakaiproject.cheftool.api.Menu;
035: import org.sakaiproject.cheftool.menu.MenuImpl;
036: import org.sakaiproject.event.api.SessionState;
037: import org.sakaiproject.event.cover.UsageSessionService;
038: import org.sakaiproject.tool.api.ActiveTool;
039: import org.sakaiproject.tool.api.Session;
040: import org.sakaiproject.tool.api.Tool;
041: import org.sakaiproject.tool.api.ToolException;
042: import org.sakaiproject.tool.api.ToolSession;
043: import org.sakaiproject.tool.cover.ActiveToolManager;
044: import org.sakaiproject.tool.cover.SessionManager;
045: import org.sakaiproject.tool.cover.ToolManager;
046: import org.sakaiproject.util.ParameterParser;
047: import org.sakaiproject.util.Web;
048: import org.sakaiproject.vm.ActionURL;
049:
050: /**
051: * <p>
052: * ToolServlet is a Servlet that support CHEF tools.
053: * </p>
054: * <p>
055: * Extending VmServlet provides support for component location and use of the Velocity Template Engine.
056: * </p>
057: */
058: public abstract class ToolServlet extends VmServlet {
059: /** Our logger. */
060: private static Log M_log = LogFactory.getLog(ToolServlet.class);
061:
062: /** ToolSession attribute name holding the helper id, if we are in helper mode. NOTE: promote to Tool -ggolden */
063: protected static final String HELPER_ID = "sakai.tool.helper.id";
064:
065: /** used to pull helper info from the request url path **/
066: private static final String HELPER_EXT = ".helper";
067:
068: /**
069: * Add some standard references to the vm context.
070: *
071: * @param request
072: * The render request.
073: * @param response
074: * The render response.
075: */
076: protected void setVmStdRef(HttpServletRequest request,
077: HttpServletResponse response) {
078: super .setVmStdRef(request, response);
079:
080: // add the tool mode
081: setVmReference("sakai_toolMode", getToolMode(request), request);
082:
083: // add alert
084: setVmReference("sakai_alert", getAlert(request), request);
085:
086: // add menu
087: setVmReference("sakai_menu", getMenu(request), request);
088:
089: } // setVmStdRef
090:
091: /**********************************************************************************************************************************************************************************************************************************************************
092: * tool mode support ******************************************************************************* Tool mode is stored in the servlet session state.
093: *********************************************************************************************************************************************************************************************************************************************************/
094:
095: /** The mode value when no mode has been set. */
096: protected final String TOOL_MODE_DEFAULT = "Default";
097:
098: /** The mode attribute name base - postfix with the portlet mode. */
099: protected final String TOOL_MODE_ATTR = "sakai.toolMode";
100:
101: /** The special panel name for the title. */
102: protected final String TITLE_PANEL = "Title";
103:
104: /** The special panel name for the main. */
105: protected final String MAIN_PANEL = "Main";
106:
107: /**
108: * Set the tool mode.
109: *
110: * @param toolMode
111: * The new tool mode.
112: * @param req
113: * The portlet request.
114: */
115: protected void setToolMode(String toolMode, HttpServletRequest req) {
116: // update the attribute in session state
117: getState(req).setAttribute(TOOL_MODE_ATTR, toolMode);
118:
119: } // setToolMode
120:
121: /**
122: * Access the tool mode for the current Portlet mode.
123: *
124: * @return the tool mode for the current Portlet mode.
125: * @param req
126: * The portlet request.
127: */
128: protected String getToolMode(HttpServletRequest req) {
129: String toolMode = (String) getState(req).getAttribute(
130: TOOL_MODE_ATTR);
131:
132: // use the default mode if nothing set
133: if (toolMode == null) {
134: toolMode = TOOL_MODE_DEFAULT;
135: }
136:
137: return toolMode;
138:
139: } // getToolMode
140:
141: /**
142: * Respond to a request by dispatching to a portlet like "do" method based on the portlet mode and tool mode
143: */
144: protected void doPost(HttpServletRequest req,
145: HttpServletResponse res) throws ServletException {
146: doGet(req, res);
147:
148: } // doPost
149:
150: /**
151: * Respond to a request by dispatching to a portlet like "do" method based on the portlet mode and tool mode
152: */
153: protected void doGet(HttpServletRequest req, HttpServletResponse res)
154: throws ServletException {
155: // get the panel
156: String panel = ((ParameterParser) req.getAttribute(ATTR_PARAMS))
157: .getString(ActionURL.PARAM_PANEL);
158: if (panel == null || panel.equals("") || panel.equals("null"))
159: panel = MAIN_PANEL;
160:
161: // HELPER_ID needs the panel appended
162: String helperId = HELPER_ID + panel;
163:
164: // detect a helper done
165: ToolSession toolSession = SessionManager
166: .getCurrentToolSession();
167: String helper = ((ParameterParser) req
168: .getAttribute(ATTR_PARAMS)).getString(helperId);
169: if (helper != null) {
170: // clear our helper id indicator from session
171: toolSession.removeAttribute(helperId);
172:
173: // redirect to the same URL w/o the helper done indication, otherwise this is left in the browser and can be re-processed later
174: String newUrl = req.getContextPath()
175: + req.getServletPath()
176: + (req.getPathInfo() == null ? "" : req
177: .getPathInfo()) + "?"
178: + ActionURL.PARAM_PANEL + "=" + panel;
179: try {
180: res.sendRedirect(newUrl);
181: } catch (IOException e) {
182: M_log
183: .warn("redirecting after helper done detection to: "
184: + newUrl + " : " + e.toString());
185: }
186: return;
187: }
188:
189: // get the sakai.tool.helper.id helper id from the tool session
190: // if defined and it's not our tool id, we need to defer to the helper
191: helper = (String) toolSession.getAttribute(helperId);
192: Tool me = ToolManager.getCurrentTool();
193: if ((helper != null) && (!helper.equals(me.getId()))) {
194: toolSession.removeAttribute(helperId);
195: String newUrl = req.getContextPath()
196: + req.getServletPath()
197: + (req.getPathInfo() == null ? "" : req
198: .getPathInfo()) + "/" + helper + HELPER_EXT;
199:
200: try {
201: res.sendRedirect(newUrl);
202: } catch (IOException e) {
203: M_log.warn("redirecting to helper to: " + newUrl
204: + " : " + e.toString());
205: }
206: return;
207: }
208:
209: // see if we have a helper request
210: if (sendToHelper(req, res, req.getPathInfo())) {
211: return;
212: }
213:
214: // init or update the session state
215: prepState(req, res);
216:
217: // see if there's an action to process
218: processAction(req, res);
219:
220: // if not redirected
221: if (!res.isCommitted()) {
222: // dispatch
223: toolModeDispatch("doView", getToolMode(req), req, res);
224: }
225:
226: } // doGet
227:
228: /**
229: * Setup for a helper tool - all subsequent requests will be directed there, till the tool is done.
230: *
231: * @param helperId
232: * The helper tool id.
233: */
234: protected void startHelper(HttpServletRequest req, String helperId,
235: String panel) {
236: if (panel == null)
237: panel = MAIN_PANEL;
238:
239: ToolSession toolSession = SessionManager
240: .getCurrentToolSession();
241: toolSession.setAttribute(HELPER_ID + panel, helperId);
242:
243: // the done URL - this url and the extra parameter to indicate done
244: // also make sure the panel is indicated - assume that it needs to be main, assuming that helpers are taking over the entire tool response
245: String doneUrl = req.getContextPath() + req.getServletPath()
246: + (req.getPathInfo() == null ? "" : req.getPathInfo())
247: + "?" + HELPER_ID + panel + "=done" + "&"
248: + ActionURL.PARAM_PANEL + "=" + panel;
249:
250: toolSession.setAttribute(helperId + Tool.HELPER_DONE_URL,
251: doneUrl);
252: }
253:
254: /**
255: * Setup for a helper tool - all subsequent requests will be directed there, till the tool is done.
256: *
257: * @param helperId
258: * The helper tool id.
259: */
260: protected void startHelper(HttpServletRequest req, String helperId) {
261: startHelper(req, helperId, MAIN_PANEL);
262: }
263:
264: /**
265: * Dispatch to a "do" method based on reflection.
266: *
267: * @param methodBase
268: * The base name of the method to call.
269: * @param methodExt
270: * The end name of the method to call.
271: * @param req
272: * The HttpServletRequest.
273: * @param res
274: * The HttpServletResponse
275: * @throws PortletExcption,
276: * IOException, just like the "do" methods.
277: */
278: protected void toolModeDispatch(String methodBase,
279: String methodExt, HttpServletRequest req,
280: HttpServletResponse res) throws ToolException {
281: String methodName = null;
282: try {
283: // the method signature
284: Class[] signature = new Class[2];
285: signature[0] = HttpServletRequest.class;
286: signature[1] = HttpServletResponse.class;
287:
288: // the method name
289: methodName = methodBase + methodExt;
290:
291: // find a method of this class with this name and signature
292: Method method = getClass().getMethod(methodName, signature);
293:
294: // the parameters
295: Object[] args = new Object[2];
296: args[0] = req;
297: args[1] = res;
298:
299: // make the call
300: method.invoke(this , args);
301:
302: } catch (NoSuchMethodException e) {
303: throw new ToolException(e);
304: } catch (IllegalAccessException e) {
305: throw new ToolException(e);
306: } catch (InvocationTargetException e) {
307: throw new ToolException(e);
308: }
309:
310: } // toolModeDispatch
311:
312: /**********************************************************************************************************************************************************************************************************************************************************
313: * action model support ******************************************************************************* Dispatch by reflection to the method named in the value of the "sakai_action" parameter. Option to use "sakai_action_ACTION" to dispatch to ACTION.
314: *********************************************************************************************************************************************************************************************************************************************************/
315:
316: /** The request parameter name root that has the action name following. */
317: protected final static String PARAM_ACTION_COMBO = "sakai_action_";
318:
319: /** The request parameter name whose value is the action. */
320: protected final static String PARAM_ACTION = "sakai_action";
321:
322: /**
323: * Process a Portlet action.
324: */
325: protected void processAction(HttpServletRequest req,
326: HttpServletResponse res) {
327: // see if there's an action parameter, whose value has the action to use
328: String action = ((ParameterParser) req
329: .getAttribute(ATTR_PARAMS)).getString(PARAM_ACTION);
330:
331: // if that's not present, see if there's a combination name with the action encoded in the name
332: if (action == null) {
333: Enumeration names = req.getParameterNames();
334: while (names.hasMoreElements()) {
335: String name = (String) names.nextElement();
336: if (name.startsWith(PARAM_ACTION_COMBO)) {
337: action = name
338: .substring(PARAM_ACTION_COMBO.length());
339: break;
340: }
341: }
342: }
343:
344: // process the action if present
345: if (action != null) {
346: actionDispatch("processAction", action, req, res);
347: }
348:
349: } // processAction
350:
351: /**
352: * Dispatch to a "processAction" method based on reflection.
353: *
354: * @param methodBase
355: * The base name of the method to call.
356: * @param methodExt
357: * The end name of the method to call.
358: * @param req
359: * The ActionRequest.
360: * @param res
361: * The ActionResponse
362: * @throws PortletExcption,
363: * IOException, just like the "do" methods.
364: */
365: protected void actionDispatch(String methodBase, String methodExt,
366: HttpServletRequest req, HttpServletResponse res) {
367: String methodName = null;
368: try {
369: // the method signature
370: Class[] signature = new Class[2];
371: signature[0] = HttpServletRequest.class;
372: signature[1] = HttpServletResponse.class;
373:
374: // the method name
375: methodName = methodBase + methodExt;
376:
377: // find a method of this class with this name and signature
378: Method method = getClass().getMethod(methodName, signature);
379:
380: // the parameters
381: Object[] args = new Object[2];
382: args[0] = req;
383: args[1] = res;
384:
385: // make the call
386: method.invoke(this , args);
387:
388: } catch (NoSuchMethodException e) {
389: getServletContext().log(
390: "Exception calling method " + methodName + " " + e);
391: } catch (IllegalAccessException e) {
392: getServletContext().log(
393: "Exception calling method " + methodName + " " + e);
394: } catch (InvocationTargetException e) {
395: String xtra = "";
396: if (e.getCause() != null)
397: xtra = " (Caused by " + e.getCause() + ")";
398: getServletContext().log(
399: "Exception calling method " + methodName + " " + e
400: + xtra);
401: }
402:
403: } // actionDispatch
404:
405: /**********************************************************************************************************************************************************************************************************************************************************
406: * session state support ******************************************************************************* SessionState is a cover for the portlet's attributes in the session. Attributes are those that are portlet scoped. Attributes names are protected
407: * with a namespace.
408: *********************************************************************************************************************************************************************************************************************************************************/
409:
410: /** The state attribute name used to store the marker of have been initialized. */
411: protected static final String ALERT_STATE_INITED = "sakai.inited";
412:
413: /**
414: * Access the "pid" - portlet window id, tool id, from the request
415: *
416: * @param req
417: * The current request.
418: * @return the "pid" - portlet window id, tool id, from the request
419: */
420: protected String getPid(HttpServletRequest req) {
421: String pid = (String) req.getAttribute(Tool.PLACEMENT_ID);
422: return pid;
423: }
424:
425: /**
426: * Access the SessionState for the current request. Note: this is scoped only for the current request.
427: *
428: * @param req
429: * The current portlet request.
430: * @return The SessionState objet for the current request.
431: */
432: protected SessionState getState(HttpServletRequest req) {
433: // key the state based on the pid, if present. If not we will use the servlet's class name
434: String key = getPid(req);
435: if (key == null) {
436: key = this .toString() + ".";
437: M_log.warn("getState(): using servlet key: " + key);
438: }
439:
440: SessionState rv = UsageSessionService.getSessionState(key);
441:
442: if (rv == null) {
443: M_log.warn("getState(): no state found for key: " + key
444: + " " + req.getPathInfo() + " "
445: + req.getQueryString() + " " + req.getRequestURI());
446: }
447:
448: return rv;
449: }
450:
451: /**
452: * Prepare state, either for first time or update
453: *
454: * @param req
455: * The current portlet request.
456: * @param res
457: * The current response.
458: */
459: protected void prepState(HttpServletRequest req,
460: HttpServletResponse res) {
461: SessionState state = getState(req);
462:
463: // If two requests from the same session to the same tool (pid) come in at the same time, we might
464: // get two threads in here doing initState() at once, which would not be good.
465: // We need to sync. on something... but we have no object that maps to a session/tool instance
466: // (state is a cover freshly created each time)
467: // lets try to sync on the Sakai session. That's more than we need but not too bad. -ggolden
468: Session session = SessionManager.getCurrentSession();
469: synchronized (session) {
470: // if this is the first time, init it
471: if (state.getAttribute(ALERT_STATE_INITED) == null) {
472: initState(state, req, res);
473:
474: // mark this state as initialized
475: state.setAttribute(ALERT_STATE_INITED,
476: new Boolean(true));
477: }
478:
479: // othewise update it
480: else {
481: updateState(state, req, res);
482: }
483: }
484:
485: } // initState
486:
487: /**
488: * Initialize for the first time the session state for this session. If overridden in a sub-class, make sure to call super.
489: *
490: * @param state
491: * The session state.
492: * @param req
493: * The current request.
494: * @param res
495: * The current response.
496: */
497: protected void initState(SessionState state,
498: HttpServletRequest req, HttpServletResponse res) {
499: }
500:
501: /**
502: * Update for this request processing the session state. If overridden in a sub-class, make sure to call super.
503: *
504: * @param state
505: * The session state.
506: * @param req
507: * The current request.
508: * @param res
509: * The current response.
510: */
511: protected void updateState(SessionState state,
512: HttpServletRequest req, HttpServletResponse res) {
513: }
514:
515: /**********************************************************************************************************************************************************************************************************************************************************
516: * alert support ******************************************************************************* Alerts are messages displayed to the user in the portlet output in a standard way. Alerts are added as needed. The Alert text reference is placed into the
517: * velocity context, and then cleared.
518: *********************************************************************************************************************************************************************************************************************************************************/
519:
520: /** The state attribute name used to store the Alert. */
521: protected static final String ALERT_ATTR = "sakai.alert";
522:
523: /**
524: * Access the Alert for the current request.
525: *
526: * @param req
527: * The current portlet request.
528: * @return The Alert objet for the current request.
529: */
530: protected Alert getAlert(HttpServletRequest req) {
531: // find the alert in state, if it's not there, make it.
532: SessionState state = getState(req);
533: Alert alert = (Alert) state.getAttribute(ALERT_ATTR);
534: if (alert == null) {
535: alert = new AlertImpl();
536: state.setAttribute(ALERT_ATTR, alert);
537: }
538:
539: return alert;
540:
541: } // getAlert
542:
543: /**
544: * Access the Alert in this state - will create one if needed.
545: *
546: * @param state
547: * The state in which to find the alert.
548: * @return The Alert objet.
549: */
550: protected Alert getAlert(SessionState state) {
551: // find the alert in state, if it's not there, make it.
552: Alert alert = (Alert) state.getAttribute(ALERT_ATTR);
553: if (alert == null) {
554: alert = new AlertImpl();
555: state.setAttribute(ALERT_ATTR, alert);
556: }
557:
558: return alert;
559:
560: } // getAlert
561:
562: /**********************************************************************************************************************************************************************************************************************************************************
563: * menu support ******************************************************************************* Menus are sets of commands to be displayed in the user interface.
564: *********************************************************************************************************************************************************************************************************************************************************/
565:
566: /** The state attribute name used to store the Menu. */
567: protected static final String MENU_ATTR = "sakai.menu";
568:
569: /**
570: * Access the Menu for the current request.
571: *
572: * @param req
573: * The current portlet request.
574: * @return The Menu objet for the current request.
575: */
576: protected Menu getMenu(HttpServletRequest req) {
577: // find the menu in state, if it's not there, make it.
578: SessionState state = getState(req);
579: Menu menu = (Menu) state.getAttribute(MENU_ATTR);
580: if (menu == null) {
581: menu = new MenuImpl();
582: state.setAttribute(MENU_ATTR, menu);
583: }
584:
585: return menu;
586:
587: } // getMenu
588:
589: /**
590: * @param req
591: * @param res
592: * @param target
593: * @return
594: * @throws ToolException
595: */
596: protected boolean sendToHelper(HttpServletRequest req,
597: HttpServletResponse res, String target)
598: throws ToolException {
599: String path = req.getPathInfo();
600: if (path == null) {
601: path = "/";
602: }
603:
604: // 0 parts means the path was just "/", otherwise parts[0] = "",
605: // parts[1] = item id, parts[2] if present is "edit"...
606: String[] parts = path.split("/");
607:
608: if (parts.length < 2) {
609: return false;
610: }
611:
612: int partIndex = 0;
613: String part = null;
614: if (!parts[1].endsWith(HELPER_EXT)) {
615: return false;
616: }
617:
618: ToolSession toolSession = SessionManager
619: .getCurrentToolSession();
620:
621: // calc helper id
622: int posEnd = parts[1].lastIndexOf(".");
623:
624: String helperId = target.substring(1, posEnd + 1);
625: ActiveTool helperTool = ActiveToolManager
626: .getActiveTool(helperId);
627:
628: String context = req.getContextPath() + req.getServletPath()
629: + Web.makePath(parts, 1, 2);
630: String toolPath = Web.makePath(parts, 2, parts.length);
631: helperTool.help(req, res, context, toolPath);
632:
633: return true; // was handled as helper call
634: }
635:
636: } // class ToolServlet
|