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: // JDK classes
050: import java.util.Date;
051: import java.util.Iterator;
052: import java.util.List;
053: import com.workingdogs.village.Record;
054:
055: // Turbine classes
056: import org.apache.torque.util.Criteria;
057:
058: import org.tigris.scarab.om.ModuleManager;
059: import org.tigris.scarab.om.MITList;
060: import org.tigris.scarab.om.MITListItem;
061: import org.tigris.scarab.om.ScarabUser;
062: import org.tigris.scarab.om.ActivityPeer;
063: import org.tigris.scarab.om.ActivitySetPeer;
064: import org.tigris.scarab.om.ActivitySetTypePeer;
065: import org.tigris.scarab.om.IssuePeer;
066: import org.tigris.scarab.util.TableModel;
067: import org.tigris.scarab.services.security.ScarabSecurity;
068:
069: /**
070: * This class represents a ReportTableModel.
071: *
072: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
073: * @version $Id: ReportTableModel.java 10051 2006-04-13 19:05:58Z jorgeuriarte $
074: */
075: public class ReportTableModel extends TableModel {
076: private static final String ACT_ATTRIBUTE_ID = ActivityPeer.ATTRIBUTE_ID
077: .substring(ActivityPeer.ATTRIBUTE_ID.indexOf('.') + 1);
078: private static final String ACT_NEW_USER_ID = ActivityPeer.NEW_USER_ID
079: .substring(ActivityPeer.NEW_USER_ID.indexOf('.') + 1);
080: private static final String ACT_NEW_OPTION_ID = ActivityPeer.NEW_OPTION_ID
081: .substring(ActivityPeer.NEW_OPTION_ID.indexOf('.') + 1);
082: private static final String ACT_ISSUE_ID = ActivityPeer.ISSUE_ID
083: .substring(ActivityPeer.ISSUE_ID.indexOf('.') + 1);
084: private static final String ACT_TRANSACTION_ID = ActivityPeer.TRANSACTION_ID
085: .substring(ActivityPeer.TRANSACTION_ID.indexOf('.') + 1);
086: private static final String ACT_END_DATE = ActivityPeer.END_DATE
087: .substring(ActivityPeer.END_DATE.indexOf('.') + 1);
088: private static final String TRAN_TRANSACTION_ID = ActivitySetPeer.TRANSACTION_ID
089: .substring(ActivitySetPeer.TRANSACTION_ID.indexOf('.') + 1);
090: private static final String TRAN_CREATED_DATE = ActivitySetPeer.CREATED_DATE
091: .substring(ActivitySetPeer.CREATED_DATE.indexOf('.') + 1);
092: private static final String TRAN_CREATED_BY = ActivitySetPeer.CREATED_BY
093: .substring(ActivitySetPeer.CREATED_BY.indexOf('.') + 1);
094: private static final String TRAN_TYPE_ID = ActivitySetPeer.TYPE_ID
095: .substring(ActivitySetPeer.TYPE_ID.indexOf('.') + 1);
096:
097: private ReportDefinition reportDefn;
098: private List rowHeadings;
099: private List columnHeadings;
100: private Date date;
101: private Integer moduleId;
102: private Integer issueTypeId;
103: private MITList mitList;
104:
105: private int[] colspan;
106: private int[] rowspan;
107: private boolean isSearchAllowed;
108:
109: ReportTableModel(ReportBridge report, Date date, ScarabUser searcher)
110: throws Exception {
111: this .reportDefn = report.getReportDefinition();
112: ReportAxis axis = null;
113: List axes = reportDefn.getReportAxisList();
114: if (axes != null && axes.size() >= 2) {
115: axis = (ReportAxis) axes.get(1);
116: }
117: if (axis != null) {
118: columnHeadings = axis.getReportHeadings();
119: }
120:
121: if (axes != null && axes.size() >= 1) {
122: axis = (ReportAxis) axes.get(0);
123: }
124: if (axis != null) {
125: rowHeadings = axis.getReportHeadings();
126: }
127:
128: this .date = date;
129:
130: List xmits = reportDefn.getModuleIssueTypes();
131: if (xmits.size() == 1) {
132: ModuleIssueType mit = (ModuleIssueType) xmits.get(0);
133: this .moduleId = mit.getModuleId();
134: this .issueTypeId = mit.getIssueTypeId();
135: isSearchAllowed = searcher.hasPermission(
136: ScarabSecurity.ISSUE__SEARCH, ModuleManager
137: .getInstance(moduleId));
138: } else {
139: String[] perms = { ScarabSecurity.ISSUE__SEARCH };
140: MITList searchableList = report.getMITList()
141: .getPermittedSublist(perms, searcher);
142: isSearchAllowed = searchableList.size() > 0;
143: if (searchableList.size() == 1) {
144: MITListItem item = searchableList.getFirstItem();
145: this .moduleId = item.getModuleId();
146: this .issueTypeId = item.getIssueTypeId();
147: } else {
148: this .mitList = searchableList;
149: }
150: }
151: }
152:
153: /**
154: * Get the RowHeadings value.
155: * @return the RowHeadings value.
156: */
157: public List getRowHeadings() {
158: return rowHeadings;
159: }
160:
161: /**
162: * Get the ColumnHeadings value.
163: * @return the ColumnHeadings value.
164: */
165: public List getColumnHeadings() {
166: return columnHeadings;
167: }
168:
169: public int getColspan(int index) {
170: int result = 1;
171: if (columnHeadings != null) {
172: if (colspan == null) {
173: int numLevels = columnHeadings.size();
174: colspan = new int[numLevels - 1];
175: for (int j = 0; j < numLevels - 1; j++) {
176: colspan[j] = 1;
177: for (int i = numLevels - 1; i > j; i--) {
178: colspan[j] *= ((ReportHeading) columnHeadings
179: .get(i)).size();
180: }
181: }
182: }
183:
184: if (index < colspan.length) {
185: result = colspan[index];
186: }
187: }
188:
189: return result;
190: }
191:
192: public int getRowspan(int index) {
193: int result = 1;
194: if (rowHeadings != null) {
195: if (rowspan == null) {
196: int numLevels = rowHeadings.size();
197: rowspan = new int[numLevels - 1];
198: for (int j = 0; j < numLevels - 1; j++) {
199: rowspan[j] = 1;
200: for (int i = numLevels - 1; i > j; i--) {
201: rowspan[j] *= ((ReportHeading) rowHeadings
202: .get(i)).size();
203: }
204: }
205: }
206:
207: if (index < rowspan.length) {
208: result = rowspan[index];
209: }
210: }
211:
212: return result;
213: }
214:
215: public int getColumnCount() {
216: return ((ReportHeading) columnHeadings.get(0)).size()
217: * getColspan(0);
218: }
219:
220: public int getRowCount() {
221: return ((ReportHeading) rowHeadings.get(0)).size()
222: * getRowspan(0);
223: }
224:
225: private Object[] getColumnDataArray(int column) {
226: Object[] dataArray;
227: if (columnHeadings == null) {
228: dataArray = new Object[0];
229: } else {
230: int numLevels = columnHeadings.size();
231: dataArray = new Object[numLevels];
232: int index = 0;
233: for (Iterator i = columnHeadings.iterator(); i.hasNext(); index++) {
234: ReportHeading heading = (ReportHeading) i.next();
235: dataArray[index] = heading
236: .get((column / getColspan(index))
237: % heading.size());
238: }
239: }
240:
241: return dataArray;
242: }
243:
244: private Object[] getRowDataArray(int row) {
245: Object[] dataArray;
246: if (rowHeadings == null) {
247: dataArray = new Object[0];
248: } else {
249: int numLevels = rowHeadings.size();
250: dataArray = new Object[numLevels];
251: int index = 0;
252: for (Iterator i = rowHeadings.iterator(); i.hasNext(); index++) {
253: ReportHeading heading = (ReportHeading) i.next();
254: dataArray[index] = heading
255: .get((row / getRowspan(index)) % heading.size());
256: }
257: }
258: return dataArray;
259: }
260:
261: public Object getValueAt(int row, int column) throws Exception {
262: if (row < 0 || row >= getRowCount()) {
263: throw new IndexOutOfBoundsException("Row index was " + row); //EXCEPTION
264: }
265:
266: if (column < 0 || column >= getColumnCount()) {
267: throw new IndexOutOfBoundsException("Column index was "
268: + column); //EXCEPTION
269: }
270:
271: Object contents = null;
272: // could use a categories list to make this simpler
273: if (columnHeadings != null
274: && columnHeadings.size() == 1
275: && ((ReportHeading) columnHeadings.get(0)).get(0) instanceof ReportDate) {
276: Date date = ((ReportDate) ((ReportHeading) columnHeadings
277: .get(0)).get(column)).dateValue();
278: if (date.getTime() <= System.currentTimeMillis()) {
279: contents = new Integer(getIssueCount(
280: getRowDataArray(row), date));
281: } else {
282: // Dates in the future are not applicable to reporting.
283: contents = "";
284: }
285: } else if (rowHeadings != null
286: && rowHeadings.size() == 1
287: && ((ReportHeading) rowHeadings.get(0)).get(0) instanceof ReportDate) {
288: Date date = ((ReportDate) ((ReportHeading) rowHeadings
289: .get(0)).get(row)).dateValue();
290: if (date.getTime() <= System.currentTimeMillis()) {
291: contents = new Integer(getIssueCount(
292: getColumnDataArray(column), date));
293: } else {
294: // Dates in the future are not applicable to reporting.
295: contents = "";
296: }
297: } else {
298: contents = new Integer(getIssueCount(getRowDataArray(row),
299: getColumnDataArray(column), date));
300: }
301:
302: return contents;
303: }
304:
305: private int getIssueCount(Object[] rowData, Object[] columnData,
306: Date date) throws Exception {
307: Criteria crit = new Criteria();
308: // select count(issue_id) from activity a1 a2 a3, activitySet t1 t2 t3
309: crit.addSelectColumn("count(DISTINCT a0." + ACT_ISSUE_ID + ')');
310: int rowLength = rowData.length;
311: for (int i = 0; i < rowLength; i++) {
312: addOptionOrGroup(i, rowData[i], date, crit);
313:
314: }
315: for (int i = 0; i < columnData.length; i++) {
316: addOptionOrGroup(i + rowLength, columnData[i], date, crit);
317: }
318: return getCountAndCleanUp(crit);
319: }
320:
321: public int getIssueCount(Object[] dataArray, Date date)
322: throws Exception {
323: Criteria crit = new Criteria();
324: crit.addSelectColumn("count(DISTINCT a0." + ACT_ISSUE_ID + ')');
325: for (int i = 0; i < dataArray.length; i++) {
326: addOptionOrGroup(i, dataArray[i], date, crit);
327: }
328: return getCountAndCleanUp(crit);
329: }
330:
331: private void addOptionOrGroup(int alias, Object optionOrGroup,
332: Date date, Criteria crit) {
333: if (optionOrGroup == null) {
334: throw new NullPointerException(
335: "cell definition cannot contain nulls"); //EXCEPTION
336: }
337:
338: String a = "a" + alias;
339: String t = "t" + alias;
340: // there is some redundancy here, but over specifying the joins usually
341: // allows the query optimizer to obtain a better optimization
342: crit.addJoin(a + '.' + ACT_ISSUE_ID, IssuePeer.ISSUE_ID);
343: for (int i = alias - 1; i >= 0; i--) {
344: crit.addJoin("a" + i + '.' + ACT_ISSUE_ID, a + '.'
345: + ACT_ISSUE_ID);
346: }
347: crit.addAlias("a" + alias, ActivityPeer.TABLE_NAME);
348: crit.addAlias("t" + alias, ActivitySetPeer.TABLE_NAME);
349:
350: crit.addJoin(a + "." + ACT_TRANSACTION_ID, t + '.'
351: + TRAN_TRANSACTION_ID);
352: crit.add(t, TRAN_CREATED_DATE, date, Criteria.LESS_THAN);
353: // end date criteria
354: Criteria.Criterion c1 = crit.getNewCriterion(a, ACT_END_DATE,
355: date, Criteria.GREATER_THAN);
356: c1.or(crit.getNewCriterion(a, ACT_END_DATE, null,
357: Criteria.EQUAL));
358: crit.add(c1);
359:
360: if (optionOrGroup instanceof ReportGroup) {
361: List options = ((ReportGroup) optionOrGroup)
362: .getReportOptionAttributes();
363: if (options != null && options.size() > 0) {
364: Integer[] nks = new Integer[options.size()];
365: for (int i = 0; i < nks.length; i++) {
366: nks[i] = ((ReportOptionAttribute) options.get(i))
367: .getOptionId();
368: }
369:
370: crit.addIn(a + '.' + ACT_NEW_OPTION_ID, nks);
371: } else {
372: List users = ((ReportGroup) optionOrGroup)
373: .getReportUserAttributes();
374: if (users != null && users.size() > 0) {
375: Integer[] nks = new Integer[users.size()];
376: for (int i = 0; i < nks.length; i++) {
377: nks[i] = ((ReportUserAttribute) users.get(i))
378: .getUserId();
379: }
380:
381: crit.addIn(a + '.' + ACT_NEW_USER_ID, nks);
382: } else {
383: // group is empty make sure there are no results
384: crit.add(a + '.' + ACT_NEW_OPTION_ID, -1);
385: }
386: }
387: } else if (optionOrGroup instanceof ReportOptionAttribute) {
388: crit.add(a, ACT_NEW_OPTION_ID,
389: ((ReportOptionAttribute) optionOrGroup)
390: .getOptionId());
391: } else if (optionOrGroup instanceof ReportUserAttribute) {
392: ReportUserAttribute rua = (ReportUserAttribute) optionOrGroup;
393: Integer attributeId = rua.getAttributeId();
394: if (attributeId.intValue() == 0) {
395: // committed by
396: crit.add(t, TRAN_TYPE_ID,
397: ActivitySetTypePeer.CREATE_ISSUE__PK);
398: crit.add(t, TRAN_CREATED_BY, rua.getUserId());
399: } else {
400: crit.add(a, ACT_ATTRIBUTE_ID, rua.getAttributeId());
401: crit.add(a, ACT_NEW_USER_ID, rua.getUserId());
402: }
403: }
404: }
405:
406: private boolean isXMITSearch() {
407: return mitList != null && !mitList.isSingleModuleIssueType();
408: }
409:
410: private int getCountAndCleanUp(Criteria crit) throws Exception {
411: int result = 0;
412: if (isSearchAllowed) {
413: if (isXMITSearch()) {
414: mitList.addToCriteria(crit);
415: } else {
416: crit.add(IssuePeer.MODULE_ID, moduleId);
417: crit.add(IssuePeer.TYPE_ID, issueTypeId);
418: }
419: crit.add(IssuePeer.DELETED, false);
420: crit.add(IssuePeer.MOVED, false);
421: result = ((Record) ActivityPeer
422: .doSelectVillageRecords(crit).get(0)).getValue(1)
423: .asInt();
424: }
425:
426: return result;
427: }
428:
429: public boolean isOption(Object obj) {
430: return obj instanceof ReportOptionAttribute;
431: }
432:
433: public boolean isOptionGroup(Object obj) {
434: return obj instanceof ReportGroup;
435: }
436:
437: public boolean isAttributeAndUser(Object obj) {
438: return obj instanceof ReportUserAttribute;
439: }
440:
441: public boolean isUser(Object obj) {
442: return obj instanceof ScarabUser;
443: }
444:
445: public boolean isReportDate(Object obj) {
446: return obj instanceof ReportDate;
447: }
448:
449: public String displayAttribute(Object cell) {
450: return reportDefn.displayAttribute(cell);
451: }
452:
453: public String displayOption(ReportOptionAttribute cell) {
454: return reportDefn.displayOption(cell);
455: }
456:
457: public String displayUser(ReportUserAttribute cell) {
458: return reportDefn.displayUser(cell);
459: }
460: }
|