001: package org.tigris.scarab.reports;
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.List;
050: import java.util.ArrayList;
051: import java.util.Iterator;
052: import java.util.Collections;
053:
054: import java.io.StringWriter;
055: import org.apache.commons.betwixt.io.BeanWriter;
056: import org.tigris.scarab.util.Log;
057: import org.apache.torque.om.NumberKey;
058:
059: import org.tigris.scarab.om.ScarabUserManager;
060: import org.tigris.scarab.om.RModuleAttribute;
061: import org.tigris.scarab.om.RModuleAttributeManager;
062: import org.tigris.scarab.om.RModuleOption;
063: import org.tigris.scarab.om.RModuleOptionManager;
064: import org.tigris.scarab.om.AttributeOptionManager;
065: import org.tigris.scarab.om.AttributeManager;
066: import org.tigris.scarab.util.ScarabConstants;
067:
068: /**
069: * This class is the container for the information used to generate a
070: * report. It is the outermost tag of the XML representation defined
071: * by <a
072: * href="http://scarab.tigris.org/source/browse/scarab/src/dtd/report.dtd?rev=1&content-type=text/x-cvsweb-markup">report.dtd</a>
073: * (please see this file for an example).
074: *
075: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
076: * @version $Id: ReportDefinition.java 10132 2006-06-02 09:06:38Z dabbous $
077: * @see <a href="http://scarab.tigris.org/source/browse/scarab/src/dtd/report.dtd?rev=1&content-type=text/x-cvsweb-markup">report.dtd</a>
078: */
079: public class ReportDefinition implements java.io.Serializable
080: //Retrievable
081: {
082: /**
083: * A report can be expensive, so limit the criteria (which translates
084: * to headings) to a number that mysql can handle safely
085: */
086: private static final int MAX_CRITERIA = ScarabConstants.REPORT_MAX_CRITERIA;
087:
088: private String name;
089:
090: private String description;
091:
092: private String format;
093:
094: private List moduleIssueTypes;
095:
096: private List reportAxisList;
097:
098: private ReportDate defaultDate;
099:
100: /**
101: * Get the Name value.
102: * @return the Name value.
103: */
104: public String getName() {
105: return name;
106: }
107:
108: /**
109: * Set the Name value.
110: * @param newName The new Name value.
111: */
112: public void setName(String newName) {
113: this .name = newName;
114: }
115:
116: /**
117: * Get the Description value.
118: * @return the Description value.
119: */
120: public String getDescription() {
121: return description;
122: }
123:
124: /**
125: * Set the Description value.
126: * @param newDescription The new Description value.
127: */
128: public void setDescription(String newDescription) {
129: this .description = newDescription;
130: }
131:
132: /**
133: * Get the format value.
134: * @return The format value.
135: */
136: public String getFormat() {
137: return format;
138: }
139:
140: /**
141: * Set the format value.
142: * @param format The new format value.
143: */
144: public void setFormat(String format) {
145: this .format = format;
146: }
147:
148: /**
149: * Get the ModuleIssueTypes value.
150: * @return the ModuleIssueTypes value.
151: */
152: public List getModuleIssueTypes() {
153: return moduleIssueTypes;
154: }
155:
156: /**
157: * Set the ModuleIssueTypes value.
158: * @param newModuleIssueTypes The new ModuleIssueTypes value.
159: */
160: public void setModuleIssueTypes(List newModuleIssueTypes) {
161: this .moduleIssueTypes = newModuleIssueTypes;
162: }
163:
164: /**
165: * Add a ModuleIssueTypes value.
166: * @param newModuleIssueType The new ModuleIssueTypes value.
167: */
168: public void addModuleIssueType(ModuleIssueType newModuleIssueType) {
169: Log.get().debug(
170: "Added a mit, " + newModuleIssueType
171: + ", to reportDefn");
172: if (moduleIssueTypes == null) {
173: moduleIssueTypes = new ArrayList();
174: }
175: if (!moduleIssueTypes.contains(newModuleIssueType)) {
176: moduleIssueTypes.add(newModuleIssueType);
177: }
178: }
179:
180: /**
181: * Get the ReportAxisList value.
182: * @return the ReportAxisList value.
183: */
184: public List getReportAxisList() {
185: return reportAxisList;
186: }
187:
188: /**
189: * Set the ReportAxisList value.
190: * @param newReportAxisList The new ReportAxisList value.
191: */
192: public void setReportAxisList(List newReportAxisList) {
193: this .reportAxisList = newReportAxisList;
194: }
195:
196: /**
197: * Add a ReportAxis value.
198: * @param newReportAxis The new ReportAxis value.
199: */
200: public void addReportAxis(ReportAxis newReportAxis) {
201: if (reportAxisList == null) {
202: reportAxisList = new ArrayList();
203: }
204: reportAxisList.add(newReportAxis);
205: }
206:
207: /**
208: * Get the ReportDate value used if no axis is time.
209: * @return the ReportDate value.
210: */
211: public ReportDate getDefaultDate() {
212: return defaultDate;
213: }
214:
215: /**
216: * Set the ReportDate value used if no axis is time.
217: * @param newDefaultDate The new ReportDate value.
218: */
219: public void setDefaultDate(ReportDate newDefaultDate) {
220: this .defaultDate = newDefaultDate;
221: }
222:
223: // private String queryKey;
224:
225: /* *
226: * Get the QueryKey value.
227: * @return the QueryKey value.
228: * /
229: public String getQueryKey()
230: {
231: if (queryKey == null)
232: {
233: return "";
234: }
235: return queryKey;
236: }
237:
238: /* *
239: * Set the QueryKey value.
240: * @param newQueryKey The new QueryKey value.
241: * /
242: public void setQueryKey(String newQueryKey)
243: {
244: this.queryKey = newQueryKey;
245: }
246: */
247:
248: /**
249: * Gets the specified axis, if it was null prior to this method request
250: * a new ReportAxis is returned for the given index
251: *
252: * @param axisIndex an <code>int</code> value
253: * @return a <code>ReportHeading</code> value
254: */
255: public ReportAxis getAxis(int axisIndex) {
256: List axisList = getReportAxisList();
257: while (axisList == null || axisList.size() < axisIndex + 1) {
258: addReportAxis(new ReportAxis());
259: axisList = getReportAxisList();
260: }
261:
262: return (ReportAxis) axisList.get(axisIndex);
263: }
264:
265: public List retrieveAllReportOptionAttributes() {
266: List result = null;
267: List axes = getReportAxisList();
268: if (axes != null && !axes.isEmpty()) {
269: for (Iterator axi = axes.iterator(); axi.hasNext();) {
270: List headings = ((ReportAxis) axi.next())
271: .getReportHeadings();
272: if (headings != null && !headings.isEmpty()) {
273: for (Iterator hi = headings.iterator(); hi
274: .hasNext();) {
275: List options = ((ReportHeading) hi.next())
276: .consolidateReportOptionAttributes();
277: if (options != null && !options.isEmpty()) {
278: for (Iterator i = options.iterator(); i
279: .hasNext();) {
280: if (result == null) {
281: result = new ArrayList();
282: }
283: result.add(i.next());
284: }
285: }
286: }
287: }
288: }
289: }
290: return result == null ? Collections.EMPTY_LIST : result;
291: }
292:
293: public List retrieveAllReportUserAttributes() {
294: List result = null;
295: List axes = getReportAxisList();
296: if (axes != null && !axes.isEmpty()) {
297: for (Iterator axi = axes.iterator(); axi.hasNext();) {
298: List headings = ((ReportAxis) axi.next())
299: .getReportHeadings();
300: if (headings != null && !headings.isEmpty()) {
301: for (Iterator hi = headings.iterator(); hi
302: .hasNext();) {
303: List users = ((ReportHeading) hi.next())
304: .consolidateReportUserAttributes();
305: if (users != null && !users.isEmpty()) {
306: for (Iterator i = users.iterator(); i
307: .hasNext();) {
308: if (result == null) {
309: result = new ArrayList();
310: }
311: result.add(i.next());
312: }
313: }
314: }
315: }
316: }
317: }
318: return result == null ? Collections.EMPTY_LIST : result;
319: }
320:
321: public String toXmlString() {
322: String s;
323: try {
324: StringWriter sw = new StringWriter(1024);
325: BeanWriter bw = new BeanWriter(sw);
326: /*
327: {
328: {
329: writeIDs = false;
330: }
331: };
332: */
333: bw.enablePrettyPrint();
334: bw
335: .writeXmlDeclaration("<?xml version='1.0' encoding='UTF-8' ?>");
336: bw.write(this );
337: bw.flush();
338: s = sw.toString();
339: bw.close();
340: } catch (Exception e) {
341: s = "ERROR! on " + super .toString();
342: Log.get().error("", e);
343: }
344:
345: return s;
346: }
347:
348: public String displayAttribute(Object obj)
349: // throws TorqueException
350: {
351: Integer attId = null;
352: if (obj instanceof ReportOptionAttribute) {
353: try {
354: attId = new Integer(AttributeOptionManager.getInstance(
355: new NumberKey(((ReportOptionAttribute) obj)
356: .getOptionId().toString()))
357: .getAttributeId().toString());
358: } catch (Exception e) {
359: Log.get().error("Error on Attribute Id=" + attId, e);
360: return "Error on Attribute Id=" + attId;
361: }
362: } else if (obj instanceof ReportUserAttribute) {
363: attId = ((ReportUserAttribute) obj).getAttributeId();
364: } else {
365: return "";
366: }
367:
368: String result = null;
369: List mits = getModuleIssueTypes();
370: if (mits != null && mits.size() == 1) {
371: ModuleIssueType mit = (ModuleIssueType) mits.get(0);
372: try {
373: RModuleAttribute rma = RModuleAttributeManager
374: .getInstance(mit.getModuleId(), attId, mit
375: .getIssueTypeId());
376: result = rma.getDisplayValue();
377: } catch (Exception e) {
378: result = "Error on Attribute Id=" + attId;
379: Log.get().error(result, e);
380: }
381: } else {
382: try {
383: result = AttributeManager.getInstance(
384: new NumberKey(attId.toString())).getName();
385: } catch (Exception e) {
386: result = "Error on Attribute Id=" + attId;
387: Log.get().error(result, e);
388: }
389: }
390: return result;
391: }
392:
393: public String displayOption(ReportOptionAttribute roa)
394: // throws TorqueException
395: {
396: Integer optionId = roa.getOptionId();
397: String result = null;
398: List mits = getModuleIssueTypes();
399: if (mits != null && mits.size() == 1) {
400: ModuleIssueType mit = (ModuleIssueType) mits.get(0);
401: try {
402: RModuleOption rmo = RModuleOptionManager.getInstance(
403: mit.getModuleId(), mit.getIssueTypeId(),
404: optionId);
405: result = rmo.getDisplayValue();
406: } catch (Exception e) {
407: result = "Error on Option Id=" + optionId;
408: Log.get().error(result, e);
409: }
410: } else {
411: try {
412: result = AttributeOptionManager.getInstance(
413: new NumberKey(optionId.toString())).getName();
414: } catch (Exception e) {
415: result = "Error on Option Id=" + optionId;
416: Log.get().error(result, e);
417: }
418: }
419: return result;
420: }
421:
422: public String displayUser(ReportUserAttribute rua)
423: // throws TorqueException
424: {
425: String result = null;
426: try {
427: result = ScarabUserManager.getInstance(
428: new NumberKey(rua.getUserId().toString()))
429: .getName();
430: } catch (Exception e) {
431: result = "Error on Option Id=" + rua.getUserId();
432: Log.get().error(result, e);
433: }
434: return result;
435: }
436:
437: public String displayHeading(ReportHeading heading) {
438: String summary = null;
439: List options = heading.getReportOptionAttributes();
440: List groups = heading.getReportGroups();
441: List users = heading.getReportUserAttributes();
442: if (options != null && !options.isEmpty()) {
443: String attribute = null;
444: StringBuffer sb = new StringBuffer(20 * options.size());
445: for (Iterator i = options.iterator(); i.hasNext();) {
446: ReportOptionAttribute roa = (ReportOptionAttribute) i
447: .next();
448: String newAttribute = displayAttribute(roa);
449: if (!newAttribute.equals(attribute)) {
450: if (attribute != null) {
451: sb.append("; ");
452: }
453: attribute = newAttribute;
454: sb.append(attribute).append(": ");
455: }
456: sb.append(displayOption(roa)).append(", ");
457: }
458: sb.setLength(sb.length() - 2);
459: summary = sb.toString();
460: } else if (groups != null && !groups.isEmpty()) {
461: StringBuffer sb = new StringBuffer(10 * groups.size());
462: for (Iterator i = groups.iterator(); i.hasNext();) {
463: sb.append(((ReportGroup) i.next()).getName()).append(
464: '/');
465: }
466: sb.setLength(sb.length() - 1);
467: summary = sb.toString();
468: } else if (users != null && !users.isEmpty()) {
469: String attribute = null;
470: StringBuffer sb = new StringBuffer(20 * users.size());
471: for (Iterator i = users.iterator(); i.hasNext();) {
472: ReportUserAttribute rua = (ReportUserAttribute) i
473: .next();
474: String newAttribute = displayAttribute(rua);
475: if (!newAttribute.equals(attribute)) {
476: if (attribute != null) {
477: sb.append("; ");
478: }
479: attribute = newAttribute;
480: sb.append(attribute).append(": ");
481: }
482: sb.append(displayUser(rua)).append(", ");
483: }
484: sb.setLength(sb.length() - 2);
485: summary = sb.toString();
486: }
487: // FIXME: Date ranges are not implemented yet.
488: else if (heading.getReportDates() != null
489: && !heading.getReportDates().isEmpty())
490: //|| heading.getReportDateRanges() != null)
491: {
492: summary = "Dates";
493: }
494: return summary;
495: }
496:
497: /**
498: * The configured maximum from Scarab.properties:
499: * scarab.report.max.criteria
500: */
501: public int maximumHeadings() {
502: return MAX_CRITERIA;
503: }
504:
505: /**
506: * Determines whether another heading level is allowed for the given axis.
507: *
508: * @param axis a non-null <code>ReportAxis</code> value
509: */
510: public boolean allowMoreHeadings(ReportAxis axis) {
511: return availableNumberOfHeadings(axis) > 0;
512: }
513:
514: /**
515: * Determines whether the report is going to be expensive. This is based
516: * on a comparison of the number of heading levels on both axes with
517: * the configured maximum in Scarab.properties.
518: */
519: public boolean reportQueryIsExpensive() {
520: return totalNumberOfNonDateHeadings() > MAX_CRITERIA;
521: }
522:
523: /**
524: * The number of headings that could still be added to the report without
525: * exceeding the configured maximum.
526: */
527: public int totalAvailableNumberOfHeadings() {
528: return maximumHeadings() - totalNumberOfNonDateHeadings();
529: }
530:
531: /**
532: * The number of headings that could still be added to the given axis
533: * without exceeding the configured maximum.
534: *
535: * @param axis a non-null <code>ReportAxis</code> value
536: */
537: public int availableNumberOfHeadings(ReportAxis axis) {
538: // the following assumes two axes
539: int result = maximumHeadings() - totalNumberOfNonDateHeadings()
540: - 1;
541: List axes = getReportAxisList();
542: if (axes != null) {
543: ReportAxis tmpAxis;
544: for (Iterator i = axes.iterator(); i.hasNext();) {
545: tmpAxis = (ReportAxis) i.next();
546: if (tmpAxis != null && !tmpAxis.equals(axis)) {
547: List headings = tmpAxis.getReportHeadings();
548: if (headings != null
549: && headings.size() > 0
550: && (((ReportHeading) headings.get(0))
551: .size() > 0)) {
552: result++;
553: }
554: break;
555: }
556: }
557: }
558: return result;
559: }
560:
561: private int totalNumberOfNonDateHeadings() {
562: int count = 0;
563: List axes = getReportAxisList();
564: if (axes != null) {
565: ReportAxis axis;
566: for (Iterator i = axes.iterator(); i.hasNext();) {
567: axis = (ReportAxis) i.next();
568: if (axis != null) {
569: count += numberOfNonDateHeadings(axis);
570: }
571: }
572: }
573: return count;
574: }
575:
576: private int numberOfNonDateHeadings(ReportAxis axis) {
577: int count = 0;
578: List headings = axis.getReportHeadings();
579: if (headings != null) {
580: int size = headings.size();
581: if (size > 1) {
582: count += size;
583: } else if (size == 1) {
584: ReportHeading firstHeading = (ReportHeading) headings
585: .get(0);
586: if (firstHeading != null && firstHeading.size() > 0) {
587: Object firstEntry = firstHeading.get(0);
588: if (!(firstEntry instanceof ReportDate)) {
589: count += 1;
590: }
591: }
592: }
593: }
594: return count;
595: }
596: }
|