001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/tool/tags/sakai_2-4-1/tool-impl/impl/src/java/org/sakaiproject/tool/impl/ActiveToolComponent.java $
003: * $Id: ActiveToolComponent.java 27663 2007-03-22 19:50:10Z bkirschn@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 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.tool.impl;
021:
022: import java.io.File;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.Enumeration;
026: import java.util.HashMap;
027: import java.util.HashSet;
028: import java.util.Iterator;
029: import java.util.Map;
030: import java.util.Properties;
031: import java.util.Set;
032: import java.util.List;
033: import java.util.ArrayList;
034:
035: import javax.servlet.RequestDispatcher;
036: import javax.servlet.ServletContext;
037: import javax.servlet.ServletException;
038: import javax.servlet.http.HttpServletRequest;
039: import javax.servlet.http.HttpServletRequestWrapper;
040: import javax.servlet.http.HttpServletResponse;
041: import javax.servlet.http.HttpServletResponseWrapper;
042:
043: import org.apache.commons.logging.Log;
044: import org.apache.commons.logging.LogFactory;
045: import org.sakaiproject.authz.api.FunctionManager;
046: import org.sakaiproject.tool.api.ActiveTool;
047: import org.sakaiproject.tool.api.ActiveToolManager;
048: import org.sakaiproject.tool.api.Placement;
049: import org.sakaiproject.tool.api.SessionManager;
050: import org.sakaiproject.tool.api.Tool;
051: import org.sakaiproject.tool.api.ToolException;
052: import org.sakaiproject.tool.api.ToolSession;
053: import org.sakaiproject.util.StringUtil;
054: import org.sakaiproject.util.Xml;
055: import org.sakaiproject.util.ResourceLoader;
056: import org.w3c.dom.Document;
057: import org.w3c.dom.Element;
058: import org.w3c.dom.Node;
059: import org.w3c.dom.NodeList;
060:
061: /**
062: * <p>
063: * ActiveToolComponent is the standard implementation of the Sakai ActiveTool API.
064: * </p>
065: */
066: public abstract class ActiveToolComponent extends ToolComponent
067: implements ActiveToolManager {
068: /** Our log (commons). */
069: private static Log M_log = LogFactory
070: .getLog(ActiveToolComponent.class);
071:
072: ResourceLoader toolProps = new ResourceLoader("tools");
073:
074: /**********************************************************************************************************************************************************************************************************************************************************
075: * Dependencies
076: *********************************************************************************************************************************************************************************************************************************************************/
077:
078: /**
079: * @return the SessionManager collaborator.
080: */
081: protected abstract SessionManager sessionManager();
082:
083: /**
084: * @return the FunctionManager collaborator.
085: */
086: protected abstract FunctionManager functionManager();
087:
088: /**********************************************************************************************************************************************************************************************************************************************************
089: * Init and Destroy
090: *********************************************************************************************************************************************************************************************************************************************************/
091:
092: /**********************************************************************************************************************************************************************************************************************************************************
093: * Work interface methods: ActiveToolManager - ToolManager is covered by our base class ToolComponent
094: *********************************************************************************************************************************************************************************************************************************************************/
095:
096: /**
097: * @inheritDoc
098: */
099: public void register(Tool tool, ServletContext context) {
100: MyActiveTool at = null;
101:
102: // make it an active tool
103: if (tool instanceof MyActiveTool) {
104: at = (MyActiveTool) tool;
105: } else {
106: at = new MyActiveTool(tool);
107: }
108:
109: at.setServletContext(context);
110:
111: // try getting the RequestDispatcher, just to test - but DON'T SAVE IT!
112: // Tomcat's RequestDispatcher is NOT thread safe and must be gotten from the context
113: // every time its needed!
114: RequestDispatcher dispatcher = context.getNamedDispatcher(at
115: .getId());
116: if (dispatcher == null) {
117: M_log.warn("missing dispatcher for tool: " + at.getId());
118: }
119:
120: m_tools.put(at.getId(), at);
121: }
122:
123: /**
124: * @inheritDoc
125: */
126: public void register(Document toolXml, ServletContext context) {
127: Element root = toolXml.getDocumentElement();
128: if (!root.getTagName().equals("registration")) {
129: M_log
130: .info("register: invalid root element (expecting \"registration\"): "
131: + root.getTagName());
132: return;
133: }
134:
135: // read the children nodes (tools)
136: NodeList rootNodes = root.getChildNodes();
137: final int rootNodesLength = rootNodes.getLength();
138: for (int i = 0; i < rootNodesLength; i++) {
139: Node rootNode = rootNodes.item(i);
140: if (rootNode.getNodeType() != Node.ELEMENT_NODE)
141: continue;
142: Element rootElement = (Element) rootNode;
143:
144: // for tool
145: if (rootElement.getTagName().equals("tool")) {
146: org.sakaiproject.util.Tool tool = parseToolRegistration(rootElement);
147: register(tool, context);
148: }
149: // for function
150: else if (rootElement.getTagName().equals("function")) {
151: String function = rootElement.getAttribute("name")
152: .trim();
153: functionManager().registerFunction(function);
154: }
155: }
156: }
157:
158: public String getLocalizedToolProperty(String toolId, String key) {
159: final String toolProp = toolProps.getString(toolId + "." + key,
160: "");
161:
162: if (toolProp.length() < 1 || toolProp.equals("")) {
163: return null;
164: } else {
165: return toolProp;
166: }
167: }
168:
169: /**
170: * @inheritDoc
171: */
172: public List<Tool> parseTools(File toolXmlFile) {
173: if (!toolXmlFile.canRead())
174: return null;
175:
176: String path = toolXmlFile.getAbsolutePath();
177: if (!path.endsWith(".xml")) {
178: M_log.info("register: skiping non .xml file: " + path);
179: return null;
180: }
181:
182: M_log.info("parse-file: " + path);
183:
184: Document doc = Xml.readDocument(path);
185: if (doc == null)
186: return null;
187: return parseTools(doc);
188: }
189:
190: /**
191: * @inheritDoc
192: */
193: public List<Tool> parseTools(Document toolXml) {
194:
195: List<Tool> retval = new ArrayList<Tool>();
196: Element root = toolXml.getDocumentElement();
197: if (!root.getTagName().equals("registration")) {
198: M_log
199: .info("register: invalid root element (expecting \"registration\"): "
200: + root.getTagName());
201: return null;
202: }
203:
204: // read the children nodes (tools)
205: NodeList rootNodes = root.getChildNodes();
206: final int rootNodesLength = rootNodes.getLength();
207: for (int i = 0; i < rootNodesLength; i++) {
208: Node rootNode = rootNodes.item(i);
209: if (rootNode.getNodeType() != Node.ELEMENT_NODE)
210: continue;
211: Element rootElement = (Element) rootNode;
212:
213: // for tool
214: if (rootElement.getTagName().equals("tool")) {
215: org.sakaiproject.util.Tool tool = parseToolRegistration(rootElement);
216: retval.add(tool);
217: }
218:
219: }
220: if (retval.size() < 1)
221: return null;
222: return retval;
223: }
224:
225: private org.sakaiproject.util.Tool parseToolRegistration(
226: Element rootElement) {
227: org.sakaiproject.util.Tool tool = new org.sakaiproject.util.Tool();
228:
229: final String toolId = rootElement.getAttribute("id").trim();
230: tool.setId(toolId);
231:
232: final String toolTitle = toolProps.getString(toolId + ".title",
233: "");
234: final String toolDescription = toolProps.getString(toolId
235: + ".description", "");
236:
237: // if the key is empty or absent, the length is 0. if so, use the string from the XML file
238: if (toolTitle.length() > 0) {
239: tool.setTitle(toolTitle);
240: } else {
241: tool.setTitle(rootElement.getAttribute("title").trim());
242: }
243:
244: // if the key is empty or absent, the length is 0. if so, use the string from the XML file
245: if (toolDescription.length() > 0) {
246: tool.setDescription(toolDescription);
247: } else {
248: tool.setDescription(rootElement.getAttribute("description")
249: .trim());
250: }
251:
252: tool.setHome(StringUtil.trimToNull(rootElement
253: .getAttribute("home")));
254:
255: if ("tool".equals(rootElement.getAttribute("accessSecurity"))) {
256: tool.setAccessSecurity(Tool.AccessSecurity.TOOL);
257: } else {
258: tool.setAccessSecurity(Tool.AccessSecurity.PORTAL);
259: }
260:
261: // collect values for these collections
262: Properties finalConfig = new Properties();
263: Properties mutableConfig = new Properties();
264: Set categories = new HashSet();
265: Set keywords = new HashSet();
266: NodeList kids = rootElement.getChildNodes();
267: final int kidsLength = kids.getLength();
268: for (int k = 0; k < kidsLength; k++) {
269: Node kidNode = kids.item(k);
270: if (kidNode.getNodeType() != Node.ELEMENT_NODE)
271: continue;
272: Element kidElement = (Element) kidNode;
273:
274: // for configuration
275: if (kidElement.getTagName().equals("configuration")) {
276: String name = kidElement.getAttribute("name").trim();
277: String value = kidElement.getAttribute("value").trim();
278: String type = kidElement.getAttribute("type").trim();
279: if (name.length() > 0) {
280: if ("final".equals(type)) {
281: finalConfig.put(name, value);
282: } else {
283: mutableConfig.put(name, value);
284: }
285: }
286: }
287:
288: // for category
289: if (kidElement.getTagName().equals("category")) {
290: String name = kidElement.getAttribute("name").trim();
291: if (name.length() > 0) {
292: categories.add(name);
293: }
294: }
295:
296: // for keyword
297: if (kidElement.getTagName().equals("keyword")) {
298: String name = kidElement.getAttribute("name").trim();
299: if (name.length() > 0) {
300: keywords.add(name);
301: }
302: }
303: }
304:
305: // set the tool's collected values
306: tool.setRegisteredConfig(finalConfig, mutableConfig);
307: tool.setCategories(categories);
308: tool.setKeywords(keywords);
309:
310: return tool;
311: }
312:
313: /**
314: * @inheritDoc
315: */
316: public void register(File toolXmlFile, ServletContext context) {
317: String path = toolXmlFile.getAbsolutePath();
318: if (!path.endsWith(".xml")) {
319: M_log.info("register: skiping non .xml file: " + path);
320: return;
321: }
322:
323: M_log.info("register: file: " + path);
324:
325: Document doc = Xml.readDocument(path);
326: register(doc, context);
327: }
328:
329: /**
330: * @inheritDoc
331: */
332: public void register(InputStream toolXmlStream,
333: ServletContext context) {
334: Document doc = Xml.readDocumentFromStream(toolXmlStream);
335: try {
336: toolXmlStream.close();
337: } catch (Exception e) {
338: }
339:
340: register(doc, context);
341: }
342:
343: /**
344: * @inheritDoc
345: */
346: public ActiveTool getActiveTool(String id) {
347: if (id == null)
348: return null;
349: return (ActiveTool) m_tools.get(id);
350: }
351:
352: /**********************************************************************************************************************************************************************************************************************************************************
353: * Entity: ActiveTool
354: *********************************************************************************************************************************************************************************************************************************************************/
355:
356: public class MyActiveTool extends org.sakaiproject.util.Tool
357: implements ActiveTool {
358:
359: protected ServletContext m_servletContext = null;
360:
361: /**
362: * Construct
363: */
364: public MyActiveTool() {
365: super ();
366: }
367:
368: public void setServletContext(ServletContext context) {
369: m_servletContext = context;
370: }
371:
372: /**
373: * Construct from a Tool
374: */
375: public MyActiveTool(org.sakaiproject.tool.api.Tool t) {
376: super ();
377: this .m_categories.addAll(t.getCategories());
378: this .m_mutableConfig.putAll(t.getMutableConfig());
379: this .m_finalConfig.putAll(t.getFinalConfig());
380: this .m_keywords.addAll(t.getKeywords());
381: this .m_id = t.getId();
382: this .m_title = t.getTitle();
383: this .m_description = t.getDescription();
384: this .m_accessSecurity = t.getAccessSecurity();
385: this .m_home = t.getHome();
386: }
387:
388: /**
389: * Return a RequestDispatcher that can be used to dispatch to this tool - RequestDispatcher is NOT THREAD-SAFE FOR REUSE, so this method must be called each and every time a request is forwarded.
390: */
391: protected RequestDispatcher getDispatcher() {
392: return m_servletContext.getNamedDispatcher(getId());
393: }
394:
395: /**
396: * @inheritDoc
397: */
398: public void forward(HttpServletRequest req,
399: HttpServletResponse res, Placement placement,
400: String toolContext, String toolPath)
401: throws ToolException {
402:
403: WrappedRequest wreq = null;
404:
405: try {
406: wreq = new WrappedRequest(req, toolContext, toolPath,
407: placement, false, this );
408: WrappedResponse wres = new WrappedResponse(wreq, res);
409:
410: getDispatcher().forward(wreq, wres);
411: } catch (IOException e) {
412: throw new ToolException(e);
413: } catch (ToolException e) {
414: throw e;
415: } catch (ServletException e) {
416: throw new ToolException(e);
417: } finally {
418: if (wreq != null)
419: wreq.cleanup();
420: }
421: }
422:
423: /**
424: * @inheritDoc
425: */
426: public void include(HttpServletRequest req,
427: HttpServletResponse res, Placement placement,
428: String toolContext, String toolPath)
429: throws ToolException {
430: WrappedRequest wreq = null;
431:
432: try {
433: wreq = new WrappedRequest(req, toolContext, toolPath,
434: placement, true, this );
435: getDispatcher().include(wreq, res);
436: } catch (IOException e) {
437: throw new ToolException(e);
438: } catch (ToolException e) {
439: throw e;
440: } catch (ServletException e) {
441: throw new ToolException(e);
442: } finally {
443: if (wreq != null)
444: wreq.cleanup();
445: }
446: }
447:
448: /**
449: * @inheritDoc
450: */
451: public void help(HttpServletRequest req,
452: HttpServletResponse res, String toolContext,
453: String toolPath) throws ToolException {
454: // fragment?
455: boolean fragment = Boolean.TRUE.toString().equals(
456: req.getAttribute(FRAGMENT));
457:
458: WrappedRequest wreq = null;
459:
460: try {
461: wreq = new WrappedRequest(req, toolContext, toolPath,
462: null, fragment, this );
463: if (fragment) {
464: getDispatcher().include(wreq, res);
465: } else {
466: getDispatcher().forward(wreq, res);
467: }
468: } catch (IOException e) {
469: throw new ToolException(e);
470: } catch (ToolException e) {
471: throw e;
472: } catch (ServletException e) {
473: throw new ToolException(e);
474: } finally {
475: if (wreq != null)
476: wreq.cleanup();
477: }
478: }
479:
480: /**
481: * Wraps a request object so we can override some standard behavior.
482: */
483: public class WrappedRequest extends HttpServletRequestWrapper {
484: /** The context to report. */
485: protected String m_context = null;
486:
487: /** The pathInfo to report. */
488: protected String m_path = null;
489:
490: /** attributes to override the wrapped req's attributes. */
491: protected Map m_attributes = new HashMap();
492:
493: /** The prior current tool session, to restore after we are done. */
494: protected ToolSession m_priorToolSession = null;
495:
496: /** The prior current tool, to restore after we are done. */
497: protected Tool m_priorTool = null;
498:
499: /** The prior current placementl, to restore after we are done. */
500: protected Placement m_priorPlacement = null;
501:
502: /** The request object we wrap. */
503: protected HttpServletRequest m_wrappedReq = null;
504:
505: public WrappedRequest(HttpServletRequest req,
506: String context, String path, Placement placement,
507: boolean fragment, Tool tool) {
508: super (req);
509: m_wrappedReq = req;
510:
511: m_context = context;
512:
513: // default if needed
514: if (m_context == null) {
515: m_context = req.getContextPath()
516: + req.getServletPath() + req.getPathInfo();
517: }
518:
519: m_path = path;
520:
521: if (placement != null) {
522: ToolSession ts = sessionManager()
523: .getCurrentSession().getToolSession(
524: placement.getId());
525:
526: // put the session in the request attribute
527: setAttribute(TOOL_SESSION, ts);
528:
529: // set as the current tool session, and setup for undoing this later
530: m_priorToolSession = sessionManager()
531: .getCurrentToolSession();
532: sessionManager().setCurrentToolSession(ts);
533:
534: // set this tool placement as current, in the request and for the service's "current" tool placement
535: setAttribute(PLACEMENT, placement);
536: setAttribute(PLACEMENT_ID, placement.getId());
537: m_priorPlacement = getCurrentPlacement();
538: setCurrentPlacement(placement);
539: }
540:
541: setAttribute(FRAGMENT, Boolean.toString(fragment));
542:
543: // set this tool as current, in the request and for the service's "current" tool
544: setAttribute(TOOL, tool);
545: m_priorTool = getCurrentTool();
546: setCurrentTool(tool);
547: }
548:
549: public String getPathInfo() {
550: if (getAttribute(NATIVE_URL) != null)
551: return super .getPathInfo();
552:
553: return m_path;
554: }
555:
556: public String getServletPath() {
557: if (getAttribute(NATIVE_URL) != null)
558: return super .getServletPath();
559:
560: return "";
561: }
562:
563: public String getContextPath() {
564: if (getAttribute(NATIVE_URL) != null)
565: return super .getContextPath();
566:
567: return m_context;
568: }
569:
570: public Object getAttribute(String name) {
571: if (m_attributes.containsKey(name)) {
572: return m_attributes.get(name);
573: }
574:
575: return super .getAttribute(name);
576: }
577:
578: public Enumeration getAttributeNames() {
579: Set s = new HashSet();
580: s.addAll(m_attributes.keySet());
581: for (Enumeration e = super .getAttributeNames(); e
582: .hasMoreElements();) {
583: String name = (String) e.nextElement();
584: s.add(name);
585: }
586:
587: return new IteratorEnumeration(s.iterator());
588: }
589:
590: public void setAttribute(String name, Object value) {
591: m_attributes.put(name, value);
592:
593: // if this is a special attribute, set it back through all wrapped requests of this class
594: if (Tool.NATIVE_URL.equals(name)) {
595: m_wrappedReq.setAttribute(name, value);
596: }
597: }
598:
599: public void removeAttribute(String name) {
600: if (m_attributes.containsKey(name)) {
601: m_attributes.remove(name);
602: }
603:
604: else {
605: super .removeAttribute(name);
606: }
607:
608: // if this is a special attribute, set it back through all wrapped requests of this class
609: if (Tool.NATIVE_URL.equals(name)) {
610: m_wrappedReq.removeAttribute(name);
611: }
612: }
613:
614: public void cleanup() {
615: // restore the tool, placement and tool session and tool placement config that was in effect before we changed it
616: setCurrentTool(m_priorTool);
617: sessionManager().setCurrentToolSession(
618: m_priorToolSession);
619: setCurrentPlacement(m_priorPlacement);
620: }
621:
622: /**************************************************************************************************************************************************************************************************************************************************
623: * Enumeration over an iterator
624: *************************************************************************************************************************************************************************************************************************************************/
625:
626: protected class IteratorEnumeration implements Enumeration {
627: /** The iterator over which this enumerates. */
628: protected Iterator m_iterator = null;
629:
630: public IteratorEnumeration(Iterator i) {
631: m_iterator = i;
632: }
633:
634: public boolean hasMoreElements() {
635: return m_iterator.hasNext();
636: }
637:
638: public Object nextElement() {
639: return m_iterator.next();
640: }
641: }
642: }
643:
644: /**
645: * Wraps a response object so we can override some standard behavior.
646: */
647: public class WrappedResponse extends HttpServletResponseWrapper {
648: /** The request. */
649: protected HttpServletRequest m_req = null;
650:
651: /** The wrapped response. */
652: protected HttpServletResponse m_res = null;
653:
654: public WrappedResponse(HttpServletRequest req,
655: HttpServletResponse res) {
656: super (res);
657:
658: m_req = req;
659: m_res = res;
660: }
661:
662: public String encodeRedirectUrl(String url) {
663: return rewriteURL(url);
664: }
665:
666: public String encodeRedirectURL(String url) {
667: return rewriteURL(url);
668: }
669:
670: public String encodeUrl(String url) {
671: return rewriteURL(url);
672: }
673:
674: public String encodeURL(String url) {
675: return rewriteURL(url);
676: }
677:
678: public void sendRedirect(String url) throws IOException {
679: super .sendRedirect(rewriteURL(url));
680: }
681:
682: /**
683: * Rewrites the given URL to insert the current tool placement id, if any, as the start of the path
684: *
685: * @param url
686: * The url to rewrite.
687: */
688: protected String rewriteURL(String url) {
689: // only if we are in native url mode - if in Sakai mode, don't include the placement id
690: if (m_req.getAttribute(NATIVE_URL) != null) {
691: // if we have a tool placement to add, add it
692: Placement placement = (Placement) m_req
693: .getAttribute(PLACEMENT);
694: if (placement != null) {
695: String placementId = placement.getId();
696: // compute the URL root "back" to this servlet
697: // Note: this must not be pre-computed, as it can change as things are dispatched
698: StringBuffer full = new StringBuffer();
699: full.append(m_req.getScheme());
700: full.append("://");
701: full.append(m_req.getServerName());
702: if (((m_req.getServerPort() != 80) && (!m_req
703: .isSecure()))
704: || ((m_req.getServerPort() != 443) && (m_req
705: .isSecure()))) {
706: full.append(":");
707: full.append(m_req.getServerPort());
708: }
709:
710: // include just the context path - anything to this context will get the encoding
711: StringBuffer rel = new StringBuffer();
712: rel.append(m_req.getContextPath());
713:
714: full.append(rel.toString());
715:
716: // if we match the fullUrl, or the relUrl, assume that this is a URL back to this servlet context
717: if ((url.startsWith(full.toString()) || url
718: .startsWith(rel.toString()))) {
719: // put the placementId in as a parameter
720: StringBuffer newUrl = new StringBuffer(url);
721: if (url.indexOf('?') != -1) {
722: newUrl.append('&');
723: } else {
724: newUrl.append('?');
725: }
726: newUrl.append(Tool.PLACEMENT_ID);
727: newUrl.append("=");
728: newUrl.append(placementId);
729:
730: // TODO: (or not) let the wrapped resp. do the work, too
731: return newUrl.toString();
732: }
733: }
734: }
735:
736: return url;
737: }
738: }
739: }
740: }
|