0001: package org.tigris.scarab.om;
0002:
0003: /* ================================================================
0004: * Copyright (c) 2000-2005 CollabNet. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions are
0008: * met:
0009: *
0010: * 1. Redistributions of source code must retain the above copyright
0011: * notice, this list of conditions and the following disclaimer.
0012: *
0013: * 2. Redistributions in binary form must reproduce the above copyright
0014: * notice, this list of conditions and the following disclaimer in the
0015: * documentation and/or other materials provided with the distribution.
0016: *
0017: * 3. The end-user documentation included with the redistribution, if
0018: * any, must include the following acknowlegement: "This product includes
0019: * software developed by Collab.Net <http://www.Collab.Net/>."
0020: * Alternately, this acknowlegement may appear in the software itself, if
0021: * and wherever such third-party acknowlegements normally appear.
0022: *
0023: * 4. The hosted project names must not be used to endorse or promote
0024: * products derived from this software without prior written
0025: * permission. For written permission, please contact info@collab.net.
0026: *
0027: * 5. Products derived from this software may not use the "Tigris" or
0028: * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without
0029: * prior written permission of Collab.Net.
0030: *
0031: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0032: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
0033: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
0034: * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
0035: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0036: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
0037: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0038: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
0039: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
0040: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
0041: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0042: *
0043: * ====================================================================
0044: *
0045: * This software consists of voluntary contributions made by many
0046: * individuals on behalf of Collab.Net.
0047: */
0048:
0049: // JDK classes
0050: import com.workingdogs.village.DataSetException;
0051: import java.io.FileNotFoundException;
0052: import java.io.IOException;
0053: import java.io.Serializable;
0054: import java.sql.Connection;
0055: import java.util.ArrayList;
0056: import java.util.Arrays;
0057: import java.util.Date;
0058: import java.util.HashMap;
0059: import java.util.HashSet;
0060: import java.util.Iterator;
0061: import java.util.List;
0062: import java.util.Locale;
0063: import java.util.Map;
0064: import java.util.Set;
0065:
0066: import org.apache.commons.collections.MapIterator;
0067: import org.apache.commons.collections.map.LinkedMap;
0068: import org.apache.commons.lang.ObjectUtils;
0069: import org.apache.commons.lang.StringUtils;
0070: import org.apache.fulcrum.localization.Localization;
0071: import org.apache.torque.TorqueException;
0072: import org.apache.torque.manager.MethodResultCache;
0073: import org.apache.torque.map.DatabaseMap;
0074: import org.apache.torque.oid.IDBroker;
0075: import org.apache.torque.om.Persistent;
0076: import org.apache.torque.util.BasePeer;
0077: import org.apache.torque.util.Criteria;
0078: import org.apache.turbine.Turbine;
0079: import org.tigris.scarab.attribute.OptionAttribute;
0080: import org.tigris.scarab.attribute.TotalVotesAttribute;
0081: import org.tigris.scarab.attribute.UserAttribute;
0082: import org.tigris.scarab.notification.ActivityType;
0083: import org.tigris.scarab.notification.NotificationManagerFactory;
0084: import org.tigris.scarab.services.cache.ScarabCache;
0085: import org.tigris.scarab.services.security.ScarabSecurity;
0086: import org.tigris.scarab.tools.ScarabGlobalTool;
0087: import org.tigris.scarab.tools.localization.L10NKeySet;
0088: import org.tigris.scarab.tools.localization.L10NMessage;
0089: import org.tigris.scarab.util.Log;
0090: import org.tigris.scarab.util.MutableBoolean;
0091: import org.tigris.scarab.util.ScarabConstants;
0092: import org.tigris.scarab.util.ScarabException;
0093: import org.tigris.scarab.util.ScarabRuntimeException;
0094: import org.tigris.scarab.workflow.WorkflowFactory;
0095:
0096: import com.workingdogs.village.Record;
0097:
0098: /**
0099: * This class represents an Issue.
0100: *
0101: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
0102: * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
0103: * @author <a href="mailto:elicia@collab.net">Elicia David</a>
0104: * @version $Id: Issue.java 10331 2006-11-07 10:17:38Z ronvoe122 $
0105: */
0106: public class Issue extends BaseIssue implements Persistent {
0107: // the following Strings are method names that are used in caching results
0108: protected static final String GET_ATTRIBUTE_VALUES_MAP = "getAttributeValuesMap";
0109: protected static final String GET_ASSOCIATED_USERS = "getAssociatedUsers";
0110: protected static final String GET_MODULE_ATTRVALUES_MAP = "getModuleAttributeValuesMap";
0111: protected static final String GET_ATTRVALUE = "getAttributeValue";
0112: protected static final String GET_ATTRVALUES = "getAttributeValues";
0113: protected static final String GET_ALL_USERS_TO_EMAIL = "getAllUsersToEmail";
0114: protected static final String GET_USER_ATTRIBUTEVALUE = "getUserAttributeValue";
0115: protected static final String GET_USER_ATTRIBUTEVALUES = "getUserAttributeValues";
0116: protected static final String GET_CREATED_DATE = "getCreatedDate";
0117: protected static final String GET_CREATED_BY = "getCreatedBy";
0118: protected static final String GET_LAST_TRANSACTION = "getLastActivitySet";
0119: protected static final String GET_MODIFIED_BY = "getModifiedBy";
0120: protected static final String GET_MODIFIED_DATE = "getModifiedDate";
0121: protected static final String GET_COMMENTS = "getComments";
0122: protected static final String GET_URLS = "getUrls";
0123: protected static final String GET_EXISTING_ATTACHMENTS = "getExistingAttachments";
0124: protected static final String GET_ACTIVITY = "getActivity";
0125: protected static final String GET_TRANSACTIONS = "getActivitySets";
0126: protected static final String GET_CHILDREN = "getChildren";
0127: protected static final String GET_PARENTS = "getParents";
0128: protected static final String GET_ALL_DEPENDENCY_TYPES = "getAllDependencyTypes";
0129: protected static final String GET_DEPENDENCY = "getDependency";
0130: protected static final String GET_TEMPLATE_TYPES = "getTemplateTypes";
0131: protected static final String GET_TEMPLATEINFO = "getTemplateInfo";
0132: protected static final String GET_CLOSED_DATE = "getClosedDate";
0133: protected static final String GET_ORPHAN_ATTRIBUTEVALUES_LIST = "getNonMatchingAttributeValuesList";
0134: protected static final String GET_DEFAULT_TEXT_ATTRIBUTEVALUE = "getDefaultTextAttributeValue";
0135: protected static final String GET_DEFAULT_TEXT = "getDefaultText";
0136: protected static final String GET_NULL_END_DATE = "getActivitiesWithNullEndDate";
0137: protected static final String GET_INITIAL_ACTIVITYSET = "getInitialActivitySet";
0138: protected static final String GET_HISTORY_LIMIT = "getHistoryLimit";
0139:
0140: private static final Integer NUMBERKEY_0 = new Integer(0);
0141: private static final Integer COPIED = new Integer(1);
0142: private static final Integer MOVED = new Integer(2);
0143:
0144: /** storage for any attachments which have not been saved yet */
0145: private List unSavedAttachments = null;
0146:
0147: /**
0148: * new issues are created only when the issuetype and module are known
0149: * Or by the Peer when retrieving from db
0150: */
0151: protected Issue() {
0152: }
0153:
0154: protected Issue(Module module, IssueType issueType)
0155: throws TorqueException {
0156: this ();
0157: setModule(module);
0158: setIssueType(issueType);
0159: }
0160:
0161: /**
0162: * Gets an issue associated to a Module
0163: */
0164: public static Issue getNewInstance(Module module,
0165: IssueType issueType) throws TorqueException {
0166: Issue issue = new Issue(module, issueType);
0167: return issue;
0168: }
0169:
0170: /**
0171: * @deprecated use IssueManager.getIssueById
0172: */
0173: public static Issue getIssueById(String id) {
0174: return IssueManager.getIssueById(id);
0175: }
0176:
0177: /**
0178: * @deprecated use IssueManager.getIssueById
0179: */
0180: public static Issue getIssueById(Issue.FederatedId fid) {
0181: return IssueManager.getIssueByIdImpl(fid);
0182: }
0183:
0184: /**
0185: * Gets the UniqueId for this Issue.
0186: */
0187: public String getUniqueId() throws TorqueException {
0188: if (getIdPrefix() == null) {
0189: setIdPrefix(getModule().getCode());
0190: }
0191: return getIdPrefix() + getIdCount();
0192: }
0193:
0194: /**
0195: * NoOp for intake's benefit
0196: */
0197: public void setUniqueId(String id) {
0198: }
0199:
0200: public String getFederatedId() throws TorqueException {
0201: if (getIdDomain() != null) {
0202: return getIdDomain() + '-' + getUniqueId();
0203: }
0204: return getUniqueId();
0205: }
0206:
0207: public void setFederatedId(String id) {
0208: FederatedId fid = new FederatedId(id);
0209: setIdDomain(fid.getDomain());
0210: setIdPrefix(fid.getPrefix());
0211: setIdCount(fid.getCount());
0212: }
0213:
0214: /**
0215: * A FederatedId has this format: {Domain}-{Code}{Id}
0216: * For example: collab.net-PACS1
0217: * The domain can also be null.
0218: */
0219: public static class FederatedId implements Serializable {
0220: private String domainId;
0221: private String prefix;
0222: private int count;
0223:
0224: public FederatedId(String id) {
0225: int dash = id.indexOf('-');
0226: if (dash > 0) {
0227: domainId = id.substring(0, dash);
0228: setUniqueId(id.substring(dash + 1));
0229: } else {
0230: setUniqueId(id);
0231: }
0232: }
0233:
0234: public FederatedId(String domain, String prefix, int count) {
0235: this .domainId = domain;
0236: setPrefix(prefix);
0237: this .count = count;
0238: }
0239:
0240: /**
0241: * @param id The unique identifier for this issue, generally a
0242: * combination of code and sequence number (e.g. SCB37).
0243: */
0244: public void setUniqueId(String id) {
0245: int codeLength = ScarabGlobalTool.getModuleCodeLength();
0246: // we could start at 1 here, if the spec says one char is
0247: // required, will keep it safe for now.
0248: StringBuffer code = new StringBuffer(codeLength);
0249: int max = id.length() < codeLength ? id.length()
0250: : codeLength;
0251: for (int i = 0; i < max; i++) {
0252: char c = id.charAt(i);
0253: if (c < '0' || c > '9') {
0254: code.append(c);
0255: }
0256: }
0257: if (code.length() != 0) {
0258: setPrefix(code.toString());
0259: }
0260: count = Integer.parseInt(id.substring(code.length()));
0261: }
0262:
0263: /**
0264: * @return The domain.
0265: */
0266: public String getDomain() {
0267: return domainId;
0268: }
0269:
0270: /**
0271: * @return The prefix (upper-cased).
0272: */
0273: public String getPrefix() {
0274: return prefix;
0275: }
0276:
0277: /**
0278: * @return The sequence of this issue within its code.
0279: */
0280: public int getCount() {
0281: return count;
0282: }
0283:
0284: /**
0285: * Set the domainId
0286: */
0287: public void setDomain(String domainId) {
0288: this .domainId = domainId;
0289: }
0290:
0291: /**
0292: * @param prefix The module code.
0293: */
0294: public void setPrefix(String prefix) {
0295: if (prefix != null) {
0296: this .prefix = prefix.toUpperCase();
0297: }
0298: }
0299:
0300: /**
0301: * @param count The sequence of this issue within its code.
0302: */
0303: public void setCount(int count) {
0304: this .count = count;
0305: }
0306:
0307: public boolean equals(Object obj) {
0308: boolean b = false;
0309: if (obj instanceof FederatedId) {
0310: FederatedId fid = (FederatedId) obj;
0311: b = fid.count == this .count;
0312: b &= ObjectUtils.equals(fid.domainId, domainId);
0313: b &= ObjectUtils.equals(fid.prefix, prefix);
0314: }
0315: return b;
0316: }
0317:
0318: public int hashCode() {
0319: int hc = count;
0320: if (domainId != null) {
0321: hc += domainId.hashCode();
0322: }
0323: if (prefix != null) {
0324: hc += prefix.hashCode();
0325: }
0326: return hc;
0327: }
0328: }
0329:
0330: /**
0331: * @param module The current module.
0332: * @param theList A textual representation of the list of issues
0333: * to parse.
0334: * @param The parsed list of issue identifiers.
0335: */
0336: public static List parseIssueList(final Module module,
0337: final String theList) throws TorqueException,
0338: DataSetException {
0339: final String[] issues = StringUtils.split(theList, ",");
0340: final List results = new ArrayList();
0341: for (int i = 0; i < issues.length; i++) {
0342: if (issues[i].indexOf('*') != -1) {
0343: // Probably better to use more Torque here, but this
0344: // is definitely going to be faster and more
0345: // efficient.
0346: final String sql = "SELECT CONCAT("
0347: + IssuePeer.ID_PREFIX + ','
0348: + IssuePeer.ID_COUNT + ") FROM "
0349: + IssuePeer.TABLE_NAME + " WHERE "
0350: + IssuePeer.ID_PREFIX + " = '"
0351: + module.getCode() + '\'';
0352: final List records = BasePeer.executeQuery(sql);
0353: for (Iterator j = records.iterator(); j.hasNext();) {
0354: final Record rec = (Record) j.next();
0355: results.add(rec.getValue(1).asString());
0356: }
0357: }
0358: // check for a -
0359: else if (issues[i].indexOf('-') == -1) {
0360: // Make sure user is not trying to access issues from another
0361: // module.
0362: final FederatedId fid = createFederatedId(module,
0363: issues[i]);
0364: if (!fid.getPrefix().equalsIgnoreCase(module.getCode())) {
0365: final String[] args = { fid.getPrefix(),
0366: module.getCode() };
0367: throw new TorqueException(Localization.format(
0368: ScarabConstants.DEFAULT_BUNDLE_NAME, module
0369: .getLocale(),
0370: "IssueIDPrefixNotForModule", args)); //EXCEPTION
0371: }
0372: results.add(issues[i]);
0373: } else {
0374: final String[] issue = StringUtils
0375: .split(issues[i], "-");
0376: if (issue.length != 2) {
0377: throw new TorqueException(Localization.format(
0378: ScarabConstants.DEFAULT_BUNDLE_NAME, module
0379: .getLocale(),
0380: "IssueIDRangeNotValid", issues[i])); //EXCEPTION
0381: }
0382: FederatedId fidStart = createFederatedId(module,
0383: issue[0]);
0384: FederatedId fidStop = createFederatedId(module,
0385: issue[1]);
0386: if (!fidStart.getPrefix().equalsIgnoreCase(
0387: module.getCode())
0388: || !fidStop.getPrefix().equalsIgnoreCase(
0389: module.getCode())) {
0390: throw new TorqueException(Localization.format(
0391: ScarabConstants.DEFAULT_BUNDLE_NAME, module
0392: .getLocale(),
0393: "IssueIDPrefixesNotForModule", module
0394: .getCode())); //EXCEPTION
0395: } else if (!fidStart.getPrefix().equalsIgnoreCase(
0396: fidStop.getPrefix())) {
0397: final String[] args = { fidStart.getPrefix(),
0398: fidStop.getPrefix() };
0399: throw new TorqueException(Localization.format(
0400: ScarabConstants.DEFAULT_BUNDLE_NAME, module
0401: .getLocale(),
0402: "IssueIDPrefixesDoNotMatch", args)); //EXCEPTION
0403: } else if (fidStart.getCount() > fidStop.getCount()) {
0404: FederatedId swap = fidStart;
0405: fidStart = fidStop;
0406: fidStop = swap;
0407: }
0408:
0409: for (int j = fidStart.getCount(); j <= fidStop
0410: .getCount(); j++) {
0411: results.add(fidStart.getPrefix() + j);
0412: }
0413: }
0414: }
0415: return results;
0416: }
0417:
0418: /**
0419: * Catches and rethrows parsing errors when creating the federated id.
0420: */
0421: private static FederatedId createFederatedId(Module module,
0422: String id) throws TorqueException {
0423: FederatedId fid = null;
0424: try {
0425: fid = new FederatedId(id.trim());
0426: if (fid.getPrefix() == null
0427: || fid.getPrefix().length() == 0) {
0428: fid.setPrefix(module.getCode());
0429: }
0430: } catch (Exception e) {
0431: throw new TorqueException("Invalid federated id: " + id); //EXCEPTION
0432: }
0433: return fid;
0434: }
0435:
0436: /**
0437: * Whether this issue is an enter issue template.
0438: */
0439: public boolean isTemplate() {
0440: boolean isTemplate = false;
0441: try {
0442: isTemplate = !getIssueType().getParentId().equals(
0443: NUMBERKEY_0);
0444: } catch (Exception e) {
0445: getLog().error(
0446: "Problem determining whether issue is template");
0447: }
0448: return isTemplate;
0449: }
0450:
0451: /**
0452: * Adds a url to an issue and passes null as the activity set
0453: * to create a new one.
0454: */
0455: public ActivitySet addUrl(final Attachment attachment,
0456: final ScarabUser user) throws TorqueException,
0457: ScarabException {
0458: return addUrl(null, attachment, user);
0459: }
0460:
0461: /**
0462: * Adds a url to an issue.
0463: */
0464: public ActivitySet addUrl(ActivitySet activitySet,
0465: final Attachment attachment, final ScarabUser user)
0466: throws TorqueException, ScarabException {
0467: attachment.setTextFields(user, this , Attachment.URL__PK);
0468: attachment.save();
0469:
0470: activitySet = attachActivitySet(activitySet, user);
0471:
0472: // Save activity record
0473: ActivityManager.createTextActivity(this , activitySet,
0474: ActivityType.URL_ADDED, attachment);
0475:
0476: return activitySet;
0477: }
0478:
0479: private Locale getLocale() throws TorqueException {
0480: return getModule().getLocale();
0481: }
0482:
0483: /**
0484: * Adds a comment to an issue and passes null as the activity set
0485: * to create a new one.
0486: */
0487: public ActivitySet addComment(final Attachment attachment,
0488: final ScarabUser user) throws TorqueException,
0489: ScarabException {
0490: return addComment(null, attachment, user);
0491: }
0492:
0493: /**
0494: * Adds a comment to an issue.
0495: */
0496: public ActivitySet addComment(ActivitySet activitySet,
0497: Attachment attachment, ScarabUser user)
0498: throws TorqueException, ScarabException {
0499: String comment = attachment.getData();
0500: if (comment == null || comment.length() == 0) {
0501: throw new ScarabException(L10NKeySet.NoDataInComment);
0502: }
0503:
0504: activitySet = attachActivitySet(activitySet, user);
0505:
0506: // populates the attachment with data to be a comment
0507: attachment = AttachmentManager.getComment(attachment, this ,
0508: user);
0509:
0510: ActivityManager.createTextActivity(this , activitySet,
0511: ActivityType.COMMENT_ADDED, attachment);
0512:
0513: NotificationManagerFactory.getInstance()
0514: .addActivityNotification(ActivityType.COMMENT_ADDED,
0515: activitySet, this , user);
0516:
0517: return activitySet;
0518: }
0519:
0520: /**
0521: * Adds an attachment file to this issue. Does not perform
0522: * a save because the issue may not have been created yet.
0523: * use the doSaveFileAttachment() to save the attachment
0524: * after the issue has been created.
0525: */
0526: public synchronized void addFile(Attachment attachment,
0527: ScarabUser user) throws TorqueException {
0528: attachment.setTypeId(Attachment.FILE__PK);
0529: attachment.setCreatedBy(user.getUserId());
0530: if (unSavedAttachments == null) {
0531: unSavedAttachments = new ArrayList();
0532: }
0533: unSavedAttachments.add(attachment);
0534: }
0535:
0536: /**
0537: * Overrides the super method in order to allow
0538: * us to return the unSavedAttachments if they exist.
0539: */
0540: public synchronized List getAttachments() throws TorqueException {
0541: if (unSavedAttachments != null && unSavedAttachments.size() > 0) {
0542: return unSavedAttachments;
0543: } else {
0544: return super .getAttachments();
0545: }
0546: }
0547:
0548: /**
0549: * Adds an attachment file to this issue. Does not perform
0550: * a save because the issue may not have been created yet.
0551: * use the doSaveFileAttachment() to save the attachment
0552: * after the issue has been created.
0553: */
0554: public synchronized ActivitySet doSaveFileAttachments(
0555: final ScarabUser user) throws TorqueException,
0556: ScarabException {
0557: return doSaveFileAttachments(null, user);
0558: }
0559:
0560: /**
0561: * Adds an attachment file to this issue. Does not perform
0562: * a save because the issue may not have been created yet.
0563: * use the doSaveFileAttachment() to save the attachment
0564: * after the issue has been created.
0565: */
0566: public synchronized ActivitySet doSaveFileAttachments(
0567: ActivitySet activitySet, final ScarabUser user)
0568: throws TorqueException, ScarabException {
0569: if (unSavedAttachments == null) {
0570: return activitySet;
0571: }
0572: activitySet = attachActivitySet(activitySet, user);
0573:
0574: final Iterator itr = unSavedAttachments.iterator();
0575: while (itr.hasNext()) {
0576: final Attachment attachment = (Attachment) itr.next();
0577: // make sure we set the issue to the newly created issue
0578: attachment.setIssue(this );
0579: attachment.save();
0580:
0581: // Save activity record
0582: ActivityManager.createTextActivity(this , activitySet,
0583: ActivityType.ATTACHMENT_CREATED, attachment);
0584:
0585: }
0586: // reset the super method so that the query has to hit the database again
0587: // so that all of the information is cleaned up and reset.
0588: super .collAttachments = null;
0589: // we don't need this one anymore either.
0590: this .unSavedAttachments = null;
0591: return activitySet;
0592: }
0593:
0594: /**
0595: * Remove an attachment file
0596: * @param index starts with 1 because velocityCount start from 1
0597: * but ArrayList starts from 0
0598: */
0599: public void removeFile(String index) throws TorqueException {
0600: int indexInt = Integer.parseInt(index) - 1;
0601: if (indexInt >= 0) {
0602: if (unSavedAttachments != null
0603: && unSavedAttachments.size() > 0) {
0604: unSavedAttachments.remove(indexInt);
0605: } else {
0606: List attachList = getAttachments();
0607: if (attachList != null && attachList.size() > 0) {
0608: attachList.remove(indexInt);
0609: }
0610: }
0611: }
0612: }
0613:
0614: /**
0615: * Throws UnsupportedOperationException. Use
0616: * <code>getModule()</code> instead.
0617: *
0618: * @return a <code>ScarabModule</code> value
0619: */
0620: public ScarabModule getScarabModule() {
0621: throw new UnsupportedOperationException("Should use getModule"); //EXCEPTION
0622: }
0623:
0624: /**
0625: * Throws UnsupportedOperationException. Use
0626: * <code>setModule(Module)</code> instead.
0627: *
0628: */
0629: public void setScarabModule(ScarabModule module) {
0630: throw new UnsupportedOperationException(
0631: "Should use setModule(Module). Note module cannot be new."); //EXCEPTION
0632: }
0633:
0634: /**
0635: * Use this instead of setScarabModule. Note: module cannot be new.
0636: */
0637: public void setModule(Module me) throws TorqueException {
0638: Integer id = me.getModuleId();
0639: if (id == null) {
0640: throw new TorqueException("Modules must be saved prior to "
0641: + "being associated with other objects."); //EXCEPTION
0642: }
0643: setModuleId(id);
0644: }
0645:
0646: /**
0647: * Module getter. Use this method instead of getScarabModule().
0648: *
0649: * @return a <code>Module</code> value
0650: */
0651: public Module getModule() throws TorqueException {
0652: Module module = null;
0653: Integer id = getModuleId();
0654: if (id != null) {
0655: module = ModuleManager.getInstance(id);
0656: }
0657:
0658: return module;
0659: }
0660:
0661: /**
0662: * The RModuleIssueType related to this issue's module and issue type.
0663: *
0664: * @return a <code>RModuleIssueType</code> if this issue's module and
0665: * issue type are not null, otherwise return null.
0666: */
0667: public RModuleIssueType getRModuleIssueType()
0668: throws TorqueException {
0669: RModuleIssueType rmit = null;
0670: Module module = getModule();
0671: IssueType issueType = getIssueType();
0672: if (module != null && issueType != null) {
0673: rmit = module.getRModuleIssueType(issueType);
0674: }
0675: return rmit;
0676: }
0677:
0678: /**
0679: * Calls the overloaded version by passing 'true' so that only active
0680: * attributes will be considered.
0681: * @see #getModuleAttributeValuesMap(boolean)
0682: */
0683: public LinkedMap getModuleAttributeValuesMap()
0684: throws TorqueException {
0685: return getModuleAttributeValuesMap(true);
0686: }
0687:
0688: /**
0689: * AttributeValues that are relevant to the issue's current module.
0690: * Empty AttributeValues that are relevant for the module, but have
0691: * not been set for the issue are included. The values are ordered
0692: * according to the module's preference
0693: *
0694: * @param isActive TRUE if only active attributes need to be considered
0695: * and FALSE if both active and inactive attributes need to be considered
0696: */
0697: public LinkedMap getModuleAttributeValuesMap(final boolean isActive)
0698: throws TorqueException {
0699: LinkedMap result = null;
0700: Object obj = getCachedObject(GET_MODULE_ATTRVALUES_MAP,
0701: isActive ? Boolean.TRUE : Boolean.FALSE);
0702: if (obj == null) {
0703: List attributes = null;
0704: Module module = getModule();
0705: IssueType issueType = getIssueType();
0706: if (isActive) {
0707: attributes = issueType.getActiveAttributes(module);
0708: } else {
0709: attributes = module.getAttributes(issueType);
0710: }
0711: Map siaValuesMap = getAttributeValuesMap();
0712: result = new LinkedMap((int) (1.25 * attributes.size() + 1));
0713: for (int i = 0; i < attributes.size(); i++) {
0714: String key = ((Attribute) attributes.get(i)).getName()
0715: .toUpperCase();
0716: if (siaValuesMap.containsKey(key)) {
0717: result.put(key, siaValuesMap.get(key));
0718: } else {
0719: Attribute attr = (Attribute) attributes.get(i);
0720: AttributeValue aval = AttributeValue
0721: .getNewInstance(attr, this );
0722: addAttributeValue(aval);
0723: String avalKey = aval.getAttribute().getName()
0724: .toUpperCase();
0725: siaValuesMap.put(avalKey, aval);
0726: result.put(key, aval);
0727: }
0728: }
0729: putCachedObject(result, GET_MODULE_ATTRVALUES_MAP,
0730: isActive ? Boolean.TRUE : Boolean.FALSE);
0731: } else {
0732: result = (LinkedMap) obj;
0733: }
0734: return result;
0735: }
0736:
0737: public void addAttributeValue(AttributeValue aval)
0738: throws TorqueException {
0739: List avals = getAttributeValues();
0740: if (!avals.contains(aval)) {
0741: super .addAttributeValue(aval);
0742: }
0743: }
0744:
0745: public AttributeValue getAttributeValue(String attributeName)
0746: throws TorqueException {
0747: Attribute attribute = Attribute.getInstance(attributeName);
0748: AttributeValue result;
0749: if (attribute == null) {
0750: result = null;
0751: } else {
0752: result = getAttributeValue(attribute);
0753: }
0754: return result;
0755: }
0756:
0757: public AttributeValue getAttributeValue(int id)
0758: throws TorqueException {
0759: Attribute attribute = Attribute.getInstance(id);
0760: return getAttributeValue(attribute);
0761: }
0762:
0763: public AttributeValue getAttributeValue(Attribute attribute)
0764: throws TorqueException {
0765: AttributeValue result = null;
0766: Object obj = ScarabCache.get(this , GET_ATTRVALUE, attribute);
0767: if (obj == null) {
0768: if (isNew()) {
0769: List avals = getAttributeValues();
0770: if (avals != null) {
0771: Iterator i = avals.iterator();
0772: while (i.hasNext()) {
0773: AttributeValue tempAval = (AttributeValue) i
0774: .next();
0775: if (tempAval.getAttribute().equals(attribute)) {
0776: result = tempAval;
0777: break;
0778: }
0779: }
0780: }
0781: } else {
0782: Criteria crit = new Criteria(2).add(
0783: AttributeValuePeer.ISSUE_ID, getIssueId()).add(
0784: AttributeValuePeer.DELETED, false).add(
0785: AttributeValuePeer.ATTRIBUTE_ID,
0786: attribute.getAttributeId());
0787:
0788: List avals = getAttributeValues(crit);
0789: if (avals.size() > 0) {
0790: result = (AttributeValue) avals.get(0);
0791: }
0792: if (avals.size() > 1) {
0793: getLog()
0794: .error(
0795: "getAttributeValue(): Error when retrieving attribute values of attribute. Expected 1 and found "
0796: + avals.size()
0797: + ". List follows: "
0798: + avals);
0799: }
0800: }
0801: ScarabCache.put(result, this , GET_ATTRVALUE, attribute);
0802: } else {
0803: result = (AttributeValue) obj;
0804: }
0805: return result;
0806: }
0807:
0808: /**
0809: * Returns the (undeleted) AttributeValues for the Attribute.
0810: */
0811: public List getAttributeValues(final Attribute attribute)
0812: throws TorqueException {
0813: List result = null;
0814: Object obj = ScarabCache.get(this , GET_ATTRVALUES, attribute);
0815: if (obj == null) {
0816: if (isNew()) {
0817: final List avals = getAttributeValues();
0818: result = new ArrayList();
0819: if (avals != null) {
0820: final Iterator i = avals.iterator();
0821: while (i.hasNext()) {
0822: final AttributeValue tempAval = (AttributeValue) i
0823: .next();
0824: if (tempAval.getAttribute().equals(attribute)) {
0825: result.add(tempAval);
0826: }
0827: }
0828: }
0829: } else {
0830: final Criteria crit = new Criteria(2).add(
0831: AttributeValuePeer.DELETED, false).add(
0832: AttributeValuePeer.ATTRIBUTE_ID,
0833: attribute.getAttributeId());
0834:
0835: result = getAttributeValues(crit);
0836: ScarabCache
0837: .put(result, this , GET_ATTRVALUES, attribute);
0838: }
0839: } else {
0840: result = (List) obj;
0841: }
0842: return result;
0843: }
0844:
0845: public boolean isAttributeValue(AttributeValue attVal)
0846: throws TorqueException {
0847: boolean isValue = false;
0848: List attValues = getAttributeValues(attVal.getAttribute());
0849: if (attValues.contains(attVal)) {
0850: isValue = true;
0851: }
0852: return isValue;
0853: }
0854:
0855: /**
0856: * Returns the attributevalue of the attribute with the value passed
0857: * (as String or Number). This is needed, because some attributes might
0858: * have MULTIPLE VALUES for the same issue (user attributes at least)
0859: * @param att Attribute for with to get the attributevalue
0860: * @param strVal String value to test
0861: * @param numVal Integer value to test
0862: * @return the attributevalue or null if not found
0863: * @throws TorqueException
0864: */
0865: private AttributeValue getAttributeValueWithValue(Attribute att,
0866: String strVal, Integer numVal) throws TorqueException {
0867: AttributeValue val = null;
0868: boolean bFound = false;
0869: List attValues = getAttributeValues(att);
0870: for (Iterator it = attValues.iterator(); !bFound
0871: && it.hasNext();) {
0872: val = (AttributeValue) it.next();
0873: if (strVal != null)
0874: bFound = val.getValue().equals(strVal);
0875: else if (!bFound && numVal != null)
0876: bFound = val.getNumericValue().equals(numVal);
0877: }
0878: return val;
0879: }
0880:
0881: /**
0882: * AttributeValues that are set for this Issue
0883: */
0884: public Map getAttributeValuesMap() throws TorqueException {
0885: Map result = null;
0886: Object obj = ScarabCache.get(this , GET_ATTRIBUTE_VALUES_MAP);
0887: if (obj == null) {
0888: final Criteria crit = new Criteria(2).add(
0889: AttributeValuePeer.DELETED, false);
0890: final List siaValues = getAttributeValues(crit);
0891: result = new HashMap((int) (1.25 * siaValues.size() + 1));
0892: for (Iterator i = siaValues.iterator(); i.hasNext();) {
0893: final AttributeValue att = (AttributeValue) i.next();
0894: result.put(att.getAttribute().getName().toUpperCase(),
0895: att);
0896: }
0897:
0898: ScarabCache.put(result, this , GET_ATTRIBUTE_VALUES_MAP);
0899: } else {
0900: result = (Map) obj;
0901: }
0902: return result;
0903: }
0904:
0905: /**
0906: * AttributeValues that are set for this issue and
0907: * Empty AttributeValues that are relevant for the module, but have
0908: * not been set for the issue are included.
0909: */
0910: public Map getAllAttributeValuesMap() throws TorqueException {
0911: Map moduleAtts = getModuleAttributeValuesMap();
0912: Map issueAtts = getAttributeValuesMap();
0913: Map allValuesMap = new HashMap(
0914: (int) (1.25 * (moduleAtts.size() + issueAtts.size()) + 1));
0915:
0916: allValuesMap.putAll(moduleAtts);
0917: allValuesMap.putAll(issueAtts);
0918: return allValuesMap;
0919: }
0920:
0921: /**
0922: * Describe <code>containsMinimumAttributeValues</code> method here.
0923: *
0924: * @return a <code>boolean</code> value
0925: * @exception Exception if an error occurs
0926: */
0927: public boolean containsMinimumAttributeValues()
0928: throws TorqueException {
0929: List attributes = getIssueType().getRequiredAttributes(
0930: getModule());
0931:
0932: boolean result = true;
0933: LinkedMap avMap = getModuleAttributeValuesMap();
0934: MapIterator i = avMap.mapIterator();
0935: while (i.hasNext()) {
0936: AttributeValue aval = (AttributeValue) avMap.get(i.next());
0937:
0938: if (aval.getOptionId() == null && aval.getValue() == null) {
0939: for (int j = attributes.size() - 1; j >= 0; j--) {
0940: if (aval.getAttribute().getPrimaryKey().equals(
0941: ((Attribute) attributes.get(j))
0942: .getPrimaryKey())) {
0943: result = false;
0944: break;
0945: }
0946: }
0947: if (!result) {
0948: break;
0949: }
0950: }
0951: }
0952: return result;
0953: }
0954:
0955: /**
0956: * Users who are valid values to the attribute this issue.
0957: * if a user has already
0958: * been assigned to this issue, they will not show up in this list.
0959: * use module.getEligibleUsers(Attribute) to get a complete list.
0960: *
0961: * @return a <code>List</code> value
0962: */
0963: public List getEligibleUsers(Attribute attribute)
0964: throws TorqueException, ScarabException {
0965: ScarabUser[] users = getModule().getEligibleUsers(attribute);
0966: // remove those already assigned
0967: List assigneeAVs = getAttributeValues(attribute);
0968: if (users != null && assigneeAVs != null) {
0969: for (int i = users.length - 1; i >= 0; i--) {
0970: for (int j = assigneeAVs.size() - 1; j >= 0; j--) {
0971: AttributeValue av = (AttributeValue) assigneeAVs
0972: .get(j);
0973: Integer avUserId = av.getUserId();
0974: Integer userUserId = users[i].getUserId();
0975: if (av != null && avUserId != null
0976: && userUserId != null
0977: && avUserId.equals(userUserId)) {
0978: users[i] = null;
0979: break;
0980: }
0981: }
0982: }
0983: }
0984:
0985: List eligibleUsers = new ArrayList(users.length);
0986: for (int i = 0; i < users.length; i++) {
0987: if (users[i] != null) {
0988: eligibleUsers.add(users[i]);
0989: }
0990: }
0991:
0992: return eligibleUsers;
0993: }
0994:
0995: /**
0996: * Returns the users which should be notified when this issue is
0997: * modified. The set contains those users associated with user
0998: * attributes for this issue, plus the creator of the issue.
0999: *
1000: * @param action
1001: * @param issue Usually a reference to this or a dependent issue.
1002: * @param users The list of users to append to, or
1003: * <code>null</code> to create a new list.
1004: */
1005: protected Set getUsersToEmail(String action, Issue issue, Set users)
1006: throws TorqueException {
1007: if (users == null) {
1008: users = new HashSet(1);
1009: }
1010:
1011: Module module = getModule();
1012:
1013: ScarabUser createdBy = issue.getCreatedBy();
1014: if (createdBy != null
1015: && !users.contains(createdBy)
1016: && AttributePeer.EMAIL_TO.equals(action)
1017: && createdBy.hasPermission(ScarabSecurity.ISSUE__ENTER,
1018: module)) {
1019: users.add(createdBy);
1020: }
1021:
1022: Criteria crit = new Criteria().add(AttributeValuePeer.ISSUE_ID,
1023: issue.getIssueId()).addJoin(
1024: AttributeValuePeer.ATTRIBUTE_ID,
1025: AttributePeer.ATTRIBUTE_ID).add(AttributePeer.ACTION,
1026: action).add(RModuleAttributePeer.MODULE_ID,
1027: getModuleId()).add(RModuleAttributePeer.ISSUE_TYPE_ID,
1028: getTypeId()).add(AttributeValuePeer.DELETED, 0).add(
1029: RModuleAttributePeer.ACTIVE, true).addJoin(
1030: RModuleAttributePeer.ATTRIBUTE_ID,
1031: AttributeValuePeer.ATTRIBUTE_ID);
1032: List userAttVals = AttributeValuePeer.doSelect(crit);
1033: for (Iterator i = userAttVals.iterator(); i.hasNext();) {
1034: AttributeValue attVal = (AttributeValue) i.next();
1035: try {
1036: ScarabUser su = ScarabUserManager.getInstance(attVal
1037: .getUserId());
1038: if (!users.contains(su)
1039: && su.hasPermission(attVal.getAttribute()
1040: .getPermission(), module)) {
1041: users.add(su);
1042: }
1043: } catch (Exception e) {
1044: throw new TorqueException(
1045: "Error retrieving users to email"); //EXCEPTION
1046: }
1047: }
1048: return users;
1049: }
1050:
1051: /**
1052: * Returns users assigned to user attributes that get emailed
1053: * When issue is modified. Plus creating user.
1054: * Adds users to email for dependant issues as well.
1055: *
1056: * @see #getUsersToEmail
1057: */
1058: public Set getAllUsersToEmail(String action) throws TorqueException {
1059: Set result = null;
1060: Object obj = ScarabCache.get(this , GET_ALL_USERS_TO_EMAIL,
1061: action);
1062: if (obj == null) {
1063: Set users = new HashSet();
1064: try {
1065: users = getUsersToEmail(action, this , users);
1066: List children = getChildren();
1067: for (int i = 0; i < children.size(); i++) {
1068: Issue depIssue = IssueManager
1069: .getInstance(((Depend) children.get(i))
1070: .getObserverId());
1071: users = getUsersToEmail(action, depIssue, users);
1072: }
1073: result = users;
1074: } catch (Exception e) {
1075: getLog().error("Issue.getUsersToEmail(): ", e);
1076: throw new TorqueException("Error in retrieving users."); //EXCEPTION
1077: }
1078: ScarabCache.put(result, this , GET_ALL_USERS_TO_EMAIL,
1079: action);
1080: } else {
1081: result = (Set) obj;
1082: }
1083: return result;
1084: }
1085:
1086: /**
1087: * Returns the specific user's attribute value.
1088: */
1089: public AttributeValue getUserAttributeValue(final ScarabUser user,
1090: final Attribute attribute) throws TorqueException {
1091: AttributeValue result = null;
1092: Object obj = getCachedObject(GET_USER_ATTRIBUTEVALUE, attribute
1093: .getAttributeId(), user.getUserId());
1094: if (obj == null) {
1095: final Criteria crit = new Criteria().add(
1096: AttributeValuePeer.ATTRIBUTE_ID,
1097: attribute.getAttributeId()).add(
1098: AttributeValuePeer.ISSUE_ID, getIssueId()).add(
1099: AttributeValuePeer.USER_ID, user.getUserId()).add(
1100: AttributeValuePeer.DELETED, 0);
1101: final List resultList = AttributeValuePeer.doSelect(crit);
1102: if (resultList != null && resultList.size() == 1) {
1103: result = (AttributeValue) resultList.get(0);
1104: }
1105: putCachedObject(result, GET_USER_ATTRIBUTEVALUE, attribute
1106: .getAttributeId(), user.getUserId());
1107: } else {
1108: result = (AttributeValue) obj;
1109: }
1110: return result;
1111: }
1112:
1113: /**
1114: * Returns attribute values for user attributes.
1115: */
1116: public List getUserAttributeValues() throws TorqueException {
1117: List result = null;
1118: Object obj = getCachedObject(GET_USER_ATTRIBUTEVALUES);
1119: if (obj == null) {
1120: List attributeList = getModule().getUserAttributes(
1121: getIssueType(), true);
1122: List attributeIdList = new ArrayList();
1123:
1124: for (int i = 0; i < attributeList.size(); i++) {
1125: Attribute att = (Attribute) attributeList.get(i);
1126: attributeIdList.add(att.getAttributeId());
1127: }
1128:
1129: if (!attributeIdList.isEmpty()) {
1130: Criteria crit = new Criteria().addIn(
1131: AttributeValuePeer.ATTRIBUTE_ID,
1132: attributeIdList).add(
1133: AttributeValuePeer.ISSUE_ID, getIssueId()).add(
1134: AttributeValuePeer.DELETED, 0);
1135: result = AttributeValuePeer.doSelect(crit);
1136: } else {
1137: result = new ArrayList(0);
1138: }
1139: putCachedObject(result, GET_USER_ATTRIBUTEVALUES);
1140: } else {
1141: result = (List) obj;
1142: }
1143: return result;
1144: }
1145:
1146: /**
1147: * The initial activity set from issue creation.
1148: *
1149: * @return a <code>ActivitySet</code> value
1150: * @exception Exception if an error occurs
1151: */
1152: public ActivitySet getInitialActivitySet() throws TorqueException {
1153: ActivitySet activitySet = getActivitySetRelatedByCreatedTransId();
1154: if (activitySet == null) {
1155: Log.get().warn("Creation ActivitySet is null for " + this );
1156: }
1157:
1158: return activitySet;
1159: }
1160:
1161: /**
1162: * The date the issue was created.
1163: *
1164: * @return a <code>Date</code> value
1165: * @exception TorqueException if an error occurs
1166: */
1167: public Date getCreatedDate() throws TorqueException {
1168: ActivitySet creationSet = getActivitySetRelatedByCreatedTransId();
1169: Date result = null;
1170: if (creationSet == null) {
1171: getLog().warn(
1172: "Issue " + getUniqueId() + " (pk=" + getIssueId()
1173: + ") does not have a creation ActivitySet");
1174: } else {
1175: result = creationSet.getCreatedDate();
1176: }
1177: return result;
1178: }
1179:
1180: /**
1181: * The user that created the issue.
1182: * @return a <code>ScarabUser</code> value
1183: */
1184: public ScarabUser getCreatedBy() throws TorqueException {
1185: ActivitySet creationSet = getActivitySetRelatedByCreatedTransId();
1186: ScarabUser result = null;
1187: if (creationSet == null) {
1188: getLog().warn(
1189: "Issue " + getUniqueId() + " (pk=" + getIssueId()
1190: + ") does not have a creation ActivitySet");
1191: } else {
1192: result = creationSet.getScarabUser();
1193: }
1194: return result;
1195: }
1196:
1197: public boolean isCreatingUser(ScarabUser user)
1198: throws TorqueException {
1199: ActivitySet creationSet = getActivitySetRelatedByCreatedTransId();
1200: boolean result = false;
1201: if (creationSet == null) {
1202: getLog().warn(
1203: "Issue " + getUniqueId() + " (pk=" + getIssueId()
1204: + ") does not have a creation ActivitySet");
1205: } else {
1206: result = creationSet.getCreatedBy()
1207: .equals(user.getUserId());
1208: }
1209: return result;
1210: }
1211:
1212: /**
1213: * The last modification made to the issue.
1214: *
1215: * @return a <code>ScarabUser</code> value
1216: */
1217: public ActivitySet getLastActivitySet() throws TorqueException {
1218: ActivitySet t = null;
1219: if (!isNew()) {
1220: Object obj = ScarabCache.get(this , GET_LAST_TRANSACTION);
1221: if (obj == null) {
1222: Criteria crit = new Criteria();
1223: crit.addJoin(ActivitySetPeer.TRANSACTION_ID,
1224: ActivityPeer.TRANSACTION_ID);
1225: crit.add(ActivityPeer.ISSUE_ID, getIssueId());
1226: Integer[] typeIds = {
1227: ActivitySetTypePeer.EDIT_ISSUE__PK,
1228: ActivitySetTypePeer.MOVE_ISSUE__PK };
1229: crit.addIn(ActivitySetPeer.TYPE_ID, typeIds);
1230: // there could be multiple attributes modified during the
1231: // creation which will lead to duplicates
1232: crit.setDistinct();
1233: crit
1234: .addDescendingOrderByColumn(ActivitySetPeer.CREATED_DATE);
1235: List activitySets = ActivitySetPeer.doSelect(crit);
1236: if (activitySets.size() > 0) {
1237: t = (ActivitySet) activitySets.get(0);
1238: }
1239: ScarabCache.put(t, this , GET_LAST_TRANSACTION);
1240: } else {
1241: t = (ActivitySet) obj;
1242: }
1243: }
1244: return t;
1245: }
1246:
1247: /**
1248: * The date issue was last modified.
1249: *
1250: * @return a <code>ScarabUser</code> value
1251: */
1252: public Date getModifiedDate() throws TorqueException {
1253: Date result = null;
1254: if (!isNew()) {
1255: ActivitySet t = getLastActivitySet();
1256: if (t == null) {
1257: result = getCreatedDate();
1258: } else {
1259: result = t.getCreatedDate();
1260: }
1261: }
1262: return result;
1263: }
1264:
1265: /**
1266: * The last user to modify the issue.
1267: *
1268: * @return a <code>ScarabUser</code> value
1269: */
1270: public ScarabUser getModifiedBy() throws TorqueException {
1271: ScarabUser result = null;
1272: if (!isNew()) {
1273: ActivitySet t = getLastActivitySet();
1274: if (t == null) {
1275: result = getCreatedBy();
1276: } else {
1277: result = ScarabUserManager
1278: .getInstance(t.getCreatedBy());
1279: }
1280: }
1281: return result;
1282: }
1283:
1284: /**
1285: * Returns the total number of comments.
1286: */
1287: public int getCommentsCount() throws TorqueException {
1288: return getComments(true).size();
1289: }
1290:
1291: /**
1292: * Determines whether the comments list is longer than
1293: * The default limit.
1294: */
1295: public boolean isCommentsLong() throws TorqueException {
1296: return (getCommentsCount() > getCommentsLimit());
1297: }
1298:
1299: /**
1300: * Gets default comments limit for this module-issue type.
1301: */
1302: public int getCommentsLimit() throws TorqueException {
1303: int limit = 0;
1304: try {
1305: limit = getModule().getRModuleIssueType(getIssueType())
1306: .getComments();
1307: } catch (Exception e) {
1308: // ignored (return 0 by default)
1309: }
1310: return limit;
1311: }
1312:
1313: /**
1314: * Returns a list of Attachment objects with type "Comment"
1315: * That are associated with this issue.
1316: */
1317: public List getComments(boolean full) throws TorqueException {
1318: List result = null;
1319: Boolean fullBool = (full ? Boolean.TRUE : Boolean.FALSE);
1320: Object obj = getCachedObject(GET_COMMENTS, fullBool);
1321: if (obj == null) {
1322: Criteria crit = new Criteria().add(AttachmentPeer.ISSUE_ID,
1323: getIssueId()).addJoin(
1324: AttachmentTypePeer.ATTACHMENT_TYPE_ID,
1325: AttachmentPeer.ATTACHMENT_TYPE_ID).add(
1326: AttachmentTypePeer.ATTACHMENT_TYPE_ID,
1327: Attachment.COMMENT__PK).addDescendingOrderByColumn(
1328: AttachmentPeer.CREATED_DATE);
1329: if (!full) {
1330: crit.setLimit(getCommentsLimit());
1331: }
1332: result = AttachmentPeer.doSelect(crit);
1333: putCachedObject(result, GET_COMMENTS, fullBool);
1334: } else {
1335: result = (List) obj;
1336: }
1337: return result;
1338: }
1339:
1340: /**
1341: * Returns a list of Attachment objects with type "URL"
1342: * That are associated with this issue.
1343: */
1344: public List getUrls() throws TorqueException {
1345: List result = null;
1346: Object obj = getCachedObject(GET_URLS);
1347: if (obj == null) {
1348: Criteria crit = new Criteria().add(AttachmentPeer.ISSUE_ID,
1349: getIssueId()).addJoin(
1350: AttachmentTypePeer.ATTACHMENT_TYPE_ID,
1351: AttachmentPeer.ATTACHMENT_TYPE_ID).add(
1352: AttachmentTypePeer.ATTACHMENT_TYPE_ID,
1353: Attachment.URL__PK).add(AttachmentPeer.DELETED, 0);
1354: result = AttachmentPeer.doSelect(crit);
1355: putCachedObject(result, GET_URLS);
1356: } else {
1357: result = (List) obj;
1358: }
1359: return result;
1360: }
1361:
1362: /**
1363: * Get attachments that are not deleted
1364: */
1365: public List getExistingAttachments() throws TorqueException {
1366: List result = null;
1367: Object obj = getCachedObject(GET_EXISTING_ATTACHMENTS);
1368: if (obj == null) {
1369: Criteria crit = new Criteria().add(AttachmentPeer.ISSUE_ID,
1370: getIssueId()).addJoin(
1371: AttachmentTypePeer.ATTACHMENT_TYPE_ID,
1372: AttachmentPeer.ATTACHMENT_TYPE_ID).add(
1373: AttachmentTypePeer.ATTACHMENT_TYPE_ID,
1374: Attachment.FILE__PK).add(AttachmentPeer.DELETED, 0);
1375: result = AttachmentPeer.doSelect(crit);
1376: putCachedObject(result, GET_EXISTING_ATTACHMENTS);
1377: } else {
1378: result = (List) obj;
1379: }
1380: return result;
1381: }
1382:
1383: public List getActivitiesWithNullEndDate(Attribute attribute)
1384: throws TorqueException {
1385: List result = null;
1386: Object obj = ScarabCache
1387: .get(this , GET_NULL_END_DATE, attribute);
1388: if (obj == null) {
1389: Criteria crit = new Criteria();
1390: crit.add(ActivityPeer.ISSUE_ID, this .getIssueId());
1391: crit.add(ActivityPeer.ATTRIBUTE_ID, attribute
1392: .getAttributeId());
1393: crit.add(ActivityPeer.END_DATE, null);
1394: result = ActivityPeer.doSelect(crit);
1395: ScarabCache.put(result, this , GET_NULL_END_DATE, attribute);
1396: } else {
1397: result = (List) obj;
1398: }
1399: return result;
1400: }
1401:
1402: /**
1403: * Gets default history limit for this module-issue type.
1404: * The default is 5.
1405: */
1406: public int getHistoryLimit() throws TorqueException {
1407: RModuleIssueType rmit = getModule().getRModuleIssueType(
1408: getIssueType());
1409: if (rmit != null) {
1410: return rmit.getHistory();
1411: } else {
1412: return 5;
1413: }
1414: }
1415:
1416: /**
1417: * Determines whether the history list is longer than
1418: * The default limit.
1419: */
1420: public boolean isHistoryLong() throws TorqueException {
1421: return isHistoryLong(getHistoryLimit());
1422: }
1423:
1424: /**
1425: * Determines whether the history list is longer than
1426: * The limit.
1427: */
1428: public boolean isHistoryLong(int limit) throws TorqueException {
1429: return (getActivity(true).size() > limit);
1430: }
1431:
1432: /**
1433: * Returns list of Activity objects associated with this Issue.
1434: */
1435: public List getActivity() throws TorqueException {
1436: return getActivity(false, getHistoryLimit());
1437: }
1438:
1439: /**
1440: * Returns limited list of Activity objects associated with this Issue.
1441: */
1442: public List getActivity(int limit) throws TorqueException {
1443: return getActivity(false, limit);
1444: }
1445:
1446: /**
1447: * Returns limited list of Activity objects associated with this Issue.
1448: * If fullHistory is false, it limits it,
1449: * (this is the default)
1450: */
1451: public List getActivity(boolean fullHistory) throws TorqueException {
1452: return getActivity(fullHistory, getHistoryLimit());
1453: }
1454:
1455: /**
1456: * Returns full list of Activity objects associated with this Issue.
1457: */
1458: private List getActivity(boolean fullHistory, int limit)
1459: throws TorqueException {
1460: List result = null;
1461: Boolean fullHistoryObj = fullHistory ? Boolean.TRUE
1462: : Boolean.FALSE;
1463: Object obj = getCachedObject(GET_ACTIVITY, fullHistoryObj,
1464: new Integer(limit));
1465: if (obj == null) {
1466: Criteria crit = new Criteria().add(ActivityPeer.ISSUE_ID,
1467: getIssueId()).addAscendingOrderByColumn(
1468: ActivityPeer.TRANSACTION_ID);
1469: if (!fullHistory) {
1470: crit.setLimit(limit);
1471: }
1472: result = ActivityPeer.doSelect(crit);
1473: putCachedObject(result, GET_ACTIVITY, fullHistoryObj,
1474: new Integer(limit));
1475: } else {
1476: result = (List) obj;
1477: }
1478: return result;
1479: }
1480:
1481: /**
1482: * Returns limited list of Activity objects associated with this Issue.
1483: */
1484: public void addActivity(Activity activity) throws TorqueException {
1485: List activityList = null;
1486: try {
1487: activityList = getActivity(true);
1488: } catch (Exception e) {
1489: throw new TorqueException(e); //EXCEPTION
1490: }
1491: super .addActivity(activity);
1492: if (!activityList.contains(activity)) {
1493: activityList.add(activity);
1494: }
1495: }
1496:
1497: /**
1498: * Returns a list of ActivitySet objects associated to this issue.
1499: */
1500: public List getActivitySets() throws TorqueException {
1501: List result = null;
1502: Object obj = ScarabCache.get(this , GET_TRANSACTIONS);
1503: if (obj == null) {
1504: Criteria crit = new Criteria();
1505: crit.add(ActivityPeer.ISSUE_ID, getIssueId());
1506: crit.addJoin(ActivitySetPeer.TRANSACTION_ID,
1507: ActivityPeer.TRANSACTION_ID);
1508: crit.setDistinct();
1509: result = ActivitySetPeer.doSelect(crit);
1510: ScarabCache.put(result, this , GET_TRANSACTIONS);
1511: } else {
1512: result = (List) obj;
1513: }
1514: return result;
1515: }
1516:
1517: /**
1518: * Creates a new ActivitySet object for the issue.
1519: */
1520: public ActivitySet getActivitySet(final ScarabUser user,
1521: final Attachment attachment, final Integer type)
1522: throws TorqueException, ScarabException {
1523: ActivitySet activitySet = null;
1524: if (attachment == null) {
1525: activitySet = ActivitySetManager.getInstance(type, user);
1526: } else {
1527: activitySet = ActivitySetManager.getInstance(type, user,
1528: attachment);
1529: }
1530: return activitySet;
1531: }
1532:
1533: /**
1534: * Creates a new ActivitySet object for the issue.
1535: */
1536: public ActivitySet getActivitySet(ScarabUser user, Integer type)
1537: throws TorqueException, ScarabException {
1538: return getActivitySet(user, null, type);
1539: }
1540:
1541: /**
1542: * Returns the combined output from getChildren() and getParents()
1543: */
1544: public List getAllDependencies() throws TorqueException {
1545: List dependencies = new ArrayList();
1546: dependencies.addAll(getChildren());
1547: dependencies.addAll(getParents());
1548: return dependencies;
1549: }
1550:
1551: /**
1552: * Returns list of child dependencies
1553: * i.e., related to this issue through the DEPEND table.
1554: */
1555: public List getChildren() throws TorqueException {
1556: return getChildren(true);
1557: }
1558:
1559: /**
1560: * Returns list of child dependencies
1561: * i.e., related to this issue through the DEPEND table.
1562: */
1563: public List getChildren(boolean hideDeleted) throws TorqueException {
1564: List result = null;
1565: Boolean hide = hideDeleted ? Boolean.TRUE : Boolean.FALSE;
1566: Object obj = getCachedObject(GET_CHILDREN, hide);
1567: if (obj == null) {
1568: Criteria crit = new Criteria().add(DependPeer.OBSERVED_ID,
1569: getIssueId());
1570: if (hideDeleted) {
1571: crit.add(DependPeer.DELETED, false);
1572: }
1573: result = DependPeer.doSelect(crit);
1574: putCachedObject(result, GET_CHILDREN, hide);
1575: } else {
1576: result = (List) obj;
1577: }
1578: return result;
1579: }
1580:
1581: /**
1582: * Returns list of parent dependencies
1583: * i.e., related to this issue through the DEPEND table.
1584: */
1585: public List getParents() throws TorqueException {
1586: return getParents(true);
1587: }
1588:
1589: /**
1590: * Returns list of parent dependencies
1591: * i.e., related to this issue through the DEPEND table.
1592: */
1593: public List getParents(boolean hideDeleted) throws TorqueException {
1594: List result = null;
1595: Boolean hide = hideDeleted ? Boolean.TRUE : Boolean.FALSE;
1596: Object obj = getCachedObject(GET_PARENTS, hide);
1597: if (obj == null) {
1598: Criteria crit = new Criteria().add(DependPeer.OBSERVER_ID,
1599: getIssueId());
1600: if (hideDeleted) {
1601: crit.add(DependPeer.DELETED, false);
1602: }
1603: result = DependPeer.doSelect(crit);
1604: putCachedObject(result, GET_PARENTS, hide);
1605: } else {
1606: result = (List) obj;
1607: }
1608: return result;
1609: }
1610:
1611: /**
1612: * Returns list of all types of dependencies an issue can have
1613: * On another issue.
1614: * @deprecated use DependencyTypeManager.getAll();
1615: */
1616: public List getAllDependencyTypes() throws TorqueException {
1617: return DependTypeManager.getAll();
1618: }
1619:
1620: public ActivitySet doAddDependency(ActivitySet activitySet,
1621: Depend depend, Issue childIssue, ScarabUser user)
1622: throws TorqueException, ScarabException {
1623: // Check whether the entered issue is already dependent on this
1624: // Issue. If so, then throw an exception because we don't want
1625: // to add it again.
1626: Depend prevDepend = this .getDependency(childIssue, true);
1627: if (prevDepend != null) {
1628: throw new ScarabException(L10NKeySet.DependencyExists);
1629: }
1630:
1631: // we definitely want to do an insert here so force it.
1632: depend.setNew(true);
1633: depend.setDeleted(false);
1634: depend.save();
1635:
1636: Attachment comment = depend.getDescriptionAsAttachment(user,
1637: this );
1638: activitySet = attachActivitySet(activitySet, user, comment);
1639:
1640: // Save activity record for the parent issue
1641: ActivityManager.createAddDependencyActivity(this , activitySet,
1642: depend);
1643:
1644: // Save activity record for the child issue
1645: ActivityManager.createAddDependencyActivity(childIssue,
1646: activitySet, depend);
1647:
1648: return activitySet;
1649: }
1650:
1651: /**
1652: * Checks to see if this issue has a dependency on the passed in issue.
1653: * or if the passed in issue has a dependency on this issue.
1654: */
1655: public Depend getDependency(Issue potentialDependency)
1656: throws TorqueException {
1657: return getDependency(potentialDependency, true);
1658: }
1659:
1660: /**
1661: * Checks to see if this issue has a dependency on the passed in issue.
1662: * or if the passed in issue has a dependency on this issue.
1663: *
1664: * @param potentialDependency the issue for which we are determining if there is a
1665: * parent or child dependency to this issue
1666: * @param hideDeleted true if deleted issues are omitted from the search
1667: * @returns the dependency object or null
1668: */
1669: public Depend getDependency(Issue potentialDependency,
1670: boolean hideDeleted) throws TorqueException {
1671: Depend result = null;
1672: Object obj = ScarabCache.get(this , GET_DEPENDENCY,
1673: potentialDependency);
1674: if (obj == null) {
1675:
1676: // Determine if this issue is a parent to the potentialDependency
1677: Criteria crit = new Criteria(2).add(DependPeer.OBSERVED_ID,
1678: getIssueId()).add(DependPeer.OBSERVER_ID,
1679: potentialDependency.getIssueId());
1680: if (hideDeleted) {
1681: crit.add(DependPeer.DELETED, false);
1682: }
1683:
1684: List childIssues = DependPeer.doSelect(crit);
1685: // A system invariant is that we will get one and only one
1686: // record back.
1687: if (!childIssues.isEmpty()) {
1688: result = (Depend) childIssues.get(0);
1689: } else {
1690: // Determine if this issue is a child to the potentialDependency
1691: Criteria crit2 = new Criteria(2).add(
1692: DependPeer.OBSERVER_ID, getIssueId()).add(
1693: DependPeer.OBSERVED_ID,
1694: potentialDependency.getIssueId());
1695: if (hideDeleted) {
1696: crit2.add(DependPeer.DELETED, false);
1697: }
1698: List parentIssues = DependPeer.doSelect(crit2);
1699: if (!parentIssues.isEmpty()) {
1700: result = (Depend) parentIssues.get(0);
1701: }
1702: }
1703:
1704: if (result != null) {
1705: ScarabCache.put(result, this , GET_DEPENDENCY,
1706: potentialDependency);
1707: }
1708: } else {
1709: result = (Depend) obj;
1710: }
1711: return result;
1712: }
1713:
1714: /**
1715: * Removes any unset attributes and sets the issue # prior to saving
1716: * for the first time. Calls super.save()
1717: *
1718: * If the issue does not have an <i>idCount</i> then the next
1719: * available ID is allocated.
1720: *
1721: * If the issue has a non-zero <i>idCount</i>, this value is honoured.
1722: * WARNING: do not set the idCount to an existing ID!
1723: * The nominated value is ignored if it is not at least as high as the
1724: * next available ID.
1725: *
1726: *
1727: * @param dbCon a <code>DBConnection</code> value
1728: * @exception TorqueException if an error occurs
1729: */
1730: public void save(Connection dbCon) throws TorqueException {
1731: Module module = getModule();
1732: if (!module.allowsIssues()
1733: || (isNew() && !module.allowsNewIssues())) {
1734: throw new UnsupportedOperationException(module.getName()
1735: + " does not allow issues."); //EXCEPTION
1736: }
1737:
1738: // remove unset AttributeValues before saving
1739: List attValues = getAttributeValues();
1740: // reverse order since removing from list
1741: for (int i = attValues.size() - 1; i >= 0; i--) {
1742: AttributeValue attVal = (AttributeValue) attValues.get(i);
1743: if (!attVal.isSet()) {
1744: attValues.remove(i);
1745: }
1746: }
1747:
1748: if (isNew()) {
1749: // set the issue id
1750: setIdDomain(module.getScarabInstanceId());
1751: setIdPrefix(module.getCode());
1752:
1753: // for an enter issue template, do not give issue id
1754: // set id count to -1 so does not show up as an issue
1755: if (isTemplate()) {
1756: setIdCount(-1);
1757: } else {
1758: try {
1759: final int suggestedID = getIdCount();
1760: if (suggestedID != 0) {
1761: // Force the next available issue ID to be the
1762: // nominated value, if not out of sequence.
1763: // TODO: assert that this issue doesn't already exist
1764: // In this case, just skip the next action.
1765: setNextIssueId(dbCon, suggestedID);
1766: }
1767:
1768: // Set the ID to the next available value.
1769: setIdCount(getNextIssueId(dbCon));
1770: } catch (Exception e) {
1771: throw new TorqueException(e); //EXCEPTION
1772: }
1773: }
1774: }
1775: super .save(dbCon);
1776: }
1777:
1778: /* Gets the next available issue ID.
1779: * If the ID table doesn't yet exist, it is created.
1780: */
1781: private int getNextIssueId(Connection con) throws TorqueException,
1782: ScarabException {
1783: int id = -1;
1784: String key = getIdTableKey();
1785: DatabaseMap dbMap = IssuePeer.getTableMap().getDatabaseMap();
1786: IDBroker idbroker = dbMap.getIDBroker();
1787: try {
1788: id = idbroker.getIdAsInt(con, key);
1789: } catch (Exception e) {
1790: synchronized (idbroker) {
1791: try {
1792: id = idbroker.getIdAsInt(con, key);
1793: } catch (Exception idRetrievalErr) {
1794: // a module code entry in the id_table was likely not
1795: // entered, insert a row into the id_table and try again.
1796: try {
1797: saveIdTableKey(con, 2);
1798: id = 1;
1799: } catch (Exception badException) {
1800: getLog()
1801: .error(
1802: "Could not get an id, even after "
1803: + "trying to add a module entry into the ID_TABLE",
1804: e);
1805: getLog()
1806: .error(
1807: "Error trying to create ID_TABLE entry for "
1808: + getIdTableKey(),
1809: badException);
1810: // throw the original
1811: throw new ScarabException(
1812: L10NKeySet.ExceptionRetrievingIssueId,
1813: badException);
1814: }
1815: }
1816: }
1817: }
1818: return id;
1819: }
1820:
1821: /*
1822: * Sets the next available issue ID to the given value
1823: * If the ID table doesn't yet exist, it is created.
1824: */
1825: private void setNextIssueId(Connection con, int newID)
1826: throws TorqueException, ScarabException {
1827: String key = getIdTableKey();
1828: DatabaseMap dbMap = IssuePeer.getTableMap().getDatabaseMap();
1829: IDBroker idbroker = dbMap.getIDBroker();
1830: int nextID = 1;
1831:
1832: synchronized (idbroker) {
1833: try {
1834: // Check if the ID table is available and get the next ID
1835: nextID = idbroker.getIdAsInt(con, key);
1836: } catch (Exception idRetrievalErr) {
1837: // No, create the ID table now
1838: saveIdTableKey(con, nextID);
1839: }
1840:
1841: if (nextID > newID) {
1842: getLog()
1843: .error(
1844: "New issue ID "
1845: + newID
1846: + "is out of sequence. Must be at least "
1847: + nextID);
1848: } else {
1849: try {
1850: // Now set the next available ID in the table
1851: setIdTableKey(con, newID);
1852: } catch (Exception badException) {
1853: getLog().error(
1854: "Error creating ID_TABLE entry for "
1855: + getIdTableKey(), badException);
1856: // throw the original
1857: throw new ScarabException(
1858: L10NKeySet.ExceptionRetrievingIssueId,
1859: badException);
1860: }
1861: }
1862: }
1863: }
1864:
1865: private String getIdTableKey() throws TorqueException {
1866: Module module = getModule();
1867: String prefix = module.getCode();
1868:
1869: String domain = module.getScarabInstanceId();
1870: if (domain != null && domain.length() > 0) {
1871: prefix = domain + "-" + prefix;
1872: }
1873: return prefix;
1874: }
1875:
1876: private void saveIdTableKey(final Connection dbCon, final int nextID)
1877: throws TorqueException {
1878: int id = 0;
1879: final DatabaseMap dbMap = IssuePeer.getTableMap()
1880: .getDatabaseMap();
1881: final IDBroker idbroker = dbMap.getIDBroker();
1882: final String idTable = IDBroker.TABLE_NAME.substring(0,
1883: IDBroker.TABLE_NAME.indexOf('.'));
1884: try {
1885: id = idbroker.getIdAsInt(dbCon, idTable);
1886: } catch (Exception e) {
1887: Log.get(getClass().getName()).error(e);
1888: throw new TorqueException(e);
1889: }
1890:
1891: final String key = getIdTableKey();
1892:
1893: // FIXME: UGLY! IDBroker doesn't have a Peer yet.
1894: final String sql = "insert into " + idTable
1895: + " (ID_TABLE_ID,TABLE_NAME,NEXT_ID,QUANTITY) "
1896: + " VALUES (" + id + ",'" + key + "'," + nextID + ",1)";
1897: BasePeer.executeStatement(sql, dbCon);
1898: }
1899:
1900: /*
1901: * Sets the next available ID to the given ID
1902: */
1903: private void setIdTableKey(final Connection dbCon, int id)
1904: throws TorqueException {
1905: final String key = getIdTableKey();
1906:
1907: // FIXME: UGLY! IDBroker doesn't have a Peer yet.
1908: final String sql = "update ID_TABLE set NEXT_ID=" + id
1909: + " where TABLE_NAME='" + key + "'";
1910: BasePeer.executeStatement(sql, dbCon);
1911: }
1912:
1913: /**
1914: * Returns list of issue template types.
1915: public List getTemplateTypes() throws TorqueException
1916: {
1917: List result = null;
1918: Object obj = ScarabCache.get(this, GET_TEMPLATE_TYPES);
1919: if (obj == null)
1920: {
1921: Criteria crit = new Criteria()
1922: .add(IssueTypePeer.ISSUE_TYPE_ID,
1923: IssueType.ISSUE__PK, Criteria.NOT_EQUAL);
1924: result = IssueTypePeer.doSelect(crit);
1925: ScarabCache.put(result, this, GET_TEMPLATE_TYPES);
1926: }
1927: else
1928: {
1929: result = (List)obj;
1930: }
1931: return result;
1932: }
1933: */
1934:
1935: /**
1936: * Get IssueTemplateInfo by Issue Id.
1937: */
1938: public IssueTemplateInfo getTemplateInfo() throws TorqueException {
1939: IssueTemplateInfo result = null;
1940: Object obj = ScarabCache.get(this , GET_TEMPLATEINFO);
1941: if (obj == null) {
1942: Criteria crit = new Criteria(1);
1943: crit.add(IssueTemplateInfoPeer.ISSUE_ID, getIssueId());
1944: result = (IssueTemplateInfo) IssueTemplateInfoPeer
1945: .doSelect(crit).get(0);
1946: ScarabCache.put(result, this , GET_TEMPLATEINFO);
1947: } else {
1948: result = (IssueTemplateInfo) obj;
1949: }
1950: return result;
1951: }
1952:
1953: /**
1954: * Get Unset required attributes in destination module / issue type.
1955: */
1956: public List getUnsetRequiredAttrs(Module newModule,
1957: IssueType newIssueType) throws TorqueException {
1958: List attrs = new ArrayList();
1959: if (!getIssueType().getIssueTypeId().equals(
1960: newIssueType.getIssueTypeId())
1961: || !getModule().getModuleId().equals(
1962: newModule.getModuleId())) {
1963: List requiredAttributes = newIssueType
1964: .getRequiredAttributes(newModule);
1965: Map attrValues = getAttributeValuesMap();
1966:
1967: for (Iterator i = requiredAttributes.iterator(); i
1968: .hasNext();) {
1969: Attribute attr = (Attribute) i.next();
1970: if (!attrValues.containsKey(attr.getName()
1971: .toUpperCase())) {
1972: attrs.add(attr);
1973: }
1974: }
1975: }
1976: return attrs;
1977: }
1978:
1979: /**
1980: * Checks if the 'nonmatching' list contains the given 'value', treating the
1981: * UserAttributes as an special case, in which the UserName is used to make
1982: * the comparison.
1983: * @param nonmatching
1984: * @param value
1985: * @return
1986: */
1987: private boolean isNonMatchingAttribute(List nonmatching,
1988: AttributeValue value) {
1989: boolean bRdo = false;
1990: if (value instanceof UserAttribute) {
1991: for (Iterator it = nonmatching.iterator(); !bRdo
1992: && it.hasNext();) {
1993: Object attr = it.next();
1994: if (attr instanceof UserAttribute) {
1995: UserAttribute userAttr = (UserAttribute) attr;
1996: bRdo = userAttr.getUserName().equals(
1997: ((UserAttribute) value).getUserName());
1998: } else {
1999: Log
2000: .get()
2001: .warn(
2002: "in Issue.isNonMatchingAttribute: encountered an attribute of type ["
2003: + attr.getClass().getName()
2004: + "] in the list of nonmatching objects");
2005: Log.get().warn(
2006: "This is not a UserAttribute.(ignored)");
2007: }
2008: }
2009: } else {
2010: bRdo = nonmatching.contains(value);
2011: }
2012: return bRdo;
2013: }
2014:
2015: /**
2016: * Move or copy issue to destination module.
2017: */
2018: public Issue move(final Module newModule,
2019: final IssueType newIssueType, final String action,
2020: final ScarabUser user, final String reason,
2021: final List commentAttrs, final List commentUserValues)
2022: throws TorqueException, ScarabException {
2023: Issue newIssue;
2024:
2025: final Attachment attachment = new Attachment();
2026:
2027: // If moving to a new issue type, just change the issue type id
2028: // otherwise, create fresh issue
2029: if (getModule().getModuleId().equals(newModule.getModuleId())
2030: && !getIssueType().getIssueTypeId().equals(
2031: newIssueType.getIssueTypeId())
2032: && action.equals("move")) {
2033: newIssue = this ;
2034: newIssue.setIssueType(newIssueType);
2035: } else {
2036: newIssue = newModule.getNewIssue(newIssueType);
2037: }
2038: newIssue.save();
2039:
2040: if (newIssue != this ) {
2041: // If moving issue to new module, delete original
2042: if (action.equals("move")) {
2043: setMoved(true);
2044: save();
2045: }
2046:
2047: ActivitySet createActivitySet = ActivitySetManager
2048: .getInstance(ActivitySetTypePeer.CREATE_ISSUE__PK,
2049: getCreatedBy());
2050: createActivitySet.setCreatedDate(getCreatedDate());
2051: createActivitySet.save();
2052: newIssue.setCreatedTransId(createActivitySet
2053: .getActivitySetId());
2054: newIssue.save();
2055:
2056: // Adjust dependencies if its a new issue id
2057: // (i.e.. moved to new module)
2058: final List children = getChildren();
2059: for (Iterator i = children.iterator(); i.hasNext();) {
2060: Depend depend = (Depend) i.next();
2061: if (action.equals("move")) {
2062: doDeleteDependency(null, depend, user);
2063: }
2064: final Issue child = IssueManager.getInstance(depend
2065: .getObserverId());
2066: final Depend newDepend = new Depend();
2067: newDepend.setObserverId(child.getIssueId());
2068: newDepend.setObservedId(newIssue.getIssueId());
2069: newDepend.setTypeId(depend.getTypeId());
2070: newIssue.doAddDependency(null, newDepend, child, user);
2071: }
2072: final List parents = getParents();
2073: for (Iterator j = parents.iterator(); j.hasNext();) {
2074: final Depend depend = (Depend) j.next();
2075: if (action.equals("move")) {
2076: doDeleteDependency(null, depend, user);
2077: }
2078: final Issue parent = IssueManager.getInstance(depend
2079: .getObservedId());
2080: final Depend newDepend = new Depend();
2081: newDepend.setObserverId(newIssue.getIssueId());
2082: newDepend.setObservedId(parent.getIssueId());
2083: newDepend.setTypeId(depend.getTypeId());
2084: parent.doAddDependency(null, newDepend, newIssue, user);
2085: }
2086:
2087: // copy attachments: comments/files etc.
2088: final Iterator attachments = getAttachments().iterator();
2089: while (attachments.hasNext()) {
2090: final Attachment oldA = (Attachment) attachments.next();
2091: final Attachment newA = oldA.copy();
2092: newA.setIssueId(newIssue.getIssueId());
2093: newA.save();
2094: final Activity oldAct = oldA.getActivity();
2095: if (oldAct != null) {
2096: final ActivitySet activitySet = newIssue
2097: .attachActivitySet(null, user);
2098: ActivityManager.createTextActivity(newIssue,
2099: activitySet, ActivityType
2100: .getActivityType(oldA.getActivity()
2101: .getActivityType()), newA);
2102: }
2103: if (Attachment.FILE__PK.equals(newA.getTypeId())) {
2104: try {
2105: oldA.copyFileTo(newA.getFullPath());
2106: } catch (Exception ex) {
2107: throw new ScarabException(
2108: L10NKeySet.ExceptionGeneral, ex);
2109: }
2110: }
2111: }
2112:
2113: // Copy over activity sets for the source issue's previous
2114: // Transactions
2115: final List activitySets = getActivitySets();
2116: final List nonMatchingAttributes = getNonMatchingAttributeValuesList(
2117: newModule, newIssueType);
2118: final List alreadyAssociatedUsers = new ArrayList();
2119: for (Iterator i = activitySets.iterator(); i.hasNext();) {
2120: final ActivitySet as = (ActivitySet) i.next();
2121: ActivitySet newAS = null;
2122: Attachment newAtt = null;
2123: // If activity set has an attachment, make a copy for new issue
2124: if (as.getAttachmentId() != null) {
2125: newAtt = as.getAttachment().copy();
2126: newAtt.save();
2127: }
2128: // Copy over activities with sets
2129: final List activities = as
2130: .getActivityListForIssue(this );
2131: for (Iterator j = activities.iterator(); j.hasNext();) {
2132: final Activity a = (Activity) j.next();
2133: // Only copy transactions that are records of previous move/copies
2134: // Or transactions relating to attributes.
2135: // Other transactions (attachments, dependencies)
2136: // Will be saved when attachments and dependencies are copied
2137: if (as.getTypeId().equals(
2138: (ActivitySetTypePeer.MOVE_ISSUE__PK))
2139: || !a.getAttributeId().equals(
2140: new Integer("0"))) {
2141: newAS = new ActivitySet();
2142: newAS.setTypeId(as.getTypeId());
2143: if (newAtt != null) {
2144: newAS.setAttachmentId(newAtt
2145: .getAttachmentId());
2146: }
2147: newAS.setCreatedBy(as.getCreatedBy());
2148: newAS.setCreatedDate(as.getCreatedDate());
2149: newAS.save();
2150:
2151: // iterate over and copy transaction's activities
2152: final Activity newA = a.copy(newIssue, newAS);
2153: newIssue.getActivity(true).add(newA);
2154:
2155: // If this is an activity relating to setting an attribute value
2156: // And the final value is in the issue right now, we'll copy
2157: // over the attribute value
2158: final AttributeValue attVal = getAttributeValueWithValue(
2159: a.getAttribute(), a.getNewValue(), a
2160: .getNewNumericValue());
2161: if (a.getEndDate() == null && attVal != null) {
2162: final List values = getAttributeValues(a
2163: .getAttribute());
2164: for (Iterator it = values.iterator(); it
2165: .hasNext();) {
2166: final AttributeValue att = (AttributeValue) it
2167: .next();
2168: // Only copy if the target artifact type contains this
2169: // Attribute
2170: if (attVal != null
2171: && !isNonMatchingAttribute(
2172: nonMatchingAttributes,
2173: att)) {
2174: final boolean isUser = (att instanceof UserAttribute);
2175: if (!isUser
2176: || !alreadyAssociatedUsers
2177: .contains(((UserAttribute) att)
2178: .getUserName()
2179: + att
2180: .getAttribute()
2181: .getName())) {
2182: final AttributeValue newAttVal = att
2183: .copy();
2184: newAttVal.setIssueId(newIssue
2185: .getIssueId());
2186: newAttVal.setActivity(newA);
2187: newAttVal
2188: .startActivitySet(newAS);
2189: newAttVal.save();
2190: if (isUser) {
2191: alreadyAssociatedUsers
2192: .add(((UserAttribute) att)
2193: .getUserName()
2194: + att
2195: .getAttribute()
2196: .getName());
2197: }
2198: }
2199: }
2200: }
2201: }
2202: }
2203: }
2204: }
2205: }
2206:
2207: // Generate comment to deal with attributes that do not
2208: // Exist in destination module, as well as the user attributes.
2209: final StringBuffer attachmentBuf = new StringBuffer();
2210: final StringBuffer delAttrsBuf = new StringBuffer();
2211: if (reason != null && reason.length() > 0) {
2212: attachmentBuf.append(reason).append(". ");
2213: }
2214: if (commentAttrs.size() > 0 || commentUserValues.size() > 0) {
2215: attachmentBuf.append(Localization.format(
2216: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2217: "DidNotCopyAttributes", newIssueType.getName()
2218: + "/" + newModule.getName()));
2219: attachmentBuf.append("\n");
2220: for (int i = 0; i < commentAttrs.size(); i++) {
2221: final List attVals = getAttributeValues((Attribute) commentAttrs
2222: .get(i));
2223: for (int j = 0; j < attVals.size(); j++) {
2224: final AttributeValue attVal = (AttributeValue) attVals
2225: .get(j);
2226: String field = null;
2227: delAttrsBuf.append(attVal.getAttribute().getName());
2228: field = attVal.getValue();
2229: delAttrsBuf.append("=").append(field).append(". ")
2230: .append("\n");
2231: }
2232: }
2233: for (int i = 0; i < commentUserValues.size(); i++) {
2234: final UserAttribute useratt = (UserAttribute) commentUserValues
2235: .get(i);
2236: delAttrsBuf.append(useratt.getAttribute().getName()
2237: + ": " + useratt.getUserName() + "\n");
2238: }
2239: final String delAttrs = delAttrsBuf.toString();
2240: attachmentBuf.append(delAttrs);
2241:
2242: // Also create a regular comment with non-matching attribute info
2243: final Attachment comment = new Attachment();
2244: comment.setTextFields(user, newIssue,
2245: Attachment.COMMENT__PK);
2246:
2247: final Object[] args = {
2248: this .getUniqueId(),
2249: newIssueType.getName() + " / "
2250: + newModule.getName() };
2251: final StringBuffer commentBuf = new StringBuffer(
2252: Localization.format(
2253: ScarabConstants.DEFAULT_BUNDLE_NAME,
2254: getLocale(),
2255: "DidNotCopyAttributesFromArtifact", args));
2256: commentBuf.append("\n").append(delAttrs);
2257: comment.setData(commentBuf.toString());
2258: comment.setName(Localization.getString(
2259: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2260: "Comment"));
2261: comment.save();
2262: } else {
2263: attachmentBuf.append(Localization.getString(
2264: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2265: "AllCopied"));
2266: }
2267: attachment.setData(attachmentBuf.toString());
2268:
2269: if (action.equals("move")) {
2270: attachment.setName(Localization.getString(
2271: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2272: "MovedIssueNote"));
2273: } else {
2274: attachment.setName(Localization.getString(
2275: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2276: "CopiedIssueNote"));
2277: }
2278: attachment.setTextFields(user, newIssue,
2279: Attachment.MODIFICATION__PK);
2280: attachment.save();
2281:
2282: // Create activitySet for the MoveIssue activity
2283: final ActivitySet activitySet2 = newIssue.attachActivitySet(
2284: null, user, attachment,
2285: ActivitySetTypePeer.MOVE_ISSUE__PK);
2286:
2287: // Save activity record
2288: final Attribute zeroAttribute = AttributeManager
2289: .getInstance(NUMBERKEY_0);
2290: ActivityManager.createTextActivity(newIssue, zeroAttribute,
2291: activitySet2, ActivityType.ISSUE_MOVED, getUniqueId(),
2292: newIssue.getUniqueId());
2293:
2294: return newIssue;
2295: }
2296:
2297: public void addVote(ScarabUser user) throws ScarabException,
2298: Exception {
2299: // check to see if the user has voted for this issue
2300: int previousVotes = 0;
2301: IssueVote issueVote = null;
2302: Criteria crit = new Criteria().add(IssueVotePeer.ISSUE_ID,
2303: getIssueId()).add(IssueVotePeer.USER_ID,
2304: user.getUserId());
2305: List votes = IssueVotePeer.doSelect(crit);
2306: if (votes != null && votes.size() != 0) {
2307: issueVote = (IssueVote) votes.get(0);
2308: previousVotes = issueVote.getVotes();
2309: } else {
2310: issueVote = new IssueVote();
2311: issueVote.setIssueId(getIssueId());
2312: issueVote.setUserId(user.getUserId());
2313: }
2314:
2315: // check if the module accepts multiple votes
2316: if (!getModule().allowsMultipleVoting() && previousVotes > 0) {
2317: throw new ScarabException(
2318: L10NKeySet.ExceptionMultipleVoteForUnallowed, user
2319: .getUserName(), getUniqueId());
2320: }
2321:
2322: // save the user's vote
2323: issueVote.setVotes(previousVotes + 1);
2324: issueVote.save();
2325:
2326: // update the total votes for the issue
2327: crit = new Criteria().add(AttributeValuePeer.ATTRIBUTE_ID,
2328: AttributePeer.TOTAL_VOTES__PK);
2329: List voteValues = getAttributeValues(crit);
2330: TotalVotesAttribute voteValue = null;
2331: if (voteValues.size() == 0) {
2332: voteValue = new TotalVotesAttribute();
2333: voteValue.setIssue(this );
2334: voteValue.setAttributeId(AttributePeer.TOTAL_VOTES__PK);
2335: } else {
2336: voteValue = (TotalVotesAttribute) voteValues.get(0);
2337: }
2338: // Updating attribute values requires a activitySet
2339: ActivitySet activitySet = attachActivitySet(null, user, null,
2340: ActivitySetTypePeer.RETOTAL_ISSUE_VOTE__PK);
2341: voteValue.startActivitySet(activitySet);
2342: voteValue.addVote();
2343: voteValue.save();
2344: }
2345:
2346: /**
2347: * Gets a list of non-user AttributeValues which match a given Module.
2348: * It is used in the MoveIssue2.vm template
2349: */
2350: public List getMatchingAttributeValuesList(Module newModule,
2351: IssueType newIssueType) throws TorqueException {
2352: List matchingAttributes = new ArrayList();
2353: Map setMap = this .getAttributeValuesMap();
2354: for (Iterator iter = setMap.keySet().iterator(); iter.hasNext();) {
2355: AttributeValue aval = (AttributeValue) setMap.get(iter
2356: .next());
2357: List values = getAttributeValues(aval.getAttribute());
2358: // loop thru the values for this attribute
2359: for (int i = 0; i < values.size(); i++) {
2360: AttributeValue attVal = (AttributeValue) values.get(i);
2361: RModuleAttribute modAttr = newModule
2362: .getRModuleAttribute(aval.getAttribute(),
2363: newIssueType);
2364:
2365: // If this attribute is active for the destination module,
2366: // Add to matching attributes list
2367: if (modAttr != null && modAttr.getActive()) {
2368: // If attribute is an option attribute,
2369: // Check if attribute option is active for destination module.
2370: if (aval instanceof OptionAttribute) {
2371: // FIXME: Use select count
2372: Criteria crit2 = new Criteria(4).add(
2373: RModuleOptionPeer.ACTIVE, true).add(
2374: RModuleOptionPeer.OPTION_ID,
2375: attVal.getOptionId()).add(
2376: RModuleOptionPeer.MODULE_ID,
2377: newModule.getModuleId()).add(
2378: RModuleOptionPeer.ISSUE_TYPE_ID,
2379: newIssueType.getIssueTypeId());
2380: List modOpt = RModuleOptionPeer.doSelect(crit2);
2381:
2382: if (!modOpt.isEmpty()) {
2383: matchingAttributes.add(attVal);
2384: }
2385: } else if (attVal instanceof UserAttribute) {
2386: ScarabUser user = null;
2387: try {
2388: user = ScarabUserManager.getInstance(attVal
2389: .getUserId());
2390: } catch (Exception e) {
2391: getLog().error(e);
2392: e.printStackTrace();
2393: }
2394: Attribute attr = attVal.getAttribute();
2395: ScarabUser[] userArray = newModule
2396: .getUsers(attr.getPermission());
2397: // If user exists in destination module with this permission,
2398: // Add as matching value
2399: if (Arrays.asList(userArray).contains(user)) {
2400: matchingAttributes.add(attVal);
2401: }
2402: } else {
2403: matchingAttributes.add(attVal);
2404: }
2405: }
2406: }
2407: }
2408: return matchingAttributes;
2409: }
2410:
2411: public List getMatchingAttributeValuesList(String moduleId,
2412: String issueTypeId) throws TorqueException {
2413: Module module = ModuleManager
2414: .getInstance(new Integer(moduleId));
2415: IssueType issueType = IssueTypeManager.getInstance(new Integer(
2416: issueTypeId));
2417: return getMatchingAttributeValuesList(module, issueType);
2418: }
2419:
2420: /**
2421: * Gets a list AttributeValues which the source module has,
2422: * But the destination module does not have, when doing a copy.
2423: * It is used in the MoveIssue2.vm template
2424: */
2425: public List getNonMatchingAttributeValuesList(Module newModule,
2426: IssueType newIssueType) throws TorqueException {
2427: List nonMatchingAttributes = new ArrayList();
2428: AttributeValue aval = null;
2429:
2430: Map setMap = this .getAttributeValuesMap();
2431: for (Iterator iter = setMap.values().iterator(); iter.hasNext();) {
2432: aval = (AttributeValue) iter.next();
2433: List values = getAttributeValues(aval.getAttribute());
2434: // loop thru the values for this attribute
2435: for (Iterator i = values.iterator(); i.hasNext();) {
2436: AttributeValue attVal = (AttributeValue) i.next();
2437: RModuleAttribute modAttr = newModule
2438: .getRModuleAttribute(aval.getAttribute(),
2439: newIssueType);
2440:
2441: // If this attribute is not active for the destination module,
2442: // Add to nonMatchingAttributes list
2443: if (modAttr == null || !modAttr.getActive()) {
2444: nonMatchingAttributes.add(attVal);
2445: } else {
2446: // If attribute is an option attribute, Check if
2447: // attribute option is active for destination module.
2448: if (attVal instanceof OptionAttribute) {
2449: Criteria crit2 = new Criteria(1).add(
2450: RModuleOptionPeer.ACTIVE, true).add(
2451: RModuleOptionPeer.OPTION_ID,
2452: attVal.getOptionId()).add(
2453: RModuleOptionPeer.MODULE_ID,
2454: newModule.getModuleId()).add(
2455: RModuleOptionPeer.ISSUE_TYPE_ID,
2456: newIssueType.getIssueTypeId());
2457: List modOpt = RModuleOptionPeer.doSelect(crit2);
2458:
2459: if (modOpt.isEmpty()) {
2460: nonMatchingAttributes.add(attVal);
2461: }
2462: } else if (attVal instanceof UserAttribute) {
2463: ScarabUser user = null;
2464: try {
2465: user = ScarabUserManager.getInstance(attVal
2466: .getUserId());
2467: } catch (Exception e) {
2468: Log.get().error(
2469: "Unable to retrieve user for "
2470: + "attribute", e);
2471: }
2472: Attribute attr = attVal.getAttribute();
2473: ScarabUser[] userArray = newModule
2474: .getUsers(attr.getPermission());
2475: // If user exists in destination module with
2476: // this permission, add as matching value.
2477: if (!Arrays.asList(userArray).contains(user)) {
2478: nonMatchingAttributes.add(attVal);
2479: }
2480: }
2481: }
2482: }
2483: }
2484: return nonMatchingAttributes;
2485: }
2486:
2487: public List getNonMatchingAttributeValuesList(String moduleId,
2488: String issueTypeId) throws TorqueException {
2489: Module module = ModuleManager
2490: .getInstance(new Integer(moduleId));
2491: IssueType issueType = IssueTypeManager.getInstance(new Integer(
2492: issueTypeId));
2493: return getNonMatchingAttributeValuesList(module, issueType);
2494: }
2495:
2496: /**
2497: * Checks if user has permission to delete issue template.
2498: * Only the creating user can delete a personal template.
2499: * Only project owner or admin can delete a project-wide template.
2500: */
2501: public void deleteItem(ScarabUser user) throws TorqueException,
2502: ScarabException {
2503: Module module = getModule();
2504: if (user.hasPermission(ScarabSecurity.ITEM__DELETE, module)
2505: || (user.getUserId().equals(getCreatedBy().getUserId()) && isTemplate())) {
2506: setDeleted(true);
2507: save();
2508: } else {
2509: throw new ScarabException(
2510: L10NKeySet.YouDoNotHavePermissionToAction);
2511: }
2512: }
2513:
2514: /**
2515: * If the user has permission to delete ISSUES in this module,
2516: * it wil mark this issue as DELETED.
2517: * @param user
2518: * @throws Exception
2519: * @throws ScarabException
2520: */
2521: public void deleteIssue(ScarabUser user) throws Exception,
2522: ScarabException {
2523: if (user.hasPermission(ScarabSecurity.ISSUE__DELETE, this
2524: .getModule())) {
2525: ActivitySet activitySet = attachActivitySet(null, user);
2526: ActivityManager
2527: .createDeleteIssueActivity(this , activitySet);
2528: this .setDeleted(true);
2529: List dependencies = this .getDependsRelatedByObservedId();
2530: dependencies.addAll(this .getDependsRelatedByObserverId());
2531: for (Iterator it = dependencies.iterator(); it.hasNext();) {
2532: Depend depend = (Depend) it.next();
2533: ActivitySet deleteSet = this .doDeleteDependency(
2534: activitySet, depend, user);
2535: for (Iterator act = deleteSet.getActivityList()
2536: .iterator(); act.hasNext();) {
2537: activitySet.addActivity((Activity) act.next());
2538: }
2539: NotificationManagerFactory.getInstance()
2540: .addActivityNotification(
2541: ActivityType.ISSUE_DELETED,
2542: activitySet, this , user);
2543: }
2544: save();
2545: }
2546: }
2547:
2548: /**
2549: * This method will return the AttributeValue which represents the default
2550: * text attribute.
2551: *
2552: * @return the AttributeValue to use as the email subject, or null
2553: * if no suitable AttributeValue could be found.
2554: */
2555: public AttributeValue getDefaultTextAttributeValue()
2556: throws TorqueException {
2557: AttributeValue result = null;
2558: Object obj = ScarabCache.get(this ,
2559: GET_DEFAULT_TEXT_ATTRIBUTEVALUE);
2560: if (obj == null) {
2561: Attribute defaultTextAttribute = getIssueType()
2562: .getDefaultTextAttribute(getModule());
2563: if (defaultTextAttribute != null) {
2564: result = getAttributeValue(defaultTextAttribute);
2565: }
2566: ScarabCache.put(result, this ,
2567: GET_DEFAULT_TEXT_ATTRIBUTEVALUE);
2568: } else {
2569: result = (AttributeValue) obj;
2570: }
2571: return result;
2572: }
2573:
2574: /**
2575: * This calls getDefaultTextAttributeValue() and then returns the
2576: * String value of the Attribute. This method is used to get the
2577: * subject of an email. if no text attribute value is found it
2578: * will use the first ActivitySet comment.
2579: */
2580: public String getDefaultText() throws TorqueException {
2581: String result = null;
2582: Object obj = ScarabCache.get(this , GET_DEFAULT_TEXT);
2583: if (obj == null) {
2584: AttributeValue emailAV = getDefaultTextAttributeValue();
2585: if (emailAV != null) {
2586: result = emailAV.getValue();
2587: }
2588: if (result == null) {
2589: ActivitySet activitySet = getInitialActivitySet();
2590: if (activitySet != null) {
2591: Attachment reason = activitySet.getAttachment();
2592: if (reason != null && reason.getData() != null
2593: && reason.getData().trim().length() > 0) {
2594: result = reason.getData();
2595: }
2596: }
2597: }
2598: result = (result == null) ? Localization.getString(
2599: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2600: "NoIssueSummaryAvailable") : result;
2601: ScarabCache.put(result, this , GET_DEFAULT_TEXT);
2602: } else {
2603: result = (String) obj;
2604: }
2605: return result;
2606: }
2607:
2608: private MethodResultCache getMethodResult() {
2609: return IssueManager.getMethodResult();
2610: }
2611:
2612: /**
2613: * gets an object from the appropriate cache, based on whether this is
2614: * a saved issue. if you know the object should only be in ScarabCache
2615: * do not use this method.
2616: */
2617: private Object getCachedObject(String methodName) {
2618: Object obj = null;
2619: // Cache Note:
2620: // we check for issue id, so that we only (JCS) cache for saved issues
2621: // if we decide to cache results for new issues we should replace
2622: // this conditional with (this instanceof IssueSearch) because
2623: // we definitely do not want to cache those.
2624: if (getIssueId() == null) {
2625: obj = ScarabCache.get(this , methodName);
2626: } else {
2627: obj = getMethodResult().get(this , methodName);
2628: }
2629: return obj;
2630: }
2631:
2632: /**
2633: * puts an object into the appropriate cache, based on whether this is
2634: * a saved issue. if you know the object should only be in ScarabCache
2635: * do not use this method.
2636: */
2637: private void putCachedObject(Object obj, String methodName) {
2638: // see Cache Note above
2639: if (getIssueId() == null) {
2640: ScarabCache.put(obj, this , methodName);
2641: } else {
2642: getMethodResult().put(obj, this , methodName);
2643: }
2644: }
2645:
2646: /**
2647: * gets an object from the appropriate cache, based on whether this is
2648: * a saved issue. if you know the object should only be in ScarabCache
2649: * do not use this method.
2650: */
2651: private Object getCachedObject(String methodName, Serializable arg1) {
2652: Object obj = null;
2653: // Cache Note:
2654: // we check for issue id, so that we only (JCS) cache for saved issues
2655: // if we decide to cache results for new issues we should replace
2656: // this conditional with (this instanceof IssueSearch) because
2657: // we definitely do not want to cache those.
2658: if (getIssueId() == null) {
2659: obj = ScarabCache.get(this , methodName, arg1);
2660: } else {
2661: obj = getMethodResult().get(this , methodName, arg1);
2662: }
2663: return obj;
2664: }
2665:
2666: /**
2667: * puts an object into the appropriate cache, based on whether this is
2668: * a saved issue. if you know the object should only be in ScarabCache
2669: * do not use this method.
2670: */
2671: private void putCachedObject(Object obj, String methodName,
2672: Serializable arg1) {
2673: // see Cache Note above
2674: if (getIssueId() == null) {
2675: ScarabCache.put(obj, this , methodName, arg1);
2676: } else {
2677: getMethodResult().put(obj, this , methodName, arg1);
2678: }
2679: }
2680:
2681: /**
2682: * gets an object from the appropriate cache, based on whether this is
2683: * a saved issue. if you know the object should only be in ScarabCache
2684: * do not use this method.
2685: */
2686: private Object getCachedObject(String methodName,
2687: Serializable arg1, Serializable arg2) {
2688: Object obj = null;
2689: // Cache Note:
2690: // we check for issue id, so that we only (JCS) cache for saved issues
2691: // if we decide to cache results for new issues we should replace
2692: // this conditional with (this instanceof IssueSearch) because
2693: // we definitely do not want to cache those.
2694: if (getIssueId() == null) {
2695: obj = ScarabCache.get(this , methodName, arg1, arg2);
2696: } else {
2697: obj = getMethodResult().get(this , methodName, arg1, arg2);
2698: }
2699: return obj;
2700: }
2701:
2702: /**
2703: * puts an object into the appropriate cache, based on whether this is
2704: * a saved issue. if you know the object should only be in ScarabCache
2705: * do not use this method.
2706: */
2707: private void putCachedObject(Object obj, String methodName,
2708: Serializable arg1, Serializable arg2) {
2709: // see Cache Note above
2710: if (getIssueId() == null) {
2711: ScarabCache.put(obj, this , methodName, arg1, arg2);
2712: } else {
2713: getMethodResult().put(obj, this , methodName, arg1, arg2);
2714: }
2715: }
2716:
2717: // *******************************************************************
2718: // Permissions methods - these are deprecated
2719: // *******************************************************************
2720:
2721: /**
2722: * Checks if user has permission to enter issue.
2723: * @deprecated user.hasPermission(ScarabSecurity.ISSUE__ENTER, module)
2724: */
2725: public boolean hasEnterPermission(ScarabUser user, Module module)
2726: throws TorqueException {
2727: boolean hasPerm = false;
2728:
2729: if (user.hasPermission(ScarabSecurity.ISSUE__ENTER, module)) {
2730: hasPerm = true;
2731: }
2732: return hasPerm;
2733: }
2734:
2735: /**
2736: * Checks if user has permission to edit issue.
2737: * @deprecated user.hasPermission(ScarabSecurity.ISSUE__EDIT, module)
2738: */
2739: public boolean hasEditPermission(ScarabUser user, Module module)
2740: throws TorqueException {
2741: boolean hasPerm = false;
2742:
2743: if (user.hasPermission(ScarabSecurity.ISSUE__EDIT, module)
2744: || user.equals(getCreatedBy())) {
2745: hasPerm = true;
2746: }
2747: return hasPerm;
2748: }
2749:
2750: /**
2751: * Checks if user has permission to move issue to destination module.
2752: * @deprecated user.hasPermission(ScarabSecurity.ISSUE__EDIT, module)
2753: */
2754: public boolean hasMovePermission(ScarabUser user, Module module)
2755: throws TorqueException {
2756: boolean hasPerm = false;
2757:
2758: if (user.hasPermission(ScarabSecurity.ISSUE__EDIT, module)
2759: || user.equals(getCreatedBy())) {
2760: hasPerm = true;
2761: }
2762: return hasPerm;
2763: }
2764:
2765: /**
2766: * Assigns user to issue.
2767: */
2768: public ActivitySet assignUser(ActivitySet activitySet,
2769: final ScarabUser assignee, final ScarabUser assigner,
2770: final Attribute attribute, final Attachment attachment)
2771: throws TorqueException, ScarabException {
2772: final UserAttribute attVal = new UserAttribute();
2773:
2774: activitySet = attachActivitySet(activitySet, assigner,
2775: attachment);
2776: attVal.startActivitySet(activitySet);
2777:
2778: ActivityManager.createUserActivity(this , attribute,
2779: activitySet, null, null, assignee.getUserId());
2780:
2781: // Save user attribute values
2782: attVal.setIssue(this );
2783: attVal.setAttributeId(attribute.getAttributeId());
2784: attVal.setUserId(assignee.getUserId());
2785: attVal.setValue(assignee.getUserName());
2786: attVal.save();
2787:
2788: return activitySet;
2789: }
2790:
2791: /**
2792: * Used to change a user attribute value from one user attribute
2793: * to a new one.
2794: */
2795: public ActivitySet changeUserAttributeValue(
2796: ActivitySet activitySet, final ScarabUser assignee,
2797: final ScarabUser assigner, final AttributeValue oldAttVal,
2798: final Attribute newAttr, final Attachment attachment)
2799: throws TorqueException, ScarabException {
2800: activitySet = attachActivitySet(activitySet, assigner,
2801: attachment);
2802: oldAttVal.startActivitySet(activitySet);
2803:
2804: // Save activity record for deletion of old assignment
2805: ActivityManager.createUserActivity(this , oldAttVal
2806: .getAttribute(), activitySet, null, assignee
2807: .getUserId(), null);
2808:
2809: // Save activity record for new assignment
2810: ActivityManager.createUserActivity(this , newAttr, activitySet,
2811: null, null, assignee.getUserId());
2812:
2813: // Save assignee value
2814: oldAttVal.setAttributeId(newAttr.getAttributeId());
2815: oldAttVal.save();
2816:
2817: return activitySet;
2818: }
2819:
2820: /**
2821: * Used to delete a user attribute value.
2822: */
2823: public ActivitySet deleteUser(ActivitySet activitySet,
2824: final ScarabUser assignee, final ScarabUser assigner,
2825: final AttributeValue attVal, final Attachment attachment)
2826: throws TorqueException, ScarabException {
2827: activitySet = attachActivitySet(activitySet, assigner,
2828: attachment);
2829: attVal.startActivitySet(activitySet);
2830:
2831: // Save activity record
2832: ActivityManager.createUserActivity(this , attVal.getAttribute(),
2833: activitySet, null, assignee.getUserId(), null);
2834:
2835: // Save assignee value
2836: attVal.setDeleted(true);
2837: attVal.save();
2838:
2839: return activitySet;
2840: }
2841:
2842: /**
2843: * Deletes a specific dependency on this issue.
2844: */
2845: public ActivitySet doDeleteDependency(ActivitySet activitySet,
2846: Depend oldDepend, final ScarabUser user)
2847: throws TorqueException, ScarabException {
2848: final Issue otherIssue = IssueManager.getInstance(oldDepend
2849: .getObserverId(), false);
2850: /* XXX Why can a child not delete a dependency??
2851: if (otherIssue.equals(this))
2852: {
2853: throw new ScarabException("CannotDeleteDependency");
2854: }
2855: */
2856: final Issue this Issue = IssueManager.getInstance(oldDepend
2857: .getObservedId(), false);
2858:
2859: // get the original object so that we do an update
2860: oldDepend = this Issue.getDependency(otherIssue);
2861: oldDepend.setNew(false);
2862: oldDepend.setDeleted(true);
2863: oldDepend.save();
2864:
2865: // need to null out the cache entry so that Issue.getDependency()
2866: // does not try to return the item from the cache
2867: ScarabCache.put(null, this Issue, GET_DEPENDENCY, otherIssue);
2868:
2869: Attachment comment = oldDepend.getDescriptionAsAttachment(user,
2870: this Issue);
2871:
2872: activitySet = this Issue.attachActivitySet(activitySet, user,
2873: comment);
2874: activitySet = otherIssue.attachActivitySet(activitySet, user,
2875: comment);
2876:
2877: ActivityManager.createDeleteDependencyActivity(this Issue,
2878: activitySet, oldDepend);
2879: ActivityManager.createDeleteDependencyActivity(otherIssue,
2880: activitySet, oldDepend);
2881:
2882: return activitySet;
2883: }
2884:
2885: /**
2886: * Given a specific attachment object allow us to update
2887: * the information in it. If the old matches the new, then
2888: * nothing is modified.
2889: */
2890: public ActivitySet doChangeUrlDescription(ActivitySet activitySet,
2891: final ScarabUser user, final Attachment attachment,
2892: final String oldDescription) throws TorqueException,
2893: ScarabException {
2894: final String newDescription = attachment.getName();
2895: if (!oldDescription.equals(newDescription)) {
2896: final Object[] args = { oldDescription, newDescription, };
2897: String desc = Localization.format(
2898: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2899: "UrlDescChangedDesc", args);
2900:
2901: if (desc.length() > 248) {
2902: desc = desc.substring(0, 248) + "...";
2903: }
2904: activitySet = attachActivitySet(activitySet, user);
2905: ActivityManager.createTextActivity(this , activitySet,
2906: ActivityType.URL_DESC_CHANGED, attachment,
2907: oldDescription, newDescription);
2908: NotificationManagerFactory.getInstance()
2909: .addActivityNotification(
2910: ActivityType.URL_DESC_CHANGED, activitySet,
2911: this , user);
2912: }
2913: return activitySet;
2914: }
2915:
2916: /**
2917: * Given a specific attachment object allow us to update
2918: * the information in it. If the old matches the new, then
2919: * nothing is modified.
2920: */
2921: public ActivitySet doChangeUrlUrl(ActivitySet activitySet,
2922: final ScarabUser user, final Attachment attachment,
2923: final String oldUrl) throws TorqueException,
2924: ScarabException {
2925: final String newUrl = attachment.getData();
2926: if (!oldUrl.equals(newUrl)) {
2927: final Object[] args = { oldUrl, newUrl };
2928: String desc = Localization.format(
2929: ScarabConstants.DEFAULT_BUNDLE_NAME, getLocale(),
2930: "UrlChangedDesc", args);
2931:
2932: if (desc.length() > 248) {
2933: desc = desc.substring(0, 248) + "...";
2934: }
2935: activitySet = attachActivitySet(activitySet, user);
2936:
2937: // Save activity record
2938: ActivityManager.createTextActivity(this , activitySet,
2939: ActivityType.URL_CHANGED, attachment, oldUrl,
2940: newUrl);
2941:
2942: NotificationManagerFactory.getInstance()
2943: .addActivityNotification(ActivityType.URL_CHANGED,
2944: activitySet, this , user);
2945: }
2946: return activitySet;
2947: }
2948:
2949: /**
2950: * changes the dependency type as well as. will not change deptype
2951: * for deleted deps
2952: */
2953: public ActivitySet doChangeDependencyType(ActivitySet activitySet,
2954: final Depend oldDepend, final Depend newDepend,
2955: final ScarabUser user) throws TorqueException,
2956: ScarabException {
2957: final String oldName = oldDepend.getDependType().getName();
2958: final String newName = newDepend.getDependType().getName();
2959:
2960: final boolean rolesHaveSwitched = (oldDepend.getObserverId()
2961: .equals(newDepend.getObservedId()) && oldDepend
2962: .getObservedId().equals(newDepend.getObserverId()));
2963: final boolean typeHasChanged = (!newName.equals(oldName));
2964:
2965: final boolean isActive = !newDepend.getDeleted();
2966:
2967: // check to see if something changed
2968: // only change dependency type for non-deleted deps
2969: if (isActive && (rolesHaveSwitched || typeHasChanged)) {
2970: final Issue otherIssue = IssueManager.getInstance(newDepend
2971: .getObservedId(), false);
2972:
2973: // always delete an old dependency
2974: oldDepend.setDeleted(true);
2975: oldDepend.save();
2976: // always create a new dependency
2977: newDepend.setNew(true);
2978: newDepend.save();
2979:
2980: // need to null out the cache entry so that Issue.getDependency()
2981: // does not try to return the item from the cache
2982: ScarabCache.put(null, this , GET_DEPENDENCY, otherIssue);
2983:
2984: final Attachment comment = newDepend
2985: .getDescriptionAsAttachment(user, this );
2986:
2987: activitySet = attachActivitySet(activitySet, user, comment);
2988: activitySet = otherIssue.attachActivitySet(activitySet,
2989: user, comment);
2990:
2991: ActivityManager.createChangeDependencyActivity(this ,
2992: activitySet, newDepend, oldName, newName);
2993: ActivityManager.createChangeDependencyActivity(otherIssue,
2994: activitySet, newDepend, oldName, newName);
2995: }
2996: return activitySet;
2997: }
2998:
2999: /**
3000: * Sets original AttributeValues for an new issue based on a hashmap of values
3001: * This is data is saved to the database and the proper ActivitySet is
3002: * also recorded.
3003: *
3004: * @throws TorqueException when the workflow has an error to report
3005: */
3006: public ActivitySet setInitialAttributeValues(
3007: ActivitySet activitySet, Attachment attachment,
3008: final HashMap newValues, final ScarabUser user)
3009: throws TorqueException, ScarabException {
3010: // Check new values for workflow
3011: final String msg = doCheckInitialAttributeValueWorkflow(
3012: newValues, user);
3013: if (msg != null) {
3014: throw new TorqueException(msg); //EXCEPTION
3015: }
3016:
3017: if (activitySet == null) {
3018: // Save activitySet record
3019: activitySet = ActivitySetManager.getInstance(
3020: ActivitySetTypePeer.CREATE_ISSUE__PK, user);
3021: activitySet.save();
3022: }
3023: setActivitySetRelatedByCreatedTransId(activitySet);
3024:
3025: // enter the values into the activitySet
3026: final LinkedMap avMap = getModuleAttributeValuesMap();
3027: final MapIterator iter = avMap.mapIterator();
3028: while (iter.hasNext()) {
3029: final AttributeValue aval = (AttributeValue) avMap.get(iter
3030: .next());
3031: try {
3032: aval.startActivitySet(activitySet);
3033: } catch (ScarabException se) {
3034: L10NMessage l10nmsg = new L10NMessage(
3035: L10NKeySet.ExceptionTorqueGeneric, se);
3036: throw new ScarabException(l10nmsg);
3037: }
3038: }
3039: this .save();
3040:
3041: // create initial issue creation activity
3042: ActivityManager.createReportIssueActivity(this , activitySet,
3043: Localization.getString(
3044: ScarabConstants.DEFAULT_BUNDLE_NAME,
3045: getLocale(), "IssueCreated"));
3046:
3047: // this needs to be done after the issue is created.
3048: // check to make sure the attachment has data before submitting it.
3049: final String attachmentData = attachment.getData();
3050: if (attachmentData != null && attachmentData.length() > 0) {
3051: attachment = AttachmentManager.getReason(attachment, this ,
3052: user);
3053: activitySet.setAttachment(attachment);
3054: }
3055: activitySet.save();
3056:
3057: // need to clear the cache since this is after the
3058: // issue is saved. for some reason, things don't
3059: // show up properly right away.
3060: ScarabCache.clear();
3061: return activitySet;
3062: }
3063:
3064: /**
3065: * Sets AttributeValues for an issue based on a hashmap of attribute values
3066: * This is data is saved to the database and the proper ActivitySet is
3067: * also recorded.
3068: * @param activitySet ActivitySet instance
3069: * @param newAttVals A map of attribute Id's vs new AttributeValues
3070: * @param attachment Attachment to the issue
3071: * @param user User responsible for this activity
3072: * @return ActivitySet object containing the changes made to the issue
3073: * @throws TorqueException when the workflow has an error to report
3074: */
3075: public ActivitySet setAttributeValues(ActivitySet activitySet,
3076: final HashMap newAttVals, final Attachment attachment,
3077: final ScarabUser user) throws TorqueException,
3078: ScarabException {
3079: if (!isTemplate()) {
3080: final String msg = doCheckAttributeValueWorkflow(
3081: newAttVals, user);
3082: if (msg != null) {
3083: throw new ScarabException(
3084: L10NKeySet.ErrorExceptionMessage, msg); //EXCEPTION
3085: }
3086: }
3087: // save the attachment if it exists.
3088: if (attachment != null) {
3089: attachment.setTextFields(user, this ,
3090: Attachment.MODIFICATION__PK);
3091: attachment.save();
3092: }
3093: activitySet = attachActivitySet(activitySet, user, attachment);
3094:
3095: final LinkedMap avMap = getModuleAttributeValuesMap();
3096: AttributeValue oldAttVal = null;
3097: AttributeValue newAttVal = null;
3098: final Iterator iter = newAttVals.keySet().iterator();
3099: boolean attValDeleted = false;
3100: while (iter.hasNext()) {
3101: final Integer attrId = (Integer) iter.next();
3102: final Attribute attr = AttributeManager.getInstance(attrId);
3103: oldAttVal = (AttributeValue) avMap.get(attr.getName()
3104: .toUpperCase());
3105: newAttVal = (AttributeValue) newAttVals.get(attrId);
3106: final String newAttValValue = newAttVal.getValue();
3107:
3108: if (oldAttVal != null
3109: && (newAttValValue != null
3110: && !newAttValValue.equals(oldAttVal
3111: .getValue()) || newAttValValue == null)) {
3112: if (Log.get().isDebugEnabled()) {
3113: Log.get().debug(
3114: "Attribute: " + attr.getName()
3115: + " has newAttValValue = "
3116: + newAttValValue);
3117: }
3118: if (newAttValValue != null
3119: && newAttValValue.length() > 0) {
3120: oldAttVal.setProperties(newAttVal);
3121: } else {
3122: oldAttVal.setDeleted(true);
3123: Log.get().debug("setDeleted(true)");
3124: attValDeleted = true;
3125: }
3126: oldAttVal.startActivitySet(activitySet);
3127: oldAttVal.save();
3128: }
3129:
3130: }
3131: if (attValDeleted) {
3132: //Remove attribute value map from cache
3133: getMethodResult().remove(this , GET_MODULE_ATTRVALUES_MAP,
3134: Boolean.TRUE);
3135: }
3136: return activitySet;
3137: }
3138:
3139: /**
3140: * Sets an ActivitySet as the lastActivitySet of an Issue.
3141: * Crates and saves a new ActivitySet, if required.
3142: * Saves the Issue.
3143: * @return ActivitySet
3144: * @throws TorqueException
3145: */
3146: protected ActivitySet attachActivitySet(ActivitySet activitySet,
3147: final ScarabUser user, final Attachment attachment,
3148: final Integer activitySetType) throws TorqueException,
3149: ScarabException {
3150: if (activitySet == null) {
3151: activitySet = getActivitySet(user, attachment,
3152: activitySetType);
3153: activitySet.save();
3154: ScarabCache.clear();
3155: }
3156: setLastTransId(activitySet.getActivitySetId());
3157: save();
3158: return activitySet;
3159: }
3160:
3161: protected ActivitySet attachActivitySet(ActivitySet activitySet,
3162: final ScarabUser user, final Attachment attachment)
3163: throws TorqueException, ScarabException {
3164: return attachActivitySet(activitySet, user, attachment,
3165: ActivitySetTypePeer.EDIT_ISSUE__PK);
3166: }
3167:
3168: protected ActivitySet attachActivitySet(ActivitySet activitySet,
3169: final ScarabUser user) throws TorqueException,
3170: ScarabException {
3171: return attachActivitySet(activitySet, user, null,
3172: ActivitySetTypePeer.EDIT_ISSUE__PK);
3173: }
3174:
3175: /**
3176: * This method is used with the setInitialAttributeValues() method to
3177: * Make sure that workflow is valid for the initial values of a new issue.
3178: * It will return a non-null String
3179: * which is the workflow error message otherwise it will return null.
3180: */
3181: public String doCheckInitialAttributeValueWorkflow(
3182: final HashMap newValues, final ScarabUser user)
3183: throws TorqueException, ScarabException {
3184: String msg = null;
3185: final Iterator iter = newValues.keySet().iterator();
3186: while (iter.hasNext()) {
3187: final Integer attrId = (Integer) iter.next();
3188: final Attribute attr = AttributeManager.getInstance(attrId);
3189: if (attr.isOptionAttribute()) {
3190: final AttributeOption toOption = AttributeOptionManager
3191: .getInstance(new Integer((String) newValues
3192: .get(attrId)));
3193: msg = WorkflowFactory.getInstance()
3194: .checkInitialTransition(toOption, this ,
3195: newValues, user);
3196: }
3197: if (msg != null) {
3198: break;
3199: }
3200: }
3201: return msg;
3202: }
3203:
3204: /**
3205: * This method is used with the setAttributeValues() method to
3206: * Make sure that workflow is valid. It will return a non-null String
3207: * which is the workflow error message otherwise it will return null.
3208: */
3209: public String doCheckAttributeValueWorkflow(
3210: final HashMap newAttVals, final ScarabUser user)
3211: throws TorqueException, ScarabException {
3212: final LinkedMap avMap = getModuleAttributeValuesMap();
3213: AttributeValue oldAttVal = null;
3214: AttributeValue newAttVal = null;
3215: String msg = null;
3216: final Iterator iter = newAttVals.keySet().iterator();
3217: while (iter.hasNext()) {
3218: final Integer attrId = (Integer) iter.next();
3219: final Attribute attr = AttributeManager.getInstance(attrId);
3220: oldAttVal = (AttributeValue) avMap.get(attr.getName()
3221: .toUpperCase());
3222: newAttVal = (AttributeValue) newAttVals.get(attrId);
3223: AttributeOption fromOption = null;
3224: AttributeOption toOption = null;
3225:
3226: if (newAttVal.getValue() != null) {
3227: if (newAttVal.getAttribute().isOptionAttribute()) {
3228: if (oldAttVal.getOptionId() == null) {
3229: fromOption = AttributeOptionManager
3230: .getInstance(ScarabConstants.INTEGER_0);
3231: } else {
3232: fromOption = oldAttVal.getAttributeOption();
3233: }
3234: toOption = newAttVal.getAttributeOption();
3235: msg = WorkflowFactory.getInstance()
3236: .checkTransition(fromOption, toOption,
3237: this , newAttVals, user);
3238: }
3239: if (msg != null) {
3240: break;
3241: }
3242: }
3243: }
3244: return msg;
3245: }
3246:
3247: /**
3248: * This method is used with the setAttributeValues() method to
3249: * Make sure that workflow is valid. It will return a non-null String
3250: * which is the workflow error message otherwise it will return null.
3251: *
3252: * @deprecated The attachment doesn't need to be passed into this method.
3253: */
3254: public String doCheckAttributeValueWorkflow(
3255: final HashMap newAttVals, final Attachment attachment,
3256: final ScarabUser user) throws TorqueException,
3257: ScarabException {
3258: return doCheckAttributeValueWorkflow(newAttVals, user);
3259: }
3260:
3261: /**
3262: * If the comment hasn't changed, it will return a valid ActivitySet
3263: * otherwise it returns null.
3264: */
3265: public ActivitySet doEditComment(ActivitySet activitySet,
3266: final String newComment, final Attachment attachment,
3267: final ScarabUser user) throws TorqueException,
3268: ScarabException {
3269: final String oldComment = attachment.getData();
3270: if (!newComment.equals(oldComment)) {
3271: attachment.setData(newComment);
3272: attachment.save();
3273:
3274: activitySet = attachActivitySet(activitySet, user);
3275: // Save activity record
3276: ActivityManager.createTextActivity(this , null, activitySet,
3277: ActivityType.COMMENT_CHANGED, null, attachment,
3278: oldComment, newComment);
3279:
3280: NotificationManagerFactory.getInstance()
3281: .addActivityNotification(
3282: ActivityType.COMMENT_CHANGED, activitySet,
3283: this , user);
3284: }
3285: return activitySet;
3286: }
3287:
3288: /**
3289: * If the URL hasn't changed, it will return a valid ActivitySet
3290: * otherwise it returns null.
3291: */
3292: public ActivitySet doDeleteUrl(ActivitySet activitySet,
3293: final Attachment attachment, final ScarabUser user)
3294: throws TorqueException, ScarabException {
3295: final String oldUrl = attachment.getData();
3296: attachment.setDeleted(true);
3297: attachment.save();
3298:
3299: activitySet = attachActivitySet(activitySet, user);
3300:
3301: // Save activity record
3302: ActivityManager.createTextActivity(this , null, activitySet,
3303: ActivityType.URL_DELETED, null, attachment, oldUrl,
3304: null);
3305: return activitySet;
3306: }
3307:
3308: /**
3309: * Remove the attachment.
3310: * On return the MutableBoolean physicallyDeleted is set to true,
3311: * if the attachment file also was removed by this operation.
3312: * If the attached File still exists for any reason, physicallyDeleted
3313: * will be set to false.
3314: * Note: You can enable/disable physical deletion by setting the
3315: * environment property scarab.attachment.remove.permanent
3316: * to true/false (false is the default setting).
3317: */
3318: public ActivitySet doRemoveAttachment(ActivitySet activitySet,
3319: final MutableBoolean physicallyDeleted,
3320: final Attachment attachment, final ScarabUser user)
3321: throws TorqueException, ScarabException {
3322: boolean attachmentPhysicallyDeleted = false;
3323: final boolean physicalDeletionAllowed = Turbine
3324: .getConfiguration().getBoolean(
3325: "scarab.attachment.remove.permanent", false);
3326:
3327: if (physicalDeletionAllowed) {
3328: attachmentPhysicallyDeleted = attachment
3329: .deletePhysicalAttachment();
3330: physicallyDeleted.set(attachmentPhysicallyDeleted);
3331: }
3332:
3333: attachment.setDeleted(true);
3334: attachment.save();
3335:
3336: activitySet = attachActivitySet(activitySet, user);
3337:
3338: // Save activity record
3339: ActivityManager.createTextActivity(this , null, activitySet,
3340: ActivityType.ATTACHMENT_REMOVED, null, attachment,
3341: attachment.getFileName(), null);
3342:
3343: return activitySet;
3344: }
3345:
3346: /**
3347: * Returns users assigned to all user attributes.
3348: */
3349: public HashSet getAssociatedUsers() throws TorqueException {
3350: HashSet users = null;
3351: final Object obj = ScarabCache.get(this , GET_ASSOCIATED_USERS);
3352: if (obj == null) {
3353: final List attributeList = getModule().getUserAttributes(
3354: getIssueType(), true);
3355: final List attributeIdList = new ArrayList();
3356:
3357: for (int i = 0; i < attributeList.size(); i++) {
3358: final Attribute att = (Attribute) attributeList.get(i);
3359: final RModuleAttribute modAttr = getModule()
3360: .getRModuleAttribute(att, getIssueType());
3361: if (modAttr.getActive()) {
3362: attributeIdList.add(att.getAttributeId());
3363: }
3364: }
3365:
3366: if (!attributeIdList.isEmpty()) {
3367: users = new HashSet();
3368: final Criteria crit = new Criteria().addIn(
3369: AttributeValuePeer.ATTRIBUTE_ID,
3370: attributeIdList).add(
3371: AttributeValuePeer.DELETED, false);
3372: crit.setDistinct();
3373:
3374: final List attValues = getAttributeValues(crit);
3375: for (int i = 0; i < attValues.size(); i++) {
3376: final List item = new ArrayList(2);
3377: final AttributeValue attVal = (AttributeValue) attValues
3378: .get(i);
3379: final ScarabUser su = ScarabUserManager
3380: .getInstance(attVal.getUserId());
3381: final Attribute attr = AttributeManager
3382: .getInstance(attVal.getAttributeId());
3383: item.add(attr);
3384: item.add(su);
3385: users.add(item);
3386: }
3387: }
3388: ScarabCache.put(users, this , GET_ASSOCIATED_USERS);
3389: } else {
3390: users = (HashSet) obj;
3391: }
3392: return users;
3393: }
3394:
3395: public String toString() {
3396: String id = null;
3397: try {
3398: id = isNew() ? "New issue" : getUniqueId();
3399: } catch (Exception e) {
3400: id = "Error in getting unique id";
3401: Log.get().warn(id, e);
3402: }
3403:
3404: return super .toString() + '{' + id + '}';
3405: }
3406:
3407: /**
3408: * Returns if the issue's BlockingCondition is fulfilled.
3409: *
3410: * @return
3411: */
3412: public boolean isBlockingConditionTrue() throws TorqueException {
3413: boolean isBlockingConditionTrue = false;
3414: final List blockingConditions = this .getRModuleIssueType()
3415: .getConditions();
3416: for (Iterator it = blockingConditions.iterator(); !isBlockingConditionTrue
3417: && it.hasNext();) {
3418: final Condition cond = (Condition) it.next();
3419: final Integer conditionOptionId = cond.getOptionId();
3420: final Attribute attr = cond.getAttributeOption()
3421: .getAttribute();
3422: final AttributeValue attrVal = this .getAttributeValue(attr);
3423: if (attrVal != null) {
3424: final Integer issueOptionId = attrVal.getOptionId();
3425: if (issueOptionId != null
3426: && issueOptionId.equals(conditionOptionId)) {
3427: isBlockingConditionTrue = true;
3428: }
3429: }
3430: }
3431: return isBlockingConditionTrue;
3432: }
3433:
3434: /**
3435: * Returns if this issue is currently blocking any other.
3436: * @return
3437: * @throws TorqueException
3438: */
3439: public boolean isBlockingAnyIssue() throws TorqueException {
3440: return this .getBlockedIssues().size() > 0;
3441: }
3442:
3443: /**
3444: * An issue is blocked when it depends, via a is_blocked_by dependency,
3445: * of an issue that is currently "blocking". Whenever an issue is blocked, some transitions
3446: * might not be availaible.
3447: * @return
3448: */
3449: public boolean isBlocked() throws TorqueException {
3450: return (getBlockingIssues().size() > 0);
3451: }
3452:
3453: /**
3454: * An issue is blocked when it depends, via a is_blocked_by dependency,
3455: * of an issue that is currently "blocking". Whenever an issue is blocked, some transitions
3456: * might not be availaible.
3457: * @return
3458: */
3459: public boolean isBlockedBy(final String blockingId)
3460: throws TorqueException {
3461: final List blockingIssues = getBlockingIssues();
3462: int issueCount = getBlockingIssues().size();
3463: if (issueCount == 0) {
3464: return false;
3465: }
3466:
3467: for (int index = 0; index < issueCount; index++) {
3468: final Issue issue = (Issue) blockingIssues.get(index);
3469: final String id = issue.getUniqueId();
3470: if (id.equals(blockingId)) {
3471: return true;
3472: }
3473: }
3474: return false;
3475: }
3476:
3477: public boolean isBlocking(final String blockedId)
3478: throws TorqueException {
3479: final List blockedIssues = getBlockedIssues();
3480: final int issueCount = blockedIssues.size();
3481: if (issueCount == 0) {
3482: return false;
3483: }
3484:
3485: for (int index = 0; index < issueCount; index++) {
3486: final Issue issue = (Issue) blockedIssues.get(index);
3487: final String id = issue.getUniqueId();
3488: if (id.equals(blockedId)) {
3489: return true;
3490: }
3491: }
3492: return false;
3493: }
3494:
3495: /**
3496: * Returns a list of issues that actually "block" this issue, i.e., that
3497: * are related via a "is blocked by" dependency, and are "blocking".
3498: * @return
3499: */
3500: public List getBlockingIssues() throws TorqueException {
3501: final List blockingIssues = new ArrayList();
3502: final List prerequisiteIssues = this .getPrerequisiteIssues();
3503: for (Iterator it = prerequisiteIssues.iterator(); it.hasNext();) {
3504: final Issue is = (Issue) it.next();
3505: if (is.isBlockingConditionTrue())
3506: blockingIssues.add(is);
3507: }
3508: return blockingIssues;
3509: }
3510:
3511: /**
3512: * Returns a list of issues that are blockable by this issue, via a "is_blocked_by"
3513: * relationship.
3514: * @return
3515: * @throws TorqueException
3516: */
3517: public List getPrerequisiteIssues() throws TorqueException {
3518: final List blockingIssues = new ArrayList();
3519: final List parentIssues = this .getParents();
3520: for (Iterator it = parentIssues.iterator(); it.hasNext();) {
3521: final Depend depend = (Depend) it.next();
3522: if (depend.getDependType().getDependTypeId().equals(
3523: DependTypePeer.BLOCKING__PK)) {
3524: blockingIssues.add(IssuePeer.retrieveByPK(depend
3525: .getObservedId()));
3526: }
3527: }
3528: return blockingIssues;
3529: }
3530:
3531: /**
3532: * Returns a list of issues that are related to this issue, via a "is_related to"
3533: * relationship.
3534: * @return
3535: * @throws TorqueException
3536: */
3537: public List getRelatedIssues() throws TorqueException {
3538: return getAssociatedIssues(DependTypePeer.NON_BLOCKING__PK);
3539: }
3540:
3541: /**
3542: * Returns a list of issues that are related to this issue, via a "is_duplicate of"
3543: * relationship.
3544: * @return
3545: * @throws TorqueException
3546: */
3547: public List getDuplicateIssues() throws TorqueException {
3548: return getAssociatedIssues(DependTypePeer.DUPLICATE__PK);
3549: }
3550:
3551: /**
3552: * Returns a list of issues that are associated to this issue via
3553: * the dependandTypeId.
3554: * @param dependTypeId
3555: * @return
3556: * @throws TorqueException
3557: */
3558: private List getAssociatedIssues(final Integer dependTypeId)
3559: throws TorqueException {
3560: final List relatedIssues = new ArrayList();
3561: final List allIssues = this .getAllDependencies();
3562: for (Iterator it = allIssues.iterator(); it.hasNext();) {
3563: final Depend depend = (Depend) it.next();
3564: final DependType type = depend.getDependType();
3565: final Integer typeId = type.getDependTypeId();
3566: if (typeId.equals(dependTypeId)) {
3567: //Assume, the dependant issue is the ObservedId in the Depend
3568: Issue relatedIssue = IssuePeer.retrieveByPK(depend
3569: .getObservedId());
3570: if (relatedIssue.getIssueId().equals(this .getIssueId())) {
3571: //No, the dependant issue is the ObserverId in the depend.
3572: relatedIssue = IssuePeer.retrieveByPK(depend
3573: .getObserverId());
3574: }
3575: relatedIssues.add(relatedIssue);
3576: }
3577: }
3578: return relatedIssues;
3579: }
3580:
3581: /**
3582: * Returns a list of issues currently BLOCKED by this issue
3583: *
3584: * @return
3585: * @throws TorqueException
3586: */
3587: public List getBlockedIssues() throws TorqueException {
3588: if (this .isBlockingConditionTrue()) {
3589: return this .getDependantIssues();
3590: } else {
3591: return new ArrayList();
3592: }
3593: }
3594:
3595: /**
3596: * Returns a list of issues that might be blocked by this issue because if its
3597: * "is_blocked_by" dependency.
3598: *
3599: * @return
3600: * @throws TorqueException
3601: */
3602: public List getDependantIssues() throws TorqueException {
3603: final List dependantIssues = new ArrayList();
3604: final List childIssues = this .getChildren();
3605: for (Iterator it = childIssues.iterator(); it.hasNext();) {
3606: final Depend depend = (Depend) it.next();
3607: if (depend.getDependType().getDependTypeId().equals(
3608: DependTypePeer.BLOCKING__PK)) {
3609: dependantIssues.add(IssuePeer.retrieveByPK(depend
3610: .getObserverId()));
3611: }
3612: }
3613: return dependantIssues;
3614: }
3615:
3616: /**
3617: * This method search for the new ID of a moved issue.
3618: * @return
3619: * @throws TorqueException
3620: */
3621: public String getIssueNewId() throws TorqueException {
3622: return ActivityPeer.getNewIssueUniqueId(this);
3623: }
3624: }
|