001: package org.tigris.scarab.om;
002:
003: /* ================================================================
004: * Copyright (c) 2000-2005 CollabNet. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are
008: * met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowlegement: "This product includes
019: * software developed by Collab.Net <http://www.Collab.Net/>."
020: * Alternately, this acknowlegement may appear in the software itself, if
021: * and wherever such third-party acknowlegements normally appear.
022: *
023: * 4. The hosted project names must not be used to endorse or promote
024: * products derived from this software without prior written
025: * permission. For written permission, please contact info@collab.net.
026: *
027: * 5. Products derived from this software may not use the "Tigris" or
028: * "Scarab" names nor may "Tigris" or "Scarab" appear in their names without
029: * prior written permission of Collab.Net.
030: *
031: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
032: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
033: * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
034: * IN NO EVENT SHALL COLLAB.NET OR ITS CONTRIBUTORS BE LIABLE FOR ANY
035: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
036: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
037: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
038: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
039: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
040: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
041: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
042: *
043: * ====================================================================
044: *
045: * This software consists of voluntary contributions made by many
046: * individuals on behalf of Collab.Net.
047: */
048:
049: // JDK classes
050: import java.util.List;
051: import java.util.ArrayList;
052: import java.util.Comparator;
053: import java.util.Collections;
054: import java.util.Iterator;
055:
056: // Turbine classes
057: import org.apache.torque.TorqueException;
058: import org.apache.torque.om.Persistent;
059: import org.apache.torque.om.ObjectKey;
060: import org.apache.torque.util.Criteria;
061:
062: import org.tigris.scarab.services.cache.ScarabCache;
063:
064: /**
065: * This class deals with AttributeOptions. For more details
066: * about the implementation of this class, read the documentation
067: * about how Scarab manages Attributes.
068: * <p>
069: * The implementation of this class is "smart" in that it will only
070: * touch the database when it absolutely needs to. For example, if
071: * you create a new AttributeOption, it will not query the database
072: * for the parent/child relationships until you ask it to. It will then
073: * cache the information locally.
074: * <p>
075: * All instances of AttributeOptions are cached using the
076: * TurbineGlobalCache service.
077: *
078: * @author <a href="mailto:jon@collab.net">Jon S. Stevens</a>
079: * @version $Id: AttributeOption.java 9977 2005-12-09 00:40:59Z hair $
080: */
081: public class AttributeOption extends BaseAttributeOption implements
082: Persistent {
083: private static final Integer STATUS__CLOSED__PK = new Integer(7);
084:
085: /** the name of this class */
086: private static final String CLASS_NAME = "AttributeOption";
087:
088: /** a local Attribute reference */
089: private Attribute aAttribute;
090:
091: /**
092: * Storage for ID's of the parents of this AttributeOption
093: */
094: private List sortedParents = null;
095:
096: /**
097: * Storage for ID's of the children of this AttributeOption
098: */
099: private List sortedChildren = null;
100:
101: /**
102: * A cached String of parentIds
103: */
104: private String parentIds = null;
105:
106: /**
107: * Used in the creation of an
108: */
109: private List orderedTree = null;
110:
111: /**
112: * Must call getInstance()
113: */
114: protected AttributeOption() {
115: }
116:
117: public static Integer getStatusClosedPK() {
118: return STATUS__CLOSED__PK;
119: }
120:
121: /**
122: * Creates a key for use in caching AttributeOptions
123: */
124: static String getCacheKey(ObjectKey key) {
125: String keyString = key.getValue().toString();
126: return new StringBuffer(CLASS_NAME.length()
127: + keyString.length()).append(CLASS_NAME).append(
128: keyString).toString();
129: }
130:
131: /**
132: * A comparator for this class. Compares on OPTION_NAME.
133: */
134: private static final Comparator COMPARATOR = new Comparator() {
135: public int compare(Object obj1, Object obj2) {
136: int result = 1;
137: AttributeOption opt1 = (AttributeOption) obj1;
138: AttributeOption opt2 = (AttributeOption) obj2;
139: if (opt1.getName().equals(opt2.getName())) {
140: result = 0;
141: } else {
142: result = -1;
143: }
144: return result;
145: }
146: };
147:
148: /**
149: * Compares numeric value and in cases where the numeric value
150: * is the same it compares the display values.
151: */
152: public static Comparator getComparator() {
153: return COMPARATOR;
154: }
155:
156: /**
157: * Get the Attribute associated with this Option
158: */
159: public Attribute getAttribute() throws TorqueException {
160: if (aAttribute == null && (getAttributeId() != null)) {
161: aAttribute = AttributeManager.getInstance(getAttributeId());
162:
163: // make sure the parent attribute is in synch.
164: super .setAttribute(aAttribute);
165: }
166: return aAttribute;
167: }
168:
169: /**
170: * Set the Attribute associated with this Option
171: */
172: public void setAttribute(Attribute v) throws TorqueException {
173: aAttribute = v;
174: super .setAttribute(v);
175: }
176:
177: /**
178: * Returns a list of AttributeOptions which are ancestors
179: * of this AttributeOption. An Ancestor is the parent tree
180: * going up from this AO. The order is bottom up.
181: */
182: public List getAncestors() throws TorqueException {
183: List options = new ArrayList();
184: addAncestors(options);
185: return options;
186: }
187:
188: /**
189: * Recursive method that loops over the ancestors
190: */
191: private void addAncestors(List ancestors) throws TorqueException {
192: List parents = getParents();
193: for (int i = parents.size() - 1; i >= 0; i--) {
194: AttributeOption parent = (AttributeOption) parents.get(i);
195: if (!ancestors.contains(parent)) {
196: ancestors.add(parent);
197: parent.addAncestors(ancestors);
198: }
199: }
200: }
201:
202: /**
203: * Returns a list of AttributeOptions which are descendants
204: * of this AttributeOption. The descendants is the child tree
205: * going down from this AO. The order is bottom up.
206: */
207: public List getDescendants() throws TorqueException {
208: List options = new ArrayList();
209: addDescendants(options);
210: return options;
211: }
212:
213: /**
214: * Recursive method that loops over the descendants
215: */
216: private void addDescendants(List descendants)
217: throws TorqueException {
218: List children = getChildren();
219: for (int i = children.size() - 1; i >= 0; i--) {
220: AttributeOption child = (AttributeOption) children.get(i);
221: descendants.add(child);
222: child.addDescendants(descendants);
223: }
224: }
225:
226: /**
227: * Returns a list of AttributeOption's which are children
228: * of this AttributeOption.
229: */
230: public List getChildren() throws TorqueException {
231: if (sortedChildren == null) {
232: buildChildren();
233: }
234: return sortedChildren;
235: }
236:
237: /**
238: * Returns a list of AttributeOption's which are parents
239: * of this AttributeOption.
240: */
241: public List getParents() throws TorqueException {
242: buildParents();
243: return sortedParents;
244: }
245:
246: /**
247: * Builds a list of AttributeOption's which are children
248: * of this AttributeOption.
249: */
250: private synchronized void buildChildren() throws TorqueException {
251: Criteria crit = new Criteria().add(
252: ROptionOptionPeer.RELATIONSHIP_ID,
253: OptionRelationship.PARENT_CHILD).add(
254: ROptionOptionPeer.OPTION1_ID, super .getOptionId());
255:
256: List relations = ROptionOptionPeer.doSelect(crit);
257: sortedChildren = new ArrayList(relations.size());
258: for (int i = 0; i < relations.size(); i++) {
259: ROptionOption relation = (ROptionOption) relations.get(i);
260: Integer key = relation.getOption2Id();
261: if (key != null) {
262: sortedChildren.add(relation.getOption2Option());
263: }
264: }
265: sortChildren();
266: }
267:
268: /**
269: * Builds a list of AttributeOption's which are parents
270: * of this AttributeOption.
271: */
272: private synchronized void buildParents() throws TorqueException {
273: Criteria crit = new Criteria().add(
274: ROptionOptionPeer.RELATIONSHIP_ID,
275: OptionRelationship.PARENT_CHILD).add(
276: ROptionOptionPeer.OPTION2_ID, super .getOptionId());
277:
278: List relations = ROptionOptionPeer.doSelect(crit);
279: sortedParents = new ArrayList(relations.size());
280: for (int i = 0; i < relations.size(); i++) {
281: ROptionOption relation = (ROptionOption) relations.get(i);
282: Integer key = relation.getOption1Id();
283: if (key != null) {
284: sortedParents.add(relation.getOption1Option());
285: }
286: }
287: sortParents();
288: }
289:
290: /**
291: * re-sorts the Parents
292: */
293: public void sortParents() {
294: synchronized (this ) {
295: Collections.sort(sortedParents, getComparator());
296: }
297: }
298:
299: /**
300: * re-sorts the children
301: */
302: public void sortChildren() {
303: synchronized (this ) {
304: Collections.sort(sortedChildren, getComparator());
305: }
306: }
307:
308: /**
309: * Checks to see if this Attribute option is a child of
310: * the passed in AttributeOption parent.
311: */
312: public boolean isChildOf(AttributeOption parent)
313: throws TorqueException {
314: return getParents().contains(parent);
315: }
316:
317: /**
318: * Checks to see if this Attribute option is a parent of
319: * the passed in AttributeOption child.
320: */
321: public boolean isParentOf(AttributeOption child)
322: throws TorqueException {
323: return getChildren().contains(child);
324: }
325:
326: /**
327: * Does this AttributeOption have children?
328: */
329: public boolean hasChildren() throws TorqueException {
330: return getChildren().size() > 0 ? true : false;
331: }
332:
333: /**
334: * Does this AttributeOption have parents?
335: */
336: public boolean hasParents() throws TorqueException {
337: return getParents().size() > 0 ? true : false;
338: }
339:
340: /**
341: * Returns direct parent of this child.
342: */
343: public AttributeOption getParent() throws TorqueException {
344: AttributeOption parent = null;
345: Criteria crit = new Criteria().add(
346: ROptionOptionPeer.RELATIONSHIP_ID,
347: OptionRelationship.PARENT_CHILD).add(
348: ROptionOptionPeer.OPTION2_ID, super .getOptionId());
349:
350: List results = ROptionOptionPeer.doSelect(crit);
351: if (results.size() == 1) {
352: ROptionOption roo = (ROptionOption) results.get(0);
353: parent = roo.getOption1Option();
354: }
355: return parent;
356: }
357:
358: /**
359: * Delete mappings with all modules and issue types.
360: */
361: public void deleteModuleMappings() throws TorqueException {
362: Criteria crit = new Criteria();
363: crit.add(RModuleOptionPeer.OPTION_ID, getOptionId());
364: RModuleOptionPeer.doDelete(crit);
365: ScarabCache.clear();
366: }
367:
368: /**
369: * Delete mappings with global issue types.
370: */
371: public void deleteIssueTypeMappings() throws TorqueException {
372: Criteria crit = new Criteria();
373: crit.add(RIssueTypeOptionPeer.OPTION_ID, getOptionId());
374: RIssueTypeOptionPeer.doDelete(crit);
375: ScarabCache.clear();
376: }
377:
378: /**
379: * Add a list of Children to this AttributeOption
380: * @throw Exception if child is already a child
381: public void addChildren(List children)
382: throws TorqueException
383: {
384: if (children == null)
385: {
386: throw new Exception ("AttributeOption.addChildren() -> no children to add");
387: }
388: else if (children.size() == 0)
389: {
390: return;
391: }
392: synchronized (this)
393: {
394: Iterator itr = children.iterator();
395: while (itr.hasNext())
396: {
397: addChild((AttributeOption)itr.next());
398: }
399: }
400: }
401: */
402:
403: /**
404: * Add a Child to this AttributeOption
405: * @throw Exception if child is already a child
406: public void addChild(AttributeOption child)
407: throws TorqueException
408: {
409: if (child.isChildOf(this))
410: {
411: throw new Exception (
412: "The child: " + child.getName() +
413: " is already a child of: " + this.getName());
414: }
415:
416: // make sure that we exist in the database
417: this.save();
418: // make sure that the child exists in the database
419: child.save();
420:
421: // create the mapping
422: Criteria crit = new Criteria();
423: crit.add (ROptionOptionPeer.OPTION1_ID, this.getOptionId());
424: crit.add (ROptionOptionPeer.OPTION2_ID, child.getOptionId());
425: ROptionOptionPeer.doInsert(crit);
426:
427: synchronized (this)
428: {
429: getChildren().add(child);
430: }
431: synchronized (child)
432: {
433: child.getParents().add(this);
434: }
435: sortChildren();
436: }
437: */
438:
439: /**
440: * Add a list of Parents to this AttributeOption
441: * @throw Exception if parents is already a parents
442: public void addParents(List parents)
443: throws TorqueException
444: {
445: if (parents == null)
446: {
447: throw new Exception ("AttributeOption.addParents() -> no parents to add");
448: }
449: else if (parents.size() == 0)
450: {
451: return;
452: }
453: synchronized (this)
454: {
455: Iterator itr = parents.iterator();
456: int counter = 1;
457: while (itr.hasNext())
458: {
459: addParent((AttributeOption)itr.next(), counter++);
460: }
461: }
462: }
463: */
464:
465: /*
466: public void addParent(AttributeOption parent)
467: {
468: ROptionOption roo = ROptionOption.getInstance();
469: roo.setOption1Id(parent.getOptionId());
470: roo.setOption2Id(this.getOptionId());
471: roo.setPreferredOrder(this.getPreferredOrder());
472: addParent(roo);
473: }
474: */
475: /**
476: * Add a Parent to this AttributeOption
477: * @throw Exception if parent is already a parent
478: public void addParent(ROptionOption parent)
479: throws TorqueException
480: {
481: if (parent.isParentOf(this))
482: {
483: throw new Exception (
484: "The parent: " + parent.getOption1Option().getName() +
485: " is already a parent of: " + this.getName());
486: }
487:
488: // make sure that we exist in the database
489: this.save();
490:
491: // create the mapping
492: Criteria crit = new Criteria();
493: crit.add (ROptionOptionPeer.OPTION1_ID, parent.getOption1Id());
494: crit.add (ROptionOptionPeer.OPTION2_ID, this.getOptionId());
495: crit.add (ROptionOptionPeer.RELATIONSHIP_ID, OptionRelationship.PARENT_CHILD);
496: crit.add (ROptionOptionPeer.PREFERRED_ORDER, preferredOrder);
497: ROptionOptionPeer.doInsert(crit);
498:
499: synchronized (this)
500: {
501: getParents().add(parent.getOption1Option());
502: }
503: synchronized (parent)
504: {
505: parent.getChildren().add(this);
506: }
507: sortParents();
508: clearParentIds();
509: }
510: */
511:
512: /**
513: * Delete all parents. This is usually not a good idea to
514: * expose to the general public and is therefore a private
515: * method.
516: private void deleteParents()
517: throws TorqueException
518: {
519: Criteria crit = new Criteria();
520: crit.add (ROptionOptionPeer.OPTION2_ID, this.getOptionId());
521: ROptionOptionPeer.doDelete(crit);
522:
523: synchronized (this)
524: {
525: getParents().clear();
526: }
527: clearParentIds();
528: }
529: */
530:
531: /**
532: * Delete a specific parent
533: public void deleteParent(AttributeOption parent)
534: throws TorqueException
535: {
536: if (!isChildOf(parent))
537: {
538: throw new Exception (
539: parent.getName() + " is not a parent of: " +
540: this.getName());
541: }
542: Criteria crit = new Criteria();
543: crit.add (ROptionOptionPeer.OPTION1_ID, parent.getOptionId());
544: crit.add (ROptionOptionPeer.OPTION2_ID, this.getOptionId());
545: ROptionOptionPeer.doDelete(crit);
546:
547: synchronized (this)
548: {
549: getParents().remove(parent);
550: }
551: synchronized (parent)
552: {
553: parent.getChildren().remove(this);
554: }
555: sortParents();
556: clearParentIds();
557: }
558: */
559:
560: /**
561: * Delete all children
562: public void deleteChildren()
563: throws TorqueException
564: {
565: Criteria crit = new Criteria();
566: crit.add (ROptionOptionPeer.OPTION1_ID, this.getOptionId());
567: ROptionOptionPeer.doDelete(crit);
568:
569: synchronized (this)
570: {
571: getChildren().clear();
572: }
573: }
574: */
575:
576: /**
577: * Delete a specific child
578: public void deleteChild(AttributeOption child)
579: throws TorqueException
580: {
581: if (!isParentOf(child))
582: {
583: throw new Exception (
584: child.getName() + " is not a child of: " +
585: this.getName());
586: }
587: Criteria crit = new Criteria();
588: crit.add (ROptionOptionPeer.OPTION1_ID, this.getOptionId());
589: crit.add (ROptionOptionPeer.OPTION2_ID, child.getOptionId());
590: ROptionOptionPeer.doDelete(crit);
591:
592: synchronized (this)
593: {
594: getChildren().remove(child);
595: }
596: synchronized (child)
597: {
598: child.getParents().remove(this);
599: }
600: sortChildren();
601: }
602: */
603:
604: /**
605: * Get a CSV list of Parent id's associated with this
606: * Attribute Option.
607: public String getParentIds()
608: throws TorqueException
609: {
610: if (parentIds == null)
611: {
612: // special case of no parents == 0
613: if (getParents().size() == 0)
614: {
615: parentIds = "0";
616: }
617: else
618: {
619: StringBuffer sb = new StringBuffer();
620: synchronized (this)
621: {
622: boolean firstTime = true;
623: Iterator itr = getParents().iterator();
624: while (itr.hasNext())
625: {
626: if (!firstTime)
627: {
628: sb.append (",");
629: }
630: AttributeOption ao = (AttributeOption)itr.next();
631: sb.append(ao.getOptionId());
632: firstTime = false;
633: }
634: }
635: parentIds = sb.toString();
636: }
637: }
638: return parentIds;
639: }
640: */
641:
642: /**
643: * Set a CSV list of Parent id's associated with this
644: * Attribute Option.
645: public void setParentIds(String ids)
646: throws TorqueException
647: {
648: if (ids == null || ids.length() == 0)
649: {
650: throw new Exception ("Need to specify a list of parent ids!");
651: }
652: StringTokenizer st = new StringTokenizer(ids, ",");
653: int tokenCount = st.countTokens();
654: List options = new ArrayList(tokenCount+1);
655: if (tokenCount == 0)
656: {
657: AttributeOption ao =
658: AttributeOption.getInstance((ObjectKey)new NumberKey(0));
659: if (!ao.isParentOf(this))
660: {
661: options.add(ao);
662: }
663: }
664: else
665: {
666: while (st.hasMoreTokens())
667: {
668: String id = st.nextToken();
669: AttributeOption ao =
670: AttributeOption.getInstance((ObjectKey)new NumberKey(id));
671: if (!ao.isParentOf(this))
672: {
673: options.add(ao);
674: }
675: }
676: }
677: deleteParents();
678: addParents(options);
679: }
680: */
681:
682: /**
683: * Clears out the lists of parent ids
684: */
685: private void clearParentIds() {
686: parentIds = null;
687: }
688:
689: /**
690: * A String representation of this object.
691: */
692: public String toString() {
693: try {
694: return "Id: " + getOptionId() + " Name: " + getName();// + " ParentIds: " + getParentIds();
695: } catch (Exception e) {
696: e.printStackTrace();
697: }
698: return null;
699: }
700:
701: /*
702: public List getOrderedChildTree(AttributeOption option)
703: throws TorqueException
704: {
705: walkTree(option);
706: ArrayList list = new ArrayList();
707: for (int j=orderedTree.size()-1; j>=0; j--)
708: {
709: AttributeOption ao = (AttributeOption) orderedTree.get(j);
710: System.out.println (
711: getTabs(ao.getParents().size()) +
712: ao.getOptionId() + " : '" +
713: ao.getParentIds() + "' : " +
714: ao.getWeight() + " : " +
715: ao.getParents().size() + " : " +
716: ao.getName());
717: list.add(ao);
718: }
719: return list;
720: }
721:
722: private void walkTree(AttributeOption option)
723: throws TorqueException
724: {
725: List children = option.getChildren();
726: for (int j=children.size()-1; j>=0; j--)
727: {
728: AttributeOption ao = (AttributeOption) children.get(j);
729: if (ao.hasChildren())
730: {
731: walkTree(ao);
732: }
733: orderedTree.add(ao);
734: }
735: }
736:
737: private String getTabs(int level)
738: {
739: StringBuffer sb = new StringBuffer();
740: for (int i = 0; i<level; i++)
741: {
742: sb.append("\t");
743: }
744: return sb.toString();
745: }
746: */
747:
748: /**
749: * Get all the global issue type mappings for this attribute option.
750: */
751: private List getIssueTypesWithMappings() throws TorqueException {
752: Criteria crit = new Criteria();
753: crit.add(RIssueTypeOptionPeer.OPTION_ID, getOptionId());
754: crit.addJoin(RIssueTypeOptionPeer.ISSUE_TYPE_ID,
755: IssueTypePeer.ISSUE_TYPE_ID);
756: return IssueTypePeer.doSelect(crit);
757: }
758:
759: /**
760: * Checks if this attribute option is associated with atleast one of the
761: * global issue types that is system defined.
762: *
763: * @return True if it is associated with a System defined
764: * global Issue Type. False otherwise.
765: */
766:
767: public boolean isSystemDefined() throws TorqueException {
768: boolean systemDefined = false;
769: List issueTypeList = getIssueTypesWithMappings();
770: for (Iterator i = issueTypeList.iterator(); i.hasNext()
771: && !systemDefined;) {
772: systemDefined = ((IssueType) i.next()).isSystemDefined();
773: }
774: return systemDefined;
775: }
776: }
|