001: package org.tigris.scarab.util;
002:
003: /* ================================================================
004: * Copyright (c) 2000-2002 CollabNet. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are
008: * met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowlegement: "This product includes
019: * software developed by Collab.Net <http://www.Collab.Net/>."
020: * Alternately, this acknowlegement may appear in the software itself, if
021: * and wherever such third-party acknowlegements normally appear.
022: *
023: * 4. The hosted project names must not be used to endorse or promote
024: * products derived from this software without prior written
025: * permission. For written permission, please contact info@collab.net.
026: *
027: * 5. Products derived from this software may not use the "Tigris" or
028: * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without
029: * prior written permission of Collab.Net.
030: *
031: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
032: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
033: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
034: * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
035: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
036: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
037: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
038: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
039: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
040: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
041: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042: *
043: * ====================================================================
044: *
045: * This software consists of voluntary contributions made by many
046: * individuals on behalf of Collab.Net.
047: */
048:
049: import java.util.Enumeration;
050:
051: import org.apache.fulcrum.parser.ParameterParser;
052: import org.apache.fulcrum.parser.ValueParser;
053: import org.apache.fulcrum.pool.InitableRecyclable;
054: import org.apache.turbine.DynamicURI;
055: import org.apache.turbine.RunData;
056: import org.apache.turbine.Turbine;
057: import org.apache.turbine.tool.TemplateLink;
058: import org.tigris.scarab.om.Issue;
059: import org.tigris.scarab.om.Module;
060: import org.tigris.scarab.om.ModuleManager;
061: import org.tigris.scarab.om.ScarabUser;
062: import org.tigris.scarab.services.security.ScarabSecurity;
063:
064: /**
065: * This class adds a ModuleManager.CURRENT_PROJECT to every link. This class is added
066: * into the context to replace the $link that Turbine adds.
067: *
068: * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
069: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
070: * @author <a href="mailto:maartenc@tigris.org">Maarten Coene</a>
071: * @version $Id: ScarabLink.java 9476 2005-03-16 22:56:11Z dabbous $
072: */
073: public class ScarabLink extends TemplateLink implements
074: InitableRecyclable, SkipFiltering {
075: private RunData data;
076: private String label;
077: private String attributeText;
078: private String alternateText;
079: private String currentModuleId;
080: private String lastUsedModuleId;
081: private Module currentModule;
082: private Module lastUsedModule;
083: private boolean isOmitModule;
084: private boolean overrideSecurity;
085:
086: /**
087: * Constructor.
088: *
089: */
090: public ScarabLink() {
091: }
092:
093: /**
094: * This will initialise a TemplateLink object that was
095: * constructed with the default constructor (ApplicationTool
096: * method).
097: *
098: * @param data assumed to be a RunData object
099: */
100: public void init(Object data) {
101: // we just blithely cast to RunData as if another object
102: // or null is passed in we'll throw an appropriate runtime
103: // exception.
104: super .init(data);
105: this .data = (RunData) data;
106: setAbsolute(false);
107: }
108:
109: public void refresh() {
110: super .refresh();
111: setAbsolute(false);
112: label = null;
113: attributeText = null;
114: alternateText = null;
115: lastUsedModuleId = null;
116: lastUsedModule = null;
117: super .setPage(null);
118: super .removePathInfo(ScarabConstants.TEMPLATE);
119: isOmitModule = false;
120: overrideSecurity = false;
121: }
122:
123: private void initCurrentModule() {
124: if (currentModule == null) {
125: ScarabUser user = (ScarabUser) data.getUser();
126: if (user != null) {
127: currentModule = user.getCurrentModule();
128: }
129: }
130: }
131:
132: /**
133: * Gets the server name.
134: *
135: * @return A String with the server name.
136: */
137: public String getServerName() {
138: initCurrentModule();
139: String result = null;
140: if (currentModule != null) {
141: result = currentModule.getHttpDomain();
142: }
143: if (result == null || result.length() == 0) {
144: result = super .getServerName();
145: }
146: return result;
147: }
148:
149: /**
150: * Gets the server port.
151: *
152: * @return A String with the server port.
153: */
154: public int getServerPort() {
155: initCurrentModule();
156: int result = -1;
157: try {
158: if (currentModule != null) {
159: String port = currentModule.getPort();
160: if (port.length() == 0) {
161: result = super .getServerPort();
162: } else {
163: result = Integer.parseInt(port);
164: }
165: }
166: } catch (Exception e) {
167: Log.get().debug("Could not parse port number", e);
168: }
169: if (result == -1) {
170: result = super .getServerPort();
171: }
172: return result;
173: }
174:
175: /**
176: * Gets the server scheme (HTTP or HTTPS).
177: *
178: * @return A String with the server scheme.
179: */
180: public String getServerScheme() {
181: initCurrentModule();
182: String result = null;
183: try {
184: if (currentModule != null) {
185: result = currentModule.getScheme();
186: }
187: } catch (Exception e) {
188: Log.get().debug("Could not get scheme parameter", e);
189: }
190: if (result == null || result.length() == 0) {
191: result = super .getServerScheme();
192: }
193: return result;
194: }
195:
196: /**
197: * Gets the server scriptName (/s).
198: *
199: * @return A String with the server scriptName.
200: */
201: public String getScriptName() {
202: initCurrentModule();
203: String result = null;
204: try {
205: if (currentModule != null) {
206: result = currentModule.getScriptName();
207: }
208: } catch (Exception e) {
209: Log.get().debug("Could not get script name parameter", e);
210: }
211: if (result == null || result.length() == 0) {
212: result = super .getScriptName();
213: }
214: return result;
215: }
216:
217: /**
218: * Sets the template variable used by the Template Service.
219: *
220: * @param t A String with the template name.
221: * @return A TemplateLink.
222: */
223: public TemplateLink setPage(String t) {
224: String moduleid = data.getParameters().getString(
225: ScarabConstants.CURRENT_MODULE);
226: return setPage(t, moduleid);
227: }
228:
229: /**
230: * Causes the link to not include the module id. Useful for templates
231: * where a module is not required or desired.
232: *
233: * @return a <code>ScarabLink</code> value
234: */
235: public ScarabLink omitModule() {
236: isOmitModule = true;
237: return this ;
238: }
239:
240: /**
241: * Shuts off permission checking. Use case: a user saves a query with
242: * module scope, so an email is sent to the project owner to approve it.
243: * The email is sent from the user who does not have permission to
244: * use the Approval.vm template. But it is known that the recipient(s)
245: * does, because that is how they are chosen to receive the email.
246: * We probably need a different link tool for emails that is not
247: * request based. but for now use this sparingly and with forethought.
248: *
249: * @return this
250: */
251: public ScarabLink overrideSecurity() {
252: overrideSecurity = true;
253: return this ;
254: }
255:
256: /**
257: * Sets the template variable used by the Template Service. The
258: * module id of the new selected module is given.
259: *
260: * @param t A String with the template name.
261: * @param moduleid The id of the new selected module.
262: * @return A TemplateLink.
263: */
264: protected TemplateLink setPage(String t, String moduleid) {
265:
266: this .currentModuleId = moduleid;
267: this .lastUsedModuleId = moduleid;
268:
269: if (isSet(moduleid) && !isOmitModule) {
270: addPathInfo(ScarabConstants.CURRENT_MODULE, moduleid);
271: }
272:
273: String issueKey = data.getParameters().getString(
274: ScarabConstants.REPORTING_ISSUE);
275: if (isSet(issueKey)) {
276: addPathInfo(ScarabConstants.REPORTING_ISSUE, issueKey);
277: }
278: Object threadKey = ((ScarabUser) data.getUser()).getThreadKey();
279: if (threadKey != null) {
280: addPathInfo(ScarabConstants.THREAD_QUERY_KEY, threadKey);
281: }
282: String reportKey = data.getParameters().getString(
283: ScarabConstants.CURRENT_REPORT);
284: if (isSet(reportKey)) {
285: if (t.startsWith("report")) {
286: addPathInfo(ScarabConstants.CURRENT_REPORT, reportKey);
287: } else if (!t.startsWith("help")) {
288: addPathInfo(ScarabConstants.REMOVE_CURRENT_REPORT,
289: reportKey);
290: }
291: }
292: // if a screen is to be passed along, add it
293: String historyScreen = data.getParameters().getString(
294: ScarabConstants.HISTORY_SCREEN);
295: if (isSet(historyScreen)) {
296: addPathInfo(ScarabConstants.HISTORY_SCREEN, historyScreen);
297: }
298: // if a admin menu is to be passed along, add it
299: String adminMenu = data.getParameters().getString(
300: ScarabConstants.CURRENT_ADMIN_MENU);
301: if (isSet(adminMenu)) {
302: addPathInfo(ScarabConstants.CURRENT_ADMIN_MENU, adminMenu);
303: }
304: // if a debug is set, add it
305: String debug = data.getParameters().getString(
306: ScarabConstants.DEBUG);
307: if (isSet(debug)) {
308: addPathInfo(ScarabConstants.DEBUG, debug);
309: }
310:
311: super .setPage(t);
312: return this ;
313: }
314:
315: private boolean isSet(String s) {
316: return s != null && s.length() > 0;
317: }
318:
319: /**
320: * Returns the name of the template that is being being processed
321: */
322: public String getCurrentView() {
323: String temp = data.getParameters().getString(
324: ScarabConstants.TEMPLATE, null);
325: if (temp != null) {
326: temp = temp.replace(',', '/');
327: }
328: return temp;
329: }
330:
331: public ScarabLink setPathInfo(String key, String value) {
332: removePathInfo(key);
333: addPathInfo(key, value);
334: return this ;
335: }
336:
337: // where is this method being used, i do not understand its purpose - jdm
338: public ScarabLink addPathInfo(String key, ParameterParser pp) {
339: addPathInfo(key, pp);
340: return this ;
341: }
342:
343: /**
344: * Adds a name=value pair to the path_info string. This method is missing
345: * in DynamicURI, but should be there.
346: *
347: * @param name A String with the name to add.
348: * @param value A double with the value to add.
349: */
350: public DynamicURI addPathInfo(String name, boolean value) {
351: addPathInfo(name, (value ? "true" : "false"));
352: return this ;
353: }
354:
355: /**
356: * Adds all the parameters in a ValueParser to the pathinfo except
357: * the action, screen, or template keys as defined by Turbine
358: */
359: public ScarabLink addPathInfo(ValueParser pp) {
360: // would be nice if DynamicURI included this method but it requires
361: // a specific implementation of ParameterParser
362: Enumeration e = pp.keys();
363: while (e.hasMoreElements()) {
364: String key = (String) e.nextElement();
365: if (!key.equalsIgnoreCase(Turbine.ACTION)
366: && !key.equalsIgnoreCase(Turbine.SCREEN)
367: && !key.equalsIgnoreCase(Turbine.TEMPLATE)) {
368: String[] values = pp.getStrings(key);
369: for (int i = 0; i < values.length; i++) {
370: addPathInfo(key, values[i]);
371: }
372: }
373: }
374: return this ;
375: }
376:
377: /**
378: * Setting the label will cause the link tool to print out the
379: * the text for the anchor tag. This is useful in that if the link
380: * should not be active for security reasons it can be completely
381: * eliminated.
382: *
383: * @param label a <code>String</code> value
384: * @return a <code>ScarabLink</code> value
385: */
386: public ScarabLink setLabel(String label) {
387: this .label = label;
388: return this ;
389: }
390:
391: /**
392: * Allows for setting attributes such as class on an anchor tag
393: * <a class="xxx" href="yyy">label</a>. Note the complete anchor
394: * tag is only returned from toString, if the lable has been set
395: * so this setter will have no effect unless setLabel is called.
396: *
397: * @param attributeText a <code>String</code> value
398: * @return a <code>ScarabLink</code> value
399: */
400: public ScarabLink setAttributeText(String attributeText) {
401: this .attributeText = attributeText;
402: return this ;
403: }
404:
405: /**
406: * Text that will be returned from toString if the user did not have
407: * permission to see the link. The default is the empty string
408: *
409: * @param alternateText a <code>String</code> value
410: * @return a <code>ScarabLink</code> value
411: */
412: public ScarabLink setAlternateText(String alternateText) {
413: this .alternateText = alternateText;
414: return this ;
415: }
416:
417: /**
418: * Prints out the url and resets the relative flag to true.
419: *
420: * @return a <code>String</code> url
421: */
422: public String toString() {
423: String tostring = null;
424: String alternateText = this .alternateText;
425: // will reset link if false
426: if (isAllowed()) {
427: tostring = getLink();
428: } else {
429: tostring = (alternateText == null) ? "" : alternateText;
430: }
431: refresh();
432: return tostring;
433: }
434:
435: /**
436: * Returns a short link for viewing a single issue
437: *
438: * @param issue an <code>Issue</code> value
439: * @return a <code>String</code> value
440: * @exception Exception if an error occurs
441: */
442: public ScarabLink getIssueIdLink(Issue issue) throws Exception {
443: this .addPathInfo("id", issue.getUniqueId());
444: return this ;
445: }
446:
447: /**
448: * Returns a short link for viewing a single issue that will not
449: * include session info and will be absolute. It is meant to
450: * be suitable for embedding in an email that points to the issue.
451: *
452: * @param issue an <code>Issue</code> value
453: * @return a <code>String</code> value
454: * @exception Exception if an error occurs
455: */
456: public ScarabLink getIssueIdAbsoluteLink(Issue issue)
457: throws Exception {
458: ScarabLink link = getIssueIdLink(issue);
459: link.setRelative(false).setEncodeUrl(false);
460: return link;
461: }
462:
463: /**
464: * Check if the user has the permission to see the link. If the user
465: * has the permission(s), <code>true</code> is returned. if the
466: * user does NOT have the proper permissions, this method has the
467: * side effect of reseting the link, so that it is ready for use
468: * in building the next link.
469: */
470: public boolean isAllowed() {
471: boolean allowed = overrideSecurity || isAllowed(getPage());
472:
473: if (!allowed) {
474: // reset link
475: super .toString();
476: refresh();
477: }
478:
479: return allowed;
480: }
481:
482: /**
483: * Check if the user has the permission to see the template t. If the user
484: * has the permission(s), <code>true</code> is returned. If template t is
485: * null, this method returns false.
486: */
487: public boolean isAllowed(String t) {
488: if (t == null) {
489: //check pathinfo for "id"
490: int count = pathInfo.size();
491: for (int i = 0; i < count; i++) {
492: Object[] pair = (Object[]) pathInfo.get(i);
493: if ("id".equals(pair[0])) {
494: t = "ViewIssue.vm";
495: break;
496: }
497: }
498: if (t == null) {
499: //check querydata for "id"
500: count = queryData.size();
501: for (int i = 0; i < count; i++) {
502: Object[] pair = (Object[]) queryData.get(i);
503: if ("id".equals(pair[0])) {
504: t = "ViewIssue.vm";
505: break;
506: }
507: }
508: if (t == null) {
509: return false;
510: }
511: }
512: }
513: boolean allowed = false;
514: try {
515: String perm = ScarabSecurity.getScreenPermission(t);
516: if (perm != null) {
517: initCurrentModule();
518:
519: if (currentModuleId != null) {
520: if (currentModule == null
521: || !currentModule.getModuleId().toString()
522: .equals(currentModuleId)) {
523: currentModule = ModuleManager
524: .getInstance(new Integer(
525: currentModuleId));
526: }
527: }
528: ScarabUser user = (ScarabUser) data.getUser();
529: allowed = currentModule != null
530: && (user.hasLoggedIn() || AnonymousUserUtil
531: .isUserAnonymous(user))
532: && user.hasPermission(perm, currentModule);
533: } else {
534: allowed = true;
535: }
536: } catch (Exception e) {
537: allowed = false;
538: Log.get().info("Could not check permission due to: ", e);
539: }
540: return allowed;
541: }
542:
543: private String getLink() {
544: String s = null;
545: if (label != null && label.length() > 0) {
546: StringBuffer sbuf = new StringBuffer(50);
547: sbuf.append("<a ");
548: if (attributeText != null && attributeText.length() > 0) {
549: sbuf.append(attributeText);
550: sbuf.append(' ');
551: }
552: sbuf.append("href=\"").append(super .toString()).append(
553: "\">").append(label).append("</a>");
554: s = sbuf.toString();
555: } else {
556: s = super .toString();
557: }
558: return s;
559: }
560:
561: /**
562: * Give subclasses access to the RunData, so they do not have to
563: * reimplement the pooling code, just to get at it.
564: */
565: protected RunData getRunData() {
566: return data;
567: }
568:
569: // ****************************************************************
570: // ****************************************************************
571: // Implementation of Recyclable
572: // ****************************************************************
573: // ****************************************************************
574:
575: private boolean disposed = false;
576:
577: /**
578: * Recycles the object by removing its disposed flag.
579: */
580: public void recycle() {
581: disposed = false;
582: }
583:
584: /**
585: * Disposes the object by setting its disposed flag.
586: */
587: public void dispose() {
588: data = null;
589: refresh();
590: disposed = true;
591: }
592:
593: /**
594: * Checks whether the object is disposed.
595: *
596: * @return true, if the object is disposed.
597: */
598: public boolean isDisposed() {
599: return disposed;
600: }
601: }
|