0001: /*
0002: * Copyright 2001 Sun Microsystems, Inc. All rights reserved.
0003: * PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms.
0004: */
0005:
0006: package com.sun.portal.search.admin.model;
0007:
0008: import java.io.*;
0009: import java.util.*;
0010: import java.util.logging.Logger;
0011: import java.util.logging.Level;
0012:
0013: import com.iplanet.jato.model.*;
0014: import com.sun.portal.search.rdm.RDMClassification;
0015: import com.sun.portal.search.rdm.RDMTaxonomy;
0016: import com.sun.portal.search.soif.*;
0017:
0018: import com.sun.portal.search.admin.CSConfig;
0019: import com.sun.portal.search.admin.util.DBUtil;
0020: import com.sun.portal.search.util.SearchConfig;
0021: import com.sun.portal.search.admin.resources.SearchResource;
0022: import com.sun.portal.log.common.PortalLogger;
0023:
0024: public class TaxonomyTreeModel extends TreeModelBase implements
0025: Serializable {
0026: public Locale userLocale = Locale.getDefault();
0027:
0028: // Create a Logger for this class
0029: private static Logger debugLogger = PortalLogger
0030: .getLogger(TaxonomyTreeModel.class);
0031:
0032: /** Creates new TaxonomyTreeModel */
0033: public TaxonomyTreeModel() {
0034: super ();
0035: load();
0036: // dump(root);
0037: }
0038:
0039: /**
0040: *
0041: *
0042: */
0043: public String getName() {
0044: return name;
0045: }
0046:
0047: /**
0048: *
0049: *
0050: */
0051: public void setName(String value) {
0052: name = value;
0053: }
0054:
0055: /**
0056: * In addition to making the root node the current node, this method
0057: * should set the node level to <code>ROOT_NODE_LEVEL</code> and call
0058: * <code>setIterationComplete(false)</code>
0059: *
0060: */
0061: public void root() throws ModelControlException {
0062: try {
0063: setCurrentNode(root);
0064: setNodeLevel(ROOT_NODE_LEVEL);
0065: setIterationComplete(false);
0066: } catch (Exception e) {
0067: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0068: .getMessage());
0069: throw new ModelControlException(
0070: getLocalizedString("category.edit.error.default"));
0071: }
0072: }
0073:
0074: /**
0075: * In addition to making the root node the current node, this method
0076: * should set the node level to <code>ROOT_NODE_LEVEL</code> and call
0077: * <code>setIterationComplete(false)</code>
0078: */
0079: public void beforeRoot() throws ModelControlException {
0080: try {
0081: setNodeLevel(UNDEFINED_NODE_LEVEL);
0082: setIterationComplete(false);
0083: } catch (Exception e) {
0084: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0085: .getMessage());
0086: throw new ModelControlException(
0087: "TaxonomyTreeModel exception e=" + e.getMessage());
0088: }
0089: }
0090:
0091: /*
0092: *
0093: */
0094: public String getNodeDesc(String nodeId)
0095: throws ModelControlException {
0096: if (nodeId != null) {
0097: RDMClassification node = (RDMClassification) tax
0098: .find(nodeId);
0099: try {
0100: if (node != null) {
0101: if (node == root) {
0102: return tax.getDescription();
0103: } else {
0104: return node.getDescription();
0105: }
0106: } else {
0107: debugLogger.log(Level.FINER, "PSSH_CSPSAM0057",
0108: nodeId);
0109: return null;
0110: }
0111: } catch (Exception e) {
0112: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0113: new String[] { nodeId, e.getMessage() });
0114: throw new ModelControlException(
0115: "TaxonomyTreeModel exception e="
0116: + e.getMessage());
0117: }
0118: } else {
0119: return null;
0120: }
0121: }
0122:
0123: /*
0124: *
0125: */
0126: public String getNodeRule(String nodeId)
0127: throws ModelControlException {
0128: if (nodeId != null) {
0129: RDMClassification node = (RDMClassification) tax
0130: .find(nodeId);
0131: try {
0132: if (node != null) {
0133: if (node == root) {
0134: return null;
0135: } else {
0136: return node.getMatchingRule();
0137: }
0138: } else {
0139: debugLogger.log(Level.FINER, "PSSH_CSPSAM0057",
0140: nodeId);
0141: return null;
0142: }
0143: } catch (Exception e) {
0144: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0145: new String[] { nodeId, e.getMessage() });
0146: throw new ModelControlException(
0147: "TaxonomyTreeModel exception e="
0148: + e.getMessage());
0149: }
0150: } else {
0151: return null;
0152: }
0153: }
0154:
0155: /*
0156: *
0157: */
0158: public String getNodeName(String nodeId)
0159: throws ModelControlException {
0160: if (nodeId != null) {
0161: RDMClassification node = (RDMClassification) tax
0162: .find(nodeId);
0163: try {
0164: if (node != null) {
0165: // The root node correspond actually to the Taxonomy
0166: if (node.getParent() == null) {
0167: // The node is the root node.
0168: // the name shown should be the one of the Taxonomy not the ROOT
0169: return tax.getId();
0170: } else {
0171: // this is a classification node
0172: return node.getId();
0173: }
0174: } else {
0175: debugLogger.log(Level.FINER, "PSSH_CSPSAM0057",
0176: nodeId);
0177: return null;
0178: }
0179: } catch (Exception e) {
0180: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0181: new String[] { nodeId, e.getMessage() });
0182: throw new ModelControlException(
0183: "TaxonomyTreeModel exception e="
0184: + e.getMessage());
0185: }
0186: } else {
0187: return null;
0188: }
0189: }
0190:
0191: /*
0192: *
0193: */
0194: public String getNodeName() throws ModelControlException {
0195: RDMClassification node = (RDMClassification) getCurrentNode();
0196: try {
0197: if (node != null) {
0198: // The root node correspond actually to the Taxonomy
0199: if (node.getParent() == null) {
0200: // The node is the root node.
0201: // the name shown should be the one of the Taxonomy not the ROOT
0202: return tax.getId();
0203: } else {
0204: // this is a classification node
0205: return node.getId();
0206: }
0207: } else {
0208: return null;
0209: }
0210: } catch (Exception e) {
0211: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0212: .getMessage());
0213: throw new ModelControlException(
0214: "TaxonomyTreeModel exception e=" + e.getMessage());
0215: }
0216: }
0217:
0218: /**
0219: *
0220: *
0221: */
0222: public String getNodeType() throws ModelControlException {
0223: try {
0224: String ret = null;
0225: if (isParentNode()) {
0226: ret = "Parent";
0227: } else {
0228: ret = "Leaf";
0229: }
0230: return ret;
0231: } catch (Exception e) {
0232: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0233: .getMessage());
0234: throw new ModelControlException(
0235: "TaxonomyTreeModel exception e=" + e.getMessage());
0236: }
0237: }
0238:
0239: /**
0240: *
0241: *
0242: */
0243: public RDMClassification getNode(String nodeID) {
0244: try {
0245: RDMClassification rc = tax.find(nodeID);
0246: return rc;
0247: } catch (Exception e) {
0248: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0249: new String[] { nodeID, e.getMessage() });
0250: return null;
0251: }
0252: }
0253:
0254: /**
0255: *
0256: *
0257: */
0258: public Object getCurrentNode() {
0259: try {
0260: Object obj = super .getCurrentNode();
0261: return obj;
0262: } catch (Exception e) {
0263: return null;
0264: }
0265:
0266: }
0267:
0268: /**
0269: *
0270: *
0271: */
0272: public void setCurrentNode(String nodeID)
0273: throws ModelControlException {
0274: try {
0275: setCurrentNode(getNode(nodeID));
0276: } catch (Exception e) {
0277: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0278: new String[] { nodeID, e.getMessage() });
0279: throw new ModelControlException(
0280: "TaxonomyTreeModel exception e=" + e.getMessage());
0281: }
0282: }
0283:
0284: /**
0285: *
0286: * this is the actual method that fills in the name of the node
0287: */
0288: public String getNodeID() {
0289: RDMClassification node = (RDMClassification) getCurrentNode();
0290: try {
0291: if (node != null) {
0292: return hashNodeId(node.getId());
0293: } else {
0294: return null;
0295: }
0296: } catch (Exception e) {
0297: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0298: .getMessage());
0299: return null;
0300: }
0301: }
0302:
0303: /**
0304: *
0305: */
0306: public boolean isParentNode(String nodeId) {
0307: if (nodeId != null) {
0308: try {
0309: RDMClassification node = (RDMClassification) tax
0310: .find(nodeId);
0311: if (node != null) {
0312: return node.nChildren() > 0;
0313: } else {
0314: return false;
0315: }
0316: } catch (Exception e) {
0317: return false;
0318: }
0319: } else {
0320: return false;
0321: }
0322: }
0323:
0324: /**
0325: *
0326: *
0327: */
0328: public boolean isParentNode() {
0329: try {
0330: RDMClassification node = (RDMClassification) getCurrentNode();
0331: if (node != null) {
0332: return node.nChildren() > 0;
0333: } else {
0334: return false;
0335: }
0336: } catch (Exception e) {
0337: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0338: .getMessage());
0339: return false;
0340: }
0341: }
0342:
0343: /**
0344: *
0345: *
0346: */
0347: public boolean isChildNode() {
0348: RDMClassification node = (RDMClassification) getCurrentNode();
0349: try {
0350: if (node != null) {
0351: if (node.getParent() != null) {
0352: return true;
0353: } else {
0354: return false;
0355: }
0356: } else {
0357: return false;
0358: }
0359: } catch (Exception e) {
0360: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0361: .getMessage());
0362: return false;
0363:
0364: }
0365:
0366: }
0367:
0368: /*
0369: * check if a child is a parent's last child
0370: */
0371: public boolean isLastChild(RDMClassification parent,
0372: RDMClassification child) throws ModelControlException {
0373: try {
0374: int lastChild = parent.nChildren() - 1;
0375: if (parent.nthChild(lastChild) == child) {
0376: return true;
0377: } else {
0378: return false;
0379: }
0380: } catch (Exception e) {
0381: debugLogger.log(Level.INFO, "PSSH_CSPSAM0060",
0382: new String[] { parent.toString(), child.toString(),
0383: e.getMessage() });
0384: throw new ModelControlException(
0385: "failed to verify if currentNode is last child");
0386: }
0387: }
0388:
0389: /*
0390: * check if current Node is the last child
0391: */
0392: public boolean isLastChild() throws ModelControlException {
0393: return isLastChild(((RDMClassification) getCurrentNode())
0394: .getParent(), (RDMClassification) getCurrentNode());
0395: }
0396:
0397: /**
0398: * The implementation of this method should call <code>
0399: * incrementNodeLevel()</code>
0400: *
0401: */
0402: public boolean firstChild() throws ModelControlException {
0403: try {
0404: RDMClassification node = (RDMClassification) getCurrentNode();
0405: if (node != null && isParentNode()) {
0406: setCurrentNode(node.getChildren().get(0));
0407: incrementNodeLevel();
0408: return true;
0409: } else {
0410: return false;
0411: }
0412: } catch (Exception e) {
0413: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0414: .getMessage());
0415: throw new ModelControlException(
0416: "TaxonomyTreeModel exception e=" + e.getMessage());
0417:
0418: }
0419: }
0420:
0421: /*
0422: * given a RDMClassification return it's children
0423: */
0424: public List getChildren(String nodeId) {
0425: if (nodeId != null) {
0426: try {
0427: RDMClassification parent = tax.find(nodeId);
0428: return parent.getChildren();
0429: } catch (Exception e) {
0430: debugLogger.log(Level.INFO, "PSSH_CSPSAM0062", e
0431: .getMessage());
0432: return null;
0433: }
0434: } else {
0435: return null;
0436: }
0437: }
0438:
0439: /**
0440: * The implementation of this method should call <code>
0441: * decrementNodeLevel()</code>
0442: *
0443: */
0444: public boolean parent() throws ModelControlException {
0445: try {
0446: RDMClassification node = (RDMClassification) getCurrentNode();
0447: if (node != null && isChildNode()) {
0448: setCurrentNode(node.getParent());
0449: decrementNodeLevel();
0450: return true;
0451: } else {
0452: return false;
0453: }
0454: } catch (Exception e) {
0455: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0456: .getMessage());
0457: throw new ModelControlException(
0458: "TaxonomyTreeModel exception e=" + e.getMessage());
0459:
0460: }
0461: }
0462:
0463: /**
0464: * The implementation of this method should leave the node level
0465: * unchanged
0466: *
0467: */
0468: public boolean nextSibling() throws ModelControlException {
0469: try {
0470: RDMClassification node = (RDMClassification) getCurrentNode();
0471: if (node != null) {
0472: RDMClassification parent = node.getParent();
0473: if (parent != null) {
0474: int index = parent.getChildren().indexOf(node);
0475: index++;
0476: if (index < parent.nChildren()) {
0477: setCurrentNode(parent.nthChild(index));
0478: return true;
0479: }
0480: }
0481: }
0482: return false;
0483: } catch (Exception e) {
0484: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
0485: .getMessage());
0486: throw new ModelControlException(
0487: "TaxonomyTreeModel exception e=" + e.getMessage());
0488:
0489: }
0490:
0491: }
0492:
0493: /**
0494: * Returns a named value from this model
0495: *
0496: * @param name
0497: * The name of the value to return
0498: * @return The specified value. If the model has multiple values for the
0499: * specified name, the first value is returned. If there is no value
0500: * for the specified name, this method returns null.
0501: */
0502: public Object getValue(String name) {
0503: try {
0504: Object[] values = getValues(name);
0505: if (values == null || values.length == 0) {
0506: return null;
0507: } else {
0508: return values[0];
0509: }
0510: } catch (Exception e) {
0511: debugLogger.log(Level.INFO, "PSSH_CSPSAM0059",
0512: new String[] { name, e.getMessage() });
0513: return null;
0514:
0515: }
0516: }
0517:
0518: /**
0519: * Sets a named value in this model. This method overwrites any current
0520: * value or values. If multiple values were present previously, they
0521: * are all discarded.
0522: *
0523: * @param name
0524: * The name of the value to set
0525: * @param value
0526: * The value to set in this model
0527: */
0528: public void setValue(String name, Object value)
0529: throws ValidationException {
0530: try {
0531: setValues(name, new Object[] { value });
0532: } catch (Exception e) {
0533: debugLogger.log(Level.INFO, "PSSH_CSPSAM0060",
0534: new Object[] { name, value, e.getMessage() });
0535: throw new ValidationException(
0536: "TaxonomyTreeModel exception e=" + e.getMessage());
0537: }
0538: }
0539:
0540: /**
0541: * Returns a named set of values from this model
0542: *
0543: * @param name
0544: * The name of the value set to return
0545: * @return The specified set of values. If there is no value for the
0546: * specified name, this method returns an array of zero length.
0547: */
0548: public Object[] getValues(String name) {
0549: try {
0550: RDMClassification node = (RDMClassification) getCurrentNode();
0551: if (node != null) {
0552: String value = null;
0553: if (name.compareTo(this .FIELD_NAME) == 0) {
0554: if (node.getParent() == null) {
0555: // this is the root node representing the taxonomy itself
0556: // the FIELD_NAME must me the one of the taxonomy's
0557: value = tax.getId();
0558: } else {
0559: // this is a classification node
0560: value = node.getSubcategory();
0561: }
0562: } else {
0563: value = node.getSOIF().getValue(name);
0564: }
0565: return new Object[] { value };
0566: } else {
0567: return null;
0568: }
0569: } catch (Exception e) {
0570: debugLogger.log(Level.INFO, "PSSH_CSPSAM0059",
0571: new String[] { name, e.getMessage() });
0572: throw new ValidationException(
0573: "TaxonomyTreeModel exception e=" + e.getMessage());
0574: }
0575:
0576: }
0577:
0578: /**
0579: * Sets a named set of values in this model
0580: *
0581: * @param name
0582: * The name of the value set to set in the model
0583: * @param values
0584: * The set of values to set in this model
0585: */
0586: public void setValues(String name, Object[] values)
0587: throws ValidationException {
0588: try {
0589: RDMClassification node = (RDMClassification) getCurrentNode();
0590: if (node != null) {
0591: if (node.getParent() == null) {
0592: // Root node this is a representation of the taxonomy not
0593: // a classification node
0594: tax.getSOIF().replace(name, (String) values[0]);
0595: } else {
0596: node.getSOIF().replace(name, (String) values[0]);
0597: }
0598: }
0599: } catch (Exception e) {
0600: debugLogger.log(Level.INFO, "PSSH_CSPSAM0059",
0601: new String[] { name, e.getMessage() });
0602: throw new ValidationException(
0603: "TaxonomyTreeModel exception e=" + e.getMessage());
0604: }
0605: }
0606:
0607: /**
0608: * Delete a node and its children
0609: *
0610: * @param id
0611: * The id of the RDMClassification
0612: */
0613: public void deleteNode(String nodeID) throws ModelControlException {
0614: try {
0615: RDMClassification node = tax.find(nodeID);
0616: if (node != null) {
0617: RDMClassification parent = node.getParent();
0618: if (parent != null) {
0619: int nodeNbDescendant = node.getNumDescendant();
0620: int parentNbChildren = parent.nChildren();
0621: int childIndex = parent.getChildren().indexOf(node);
0622: parent.getChildren().remove(childIndex);
0623: Collections.sort(parent.getChildren());
0624:
0625: // updating ascendants' num of descendants
0626: int i = 0;
0627: for (RDMClassification asc = parent; asc != null;) {
0628: int cur = asc.getNumDescendant();
0629: asc.setNumDescendant(cur
0630: - (nodeNbDescendant + 1));
0631: asc = asc.getParent();
0632: }
0633: } else {
0634: // this is the ROOT selected
0635: // create an empty Taxonomy named Search out of the i18n property file
0636: // this is the root => no parent
0637: try {
0638: tax = new RDMTaxonomy(
0639: getLocalizedString("category.edit.default_taxonomy_name"));
0640: root = tax.find(RDMTaxonomy.RDM_TAXONOMY_ROOT);
0641: //dump(root);
0642: } catch (Exception e) {
0643: // throw exception
0644: throw new ModelControlException(
0645: getLocalizedString("category.edit.error.delete")
0646: + " "
0647: + nodeID
0648: + " "
0649: + getLocalizedString("category.edit.error.delete_not_found"));
0650: }
0651: }
0652: setModifiedState(true);
0653: } else {
0654: debugLogger.log(Level.FINER, "PSSH_CSPSAM0057", nodeID);
0655: throw new ModelControlException(
0656: getLocalizedString("category.edit.error.delete_default"));
0657: }
0658: } catch (NullPointerException e) {
0659: // throw exception
0660: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0661: new String[] { nodeID, e.getMessage() });
0662: throw new ModelControlException(
0663: getLocalizedString("category.edit.error.delete_default"));
0664: }
0665: }
0666:
0667: /*
0668: * return the number of direct child.
0669: * returns -1 if error
0670: */
0671: public int nChildren(String nodeId) {
0672: try {
0673: RDMClassification node = tax.find(nodeId);
0674: return node.nChildren();
0675: } catch (Exception e) {
0676: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0677: new String[] { nodeId, e.getMessage() });
0678: return -1;
0679: }
0680: }
0681:
0682: /*
0683: * return the number of nodes in descendance
0684: * returns -1 if error
0685: */
0686: public int getNumDescendant(String nodeId) {
0687: try {
0688: RDMClassification node = tax.find(nodeId);
0689: return node.getNumDescendant();
0690: } catch (Exception e) {
0691: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
0692: new String[] { nodeId, e.getMessage() });
0693: return -1;
0694: }
0695: }
0696:
0697: /**
0698: * Insert a child node
0699: *
0700: * @param id
0701: * The id of the RDMClassification
0702: */
0703: public void insertChildNode(String parentId, String childId,
0704: String childDesc, String childRule)
0705: throws ModelControlException {
0706: try {
0707: if (this .tax == null) {
0708: throw new ModelControlException(
0709: getLocalizedString("category.edit.error.add_child")
0710: + " " + childId);
0711: }
0712: RDMClassification nodeTax = this .tax.find(parentId);
0713: insertChildNode(nodeTax, childId, childDesc, childRule);
0714: } catch (ModelControlException mce) {
0715: throw mce;
0716: } catch (NullPointerException e) {
0717: // throw exception
0718: debugLogger.log(Level.INFO, "PSSH_CSPSAM0061",
0719: new String[] { parentId, childId, childDesc,
0720: e.getMessage() });
0721: throw new ModelControlException(
0722: getLocalizedString("category.edit.error.add_default"));
0723: }
0724: }
0725:
0726: /**
0727: * Insert a sibling node
0728: *
0729: * @param id
0730: * The id of the RDMClassification
0731: */
0732: public void insertSiblingNode(String nodeId, String siblingId,
0733: String childDesc, String childRule)
0734: throws ModelControlException {
0735: try {
0736: if (this .tax == null) {
0737: throw new ModelControlException(
0738: getLocalizedString("category.edit.error.add_sibling")
0739: + " " + siblingId);
0740: }
0741: RDMClassification nodeTax = this .tax.find(nodeId)
0742: .getParent();
0743: insertChildNode(nodeTax, siblingId, childDesc, childRule);
0744: } catch (ModelControlException mce) {
0745: throw mce;
0746: } catch (NullPointerException e) {
0747: // throw exception
0748: debugLogger.log(Level.INFO, "PSSH_CSPSAM0061",
0749: new String[] { nodeId, siblingId, childDesc,
0750: e.getMessage() });
0751: throw new ModelControlException(
0752: getLocalizedString("category.edit.error.add_sibling")
0753: + " "
0754: + siblingId
0755: + " "
0756: + getLocalizedString("category.edit.error.add_sibling_to")
0757: + " " + nodeId);
0758: }
0759: }
0760:
0761: /**
0762: * Insert a child node
0763: *
0764: * @param id
0765: * The id of the RDMClassification
0766: *
0767: * return true if successful
0768: * return false if already a equivalent child exist
0769: * throwseException in case of failure
0770: */
0771: public void insertChildNode(RDMClassification rc, String childId,
0772: String childDesc, String childRule)
0773: throws ModelControlException {
0774: try {
0775: String childAbsolutID = null;
0776: if (rc == root) {
0777: childAbsolutID = childId;
0778: } else {
0779: childAbsolutID = rc.getId() + ":" + childId;
0780: }
0781: if (rc != null) {
0782: if ((rc == root)
0783: && (childId
0784: .equals(RDMTaxonomy.RDM_TAXONOMY_ROOT) || childId
0785: .equals(tax.getId()))) {
0786: // 1st level child cannot be ROOT or same name as taxonomy
0787: if (childId.equals(RDMTaxonomy.RDM_TAXONOMY_ROOT)) {
0788: throw new ModelControlException(
0789: getLocalizedString("category.edit.error.root_child"));
0790: } else {
0791: throw new ModelControlException(
0792: getLocalizedString("category.edit.error.must_differ_from_top_classification"));
0793: }
0794: }
0795: // using similar method to the one used in RDMTaxonomy's constructor
0796: // 1. check if the child doesn't already exist
0797: if (tax.find(childAbsolutID) == null) {
0798: // 2. create a new RDMClassification
0799: RDMClassification newRc = newChild(rc, childId,
0800: childDesc, childRule, rc.getTaxonomyId());
0801: if (newRc != null) {
0802: // 3. insert RDMClassification in taxonomy
0803: tax.insert(newRc);
0804: setModifiedState(true);
0805: } else {
0806: throw new ModelControlException(
0807: getLocalizedString("category.edit.error.add")
0808: + " "
0809: + childAbsolutID
0810: + " "
0811: + getLocalizedString("category.edit.error.add_cannot_create"));
0812: }
0813: } else {
0814: throw new ModelControlException(
0815: getLocalizedString("category.edit.error.add")
0816: + " "
0817: + childAbsolutID
0818: + " "
0819: + getLocalizedString("category.edit.error.add_exists"));
0820: }
0821: } else {
0822: throw new ModelControlException(
0823: getLocalizedString("category.edit.error.add")
0824: + " "
0825: + childAbsolutID
0826: + " "
0827: + getLocalizedString("category.edit.error.add_no_parent"));
0828: }
0829: } catch (NullPointerException npe) {
0830: // throw exception
0831: debugLogger.log(Level.INFO, "PSSH_CSPSAM0063",
0832: new String[] { childId, rc.getId(),
0833: npe.getMessage() });
0834: throw new ModelControlException(
0835: getLocalizedString("category.edit.error.add_default"));
0836: }
0837: }
0838:
0839: /**
0840: * Update a node
0841: *
0842: * @param id
0843: * The id of the RDMClassification
0844: */
0845: public void updateNode(String currentId, String newId,
0846: String newDesc, String newRule)
0847: throws ModelControlException {
0848: try {
0849: if (newId.trim() != "") {
0850: // 1. modify the current selected node
0851: RDMClassification node = this .tax.find(currentId);
0852: StringBuffer sb = new StringBuffer();
0853: if (node.getParentId() == null) {
0854: // if the selected node is the Root the newId becomes also the
0855: // name of the Taxonomy
0856: updateTaxonomy(newId, newDesc);
0857: } else {
0858: // if parent is Root reject rename child same as ROOT or taxId
0859: if (node.getParent() == root) {
0860: if ((newId.equals(tax.getId()))
0861: || (newId
0862: .equals(RDMTaxonomy.RDM_TAXONOMY_ROOT))) {
0863: if (newId
0864: .equals(RDMTaxonomy.RDM_TAXONOMY_ROOT)) {
0865: throw new ModelControlException(
0866: getLocalizedString("category.edit.error.root_child"));
0867: } else {
0868: throw new ModelControlException(
0869: getLocalizedString("category.edit.error.must_differ_from_taxonomy"));
0870: }
0871: }
0872: }
0873: if (currentId.indexOf(':') != -1) {
0874: // build the new id with the prefix of current id
0875: sb.append(currentId.substring(0, currentId
0876: .lastIndexOf(":")));
0877: sb.append(":" + newId);
0878: } else {
0879: sb.append(newId);
0880: }
0881: // checking if we are renaming the node, reject if same as sibling
0882: boolean reject = false;
0883: if (!newId.equals(lastNode(currentId))) {
0884: // verify that the new name differs from existing siblings
0885: RDMClassification parentRc = node.getParent();
0886: for (int i = 0; i < parentRc.nChildren(); i++) {
0887: if (parentRc.nthChild(i).getId().equals(
0888: sb.toString())) {
0889: reject = true;
0890: break;
0891: }
0892: }
0893: }
0894: if (reject) {
0895: throw new ModelControlException(
0896: getLocalizedString("category.edit.error.update")
0897: + " "
0898: + sb.toString()
0899: + " "
0900: + getLocalizedString("category.edit.error.add_exists"));
0901: }
0902: node.setSubcategory(sb.toString());
0903: recurseUpdateNode(node, currentId, sb.toString());
0904: node.setDescription(newDesc);
0905: node.setMatchingRule(newRule);
0906: setModifiedState(true);
0907: }
0908: } else {
0909: throw new ModelControlException(
0910: getLocalizedString("category.edit.error.update")
0911: + " "
0912: + currentId
0913: + " "
0914: + getLocalizedString("category.edit.error.update_blank"));
0915: }
0916: } catch (NullPointerException npe) {
0917: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", npe
0918: .getMessage());
0919: throw new ModelControlException(
0920: getLocalizedString("category.edit.error.update_default"));
0921: }
0922: }
0923:
0924: /*
0925: *
0926: */
0927: public String getTaxonomyDescription() {
0928: if (tax != null)
0929: return tax.getDescription();
0930: else
0931: return null;
0932: }
0933:
0934: /*
0935: * updates the taxonomy name and recursively update
0936: * all its classification nodes taxonomy-id value
0937: */
0938: public void updateTaxonomy(String newId, String newDesc)
0939: throws ModelControlException {
0940: try {
0941: if (!newId.trim().equals("")) {
0942: // if any root children has the name=newId then reject
0943: boolean reject = false;
0944: for (int i = 0; i < root.nChildren(); i++) {
0945: if (root.nthChild(i).getId().equals(newId)) {
0946: reject = true;
0947: break;
0948: }
0949: }
0950: if (reject) {
0951: throw new ModelControlException(
0952: getLocalizedString("category.edit.error.must_differ_from_taxonomy"));
0953: } else {
0954: // if (!newId.trim().equals(root.getId())) {
0955: // updating all chidren nodes
0956: recurseUpdateNodeTaxonomy(root, newId);
0957:
0958: // change the taxonomyName
0959: tax.setId(newId);
0960: tax.setDescription(newDesc.trim());
0961: setModifiedState(true);
0962: }
0963: } else {
0964: throw new ModelControlException(
0965: getLocalizedString("category.edit.error.blank_taxonomy"));
0966: }
0967: } catch (NullPointerException e) {
0968: throw new ModelControlException(
0969: getLocalizedString("category.edit.error.update_default"));
0970: }
0971: }
0972:
0973: /**
0974: * Updates all classification node's taxonomyID value
0975: *
0976: * @param newId
0977: */
0978: public void recurseUpdateNodeTaxonomy(RDMClassification rc,
0979: String newId) throws ModelControlException {
0980: try {
0981: // change current node taxonomyId
0982: rc.setTaxonomyId(newId);
0983:
0984: // change all its children one's
0985: for (int i = 0; i < rc.nChildren(); i++) {
0986: recurseUpdateNodeTaxonomy(rc.nthChild(i), newId);
0987: }
0988: } catch (NullPointerException npe) {
0989: String rcStr;
0990: if (rc != null) {
0991: rcStr = rc.getId();
0992: } else {
0993: rcStr = "(rc is NULL)";
0994: }
0995: debugLogger.log(Level.FINER, "PSSH_CSPSAM0064", rcStr);
0996: throw new ModelControlException(
0997: "cannot recursively update Taxonomy on children of category "
0998: + rcStr);
0999: }
1000: }
1001:
1002: /**
1003: * Update a node's children's 'Id' and 'parentId'
1004: *
1005: * @param id
1006: * The id of the RDMClassification
1007: */
1008: public void recurseUpdateNode(RDMClassification node,
1009: String currentId, String newId)
1010: throws ModelControlException {
1011: try {
1012: // if current node's Id is prefixed with currentId rename Id
1013: String nodeId = node.getId();
1014: if (nodeId.startsWith(currentId)) {
1015: StringBuffer newNodeId = new StringBuffer(newId);
1016: String nodeIdSuffix = nodeId.substring(currentId
1017: .length());
1018: if (nodeIdSuffix.length() > 0) {
1019: // past existing suffix portion to newNodeId
1020: newNodeId.append(nodeIdSuffix);
1021: }
1022: node.setId(newNodeId.toString());
1023: }
1024:
1025: // if current node's parentId is prefixed with currentId rename parentId
1026: String parentId = node.getParentId();
1027: if (parentId != null) {
1028: if (parentId.startsWith(currentId)) {
1029: StringBuffer newParentNodeId = new StringBuffer(
1030: newId);
1031: String parentNodeIdSuffix = parentId
1032: .substring(currentId.length());
1033: if (parentNodeIdSuffix.length() > 0) {
1034: // past existing suffix portion to newParentNodeId
1035: newParentNodeId.append(parentNodeIdSuffix);
1036: }
1037: node.setParentId(newParentNodeId.toString());
1038: }
1039: }
1040:
1041: // call same method on all direct children of current node
1042: for (int i = 0; i < node.nChildren(); i++) {
1043: recurseUpdateNode(node.nthChild(i), currentId, newId);
1044: }
1045: } catch (NullPointerException npe) {
1046: debugLogger.log(Level.INFO, "PSSH_CSPSAM0065", currentId);
1047: throw new ModelControlException(
1048: "cannot recursivly update children of category "
1049: + currentId);
1050: }
1051: }
1052:
1053: /*
1054: * returns all descendante children ID given a start parent ID
1055: */
1056: public ArrayList getDescendance(String parentID)
1057: throws ModelControlException {
1058: try {
1059: // search the parent ID in the tree
1060: RDMClassification node = this .tax.find(parentID);
1061:
1062: // build the array
1063: ArrayList descendance = new ArrayList();
1064: getDescendance(node, descendance);
1065:
1066: // return result
1067: return descendance;
1068: } catch (Exception e) {
1069: throw new ModelControlException(
1070: "Cannot get the descendance of node=" + parentID);
1071: }
1072: }
1073:
1074: /*
1075: * returns all descendante children ID given a start parent ID
1076: */
1077: private void getDescendance(RDMClassification rc,
1078: ArrayList descendance) {
1079: // add all direct children to the descendante array
1080: // recursively adds grand children
1081: for (int i = 0; i < rc.nChildren(); i++) {
1082: RDMClassification childRc = rc.nthChild(i);
1083: descendance.add(childRc.getId());
1084: getDescendance(childRc, descendance);
1085: }
1086: }
1087:
1088: /*
1089: * utility to return the lastNode from a classification NodeID
1090: */
1091: public static String lastNode(String nodeID) {
1092: try {
1093: String value = null;
1094: if (nodeID != null) {
1095: int ndx = nodeID.lastIndexOf(':');
1096: if (ndx > 0) {
1097: value = nodeID.substring(ndx + 1);
1098: } else {
1099: value = nodeID;
1100: }
1101: } else {
1102: value = null;
1103: }
1104: return value;
1105: } catch (Exception e) {
1106: debugLogger.log(Level.INFO, "PSSH_CSPSAM0058",
1107: new String[] { nodeID, e.getMessage() });
1108: return null;
1109: }
1110: }
1111:
1112: /**
1113: * load Taxonomy from taxonomy.rdm config file
1114: */
1115: public void load() {
1116: try {
1117: setErrorMsg(null);
1118: tax = null;
1119: root = null;
1120: String s = SearchConfig.getValue(SearchConfig.TAX);
1121: SOIFInputStream ss = new SOIFInputStream(s, "UTF-8");
1122: tax = new RDMTaxonomy(ss);
1123: if (tax == null) {
1124: root = null;
1125: debugLogger.finer("PSSH_CSPSAM0066");
1126: } else {
1127: root = tax.find(RDMTaxonomy.RDM_TAXONOMY_ROOT);
1128: if (root == null) {
1129: debugLogger.finer("PSSH_CSPSAM0067");
1130: setErrorMsg(getLocalizedString("category.edit.error.default"));
1131: }
1132: // reset the error
1133: setErrorMsg(null);
1134: // set the model as not modified
1135: setModifiedState(false);
1136: // set the timestamp tracking on file concurrent changes
1137: setTimeStamp();
1138: }
1139: } catch (FileNotFoundException fnf) {
1140: setErrorMsg(getLocalizedString("category.edit.error.missing_config_file"));
1141: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", fnf
1142: .getMessage());
1143: } catch (UnsupportedEncodingException ue) {
1144: setErrorMsg(getLocalizedString("category.edit.error.invalid_config_file"));
1145: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", ue
1146: .getMessage());
1147: } catch (SOIFException se) {
1148: setErrorMsg(getLocalizedString("category.edit.error.invalid_config_file"));
1149: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", se
1150: .getMessage());
1151: } catch (Exception e) {
1152: String exceptionMsg = e.getMessage();
1153: if ((exceptionMsg != null)
1154: && (exceptionMsg.indexOf(SOIF.INVALIDSOIF) != -1)) {
1155: setErrorMsg(getLocalizedString("category.edit.error.invalid_config_file"));
1156: } else {
1157: setErrorMsg(getLocalizedString("category.edit.error.default"));
1158: }
1159: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
1160: .getMessage());
1161: }
1162: }
1163:
1164: /*
1165: * new Classification in the current taxonomy
1166: */
1167: RDMClassification newChild(RDMClassification parent,
1168: String childId, String childDesc, String childRule,
1169: String taxonomyId) {
1170: try {
1171: if (childId.trim() != "") {
1172: String parentId = parent.getId();
1173: String newId;
1174: if (parentId.equals(RDMTaxonomy.RDM_TAXONOMY_ROOT)) {
1175: newId = childId;
1176: } else {
1177: newId = parentId + ":" + childId;
1178: }
1179: RDMClassification rc = new RDMClassification(newId);
1180: rc.setTaxonomyId(taxonomyId);
1181: rc.setParentId(parentId);
1182: rc.setDescription(childDesc);
1183: rc.setMatchingRule(childRule);
1184: return rc;
1185: } else {
1186: return null;
1187: }
1188: } catch (Exception e) {
1189: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
1190: .getMessage());
1191: return null;
1192: }
1193: }
1194:
1195: /**
1196: * 1. save current Taxonomy
1197: */
1198: public void save() throws ModelControlException {
1199: // save the current taxonomy in file
1200: String server_root = CSConfig.getServerRoot();
1201: try {
1202: SOIFOutputStream sos = new SOIFOutputStream(SearchConfig
1203: .getValue(SearchConfig.TAX), "UTF-8");
1204: // write taxonomy
1205: sos.write(tax.getSOIF());
1206: sos.flush();
1207: // writes the entire classification
1208: write(sos);
1209: sos.flush();
1210: sos.close();
1211: // purge + reindex DB
1212: if (DBUtil.indexCategories(CSConfig.getServerRoot()) != true) {
1213: debugLogger.finer("PSSH_CSPSAM0068");
1214: }
1215: // reset the error message
1216: setErrorMsg(null);
1217: // set the model as not modified
1218: setModifiedState(false);
1219: // set the timestamp to match newly saved file.
1220: setTimeStamp();
1221: // refresh the CSConfig static object representing the taxonomy
1222: // !!! TODO - CSConfig shouldn't keep taxonomy as a static object
1223: CSConfig.loadTaxonomy();
1224: } catch (FileNotFoundException fnf) {
1225: setErrorMsg(getLocalizedString("category.edit.error.missing_config_file"));
1226: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", fnf
1227: .getMessage());
1228: } catch (UnsupportedEncodingException ue) {
1229: setErrorMsg(getLocalizedString("category.edit.error.invalid_config_file"));
1230: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", ue
1231: .getMessage());
1232: } catch (Exception e) {
1233: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", e
1234: .getMessage());
1235: setErrorMsg(getLocalizedString("category.edit.error.default"));
1236: }
1237: }
1238:
1239: /*
1240: * returns the boolean state of modification reflecting differences between
1241: * the in memory taxonomy object and the taxonomy.rdm conf file
1242: */
1243: public boolean getModifiedState() {
1244: return modified;
1245: }
1246:
1247: /*
1248: * sets the modification flag of the in memory taxonomy vs the taxonomy.rdm config file
1249: */
1250: private void setModifiedState(boolean modified) {
1251: this .modified = modified;
1252: this .lastActionDate = new Date();
1253: }
1254:
1255: public void write(SOIFOutputStream ss) {
1256: write(ss, root);
1257: }
1258:
1259: private void write(SOIFOutputStream ss, RDMClassification rc) {
1260: try {
1261: StringBuffer sb = null;
1262:
1263: if (rc != root) {
1264: // dumping current leaving out the ROOT dummy node
1265: ss.write(rc.getSOIF());
1266: }
1267:
1268: // dumping childrens
1269: if (rc.getNumDescendant() != 0) {
1270: // loop on dumping all the classification
1271: for (int j = 0; j < rc.nChildren(); j++) {
1272: write(ss, rc.nthChild(j));
1273: }
1274: }
1275: } catch (IOException ioe) {
1276: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", ioe
1277: .getMessage());
1278: }
1279: }
1280:
1281: /*
1282: * debug/trace methods
1283: */
1284: public void dump(RDMClassification rc) {
1285: // dumping children SOIFs
1286: debugLogger.log(Level.FINER, "PSSH_CSPSAM0069", tax.getId());
1287:
1288: dumpChildren(rc, 0);
1289: }
1290:
1291: public void dumpChildren(RDMClassification rc, int level) {
1292:
1293: try {
1294: // indentation
1295: StringBuffer sb = new StringBuffer("level(" + rc.getDepth()
1296: + "):");
1297: for (int i = 0; i < level; i++) {
1298: sb.append(" ");
1299: }
1300:
1301: // dumping current
1302: debugLogger.log(Level.FINER, "PSSH_CSPSAM0070",
1303: (sb.toString() + new String(rc.getSOIF()
1304: .toByteArray())));
1305:
1306: // dumping childrens
1307: if (rc.getNumDescendant() != 0) {
1308: // loop on dumping all the classification
1309: for (int j = 0; j < rc.nChildren(); j++) {
1310: dumpChildren(rc.nthChild(j), level + 1);
1311: }
1312: }
1313: } catch (IOException ioe) {
1314: debugLogger.log(Level.INFO, "PSSH_CSPSAM0012", ioe
1315: .getMessage());
1316: }
1317: }
1318:
1319: /*
1320: * for debug purposes
1321: */
1322: public RDMClassification getRoot() {
1323: return root;
1324: }
1325:
1326: public String getLocalizedString(String key) {
1327: return SearchResource.geti18nString(key, userLocale);
1328: }
1329:
1330: /*
1331: * returns true if the configuration file is newer than the model timestamp
1332: */
1333: public boolean isConfigNewer() throws ModelControlException {
1334: try {
1335: File f = new File(SearchConfig.getValue(SearchConfig.TAX));
1336: if ((getTimeStamp() < f.lastModified())
1337: && (getTimeStamp() != 0)) {
1338: debugLogger.log(Level.FINER, "PSSH_CSPSAM0071",
1339: new String[] { Long.toString(f.lastModified()),
1340: Long.toString(getTimeStamp()) });
1341: return true;
1342: } else {
1343: return false;
1344: }
1345: } catch (Exception e) {
1346: debugLogger.info("PSSH_CSPSAM0072");
1347: throw new ModelControlException(
1348: getLocalizedString("category.edit.warning.config_timestamp"));
1349: }
1350: }
1351:
1352: /*
1353: * method that generates the hashCode of the nodeId so as double-Byte String
1354: * to replace munged String up by Jato type Converter
1355: */
1356: public static String hashNodeId(String nodeId) {
1357: if (nodeId != null) {
1358: return String.valueOf(nodeId.hashCode());
1359: } else {
1360: debugLogger.info("PSSH_CSPSAM0073");
1361: return null;
1362: }
1363: }
1364:
1365: /*
1366: * setting the persistance error message
1367: */
1368: public void setErrorMsg(String errMsg) {
1369: errorMsg = errMsg;
1370: }
1371:
1372: /*
1373: * getting the persistance error message
1374: */
1375: public String getErrorMsg() {
1376: return errorMsg;
1377: }
1378:
1379: /*
1380: * returns the last Date an action (add/del/update/save/load) was taken on the model
1381: */
1382: public Date getLastActionDate() {
1383: return lastActionDate;
1384: }
1385:
1386: /*
1387: * sets the last Date an action (add/del/update/save/load) was taken on the model
1388: */
1389: private void setLastActionDate(long d) {
1390: lastActionDate = new Date(d);
1391: }
1392:
1393: /*
1394: * returns the last stored model modification date
1395: */
1396: private long getTimeStamp() {
1397: return lastModified;
1398: }
1399:
1400: /*
1401: * set the private model config file last modified timestamp
1402: */
1403: private void setTimeStamp() {
1404: try {
1405: File f = new File(SearchConfig.getValue(SearchConfig.TAX));
1406: lastModified = f.lastModified();
1407: setLastActionDate(lastModified);
1408: } catch (Exception e) {
1409: debugLogger.log(Level.INFO, "PSSH_CSPSAM0074", e
1410: .getMessage());
1411: setErrorMsg(getLocalizedString("category.edit.warning.config_timestamp"));
1412: }
1413: }
1414:
1415: /**
1416: * method that verifies if a Category name is composed of only valid characters
1417: */
1418: public boolean isValidName(String catName)
1419: throws ModelControlException {
1420: if (catName != null) {
1421: String trimmedCatName = catName.trim();
1422: if (trimmedCatName.length() != 0) {
1423: // disallow some problem chars:
1424: // ' and \ cause Java Script escaping problems
1425: // < and > cause Nova escaping problems (and > is often used as a cat separator)
1426: // : is the reserved category separator
1427: if ((trimmedCatName.indexOf(':') == -1)
1428: && (trimmedCatName.indexOf(';') == -1)
1429: && (trimmedCatName.indexOf('<') == -1)
1430: && (trimmedCatName.indexOf('>') == -1)
1431: && (trimmedCatName.indexOf('"') == -1)
1432: && (trimmedCatName.indexOf('\\') == -1)) {
1433: return true;
1434: } else {
1435: throw new ModelControlException(
1436: getLocalizedString("category.edit.error.invalid_name"));
1437: }
1438:
1439: } else {
1440: throw new ModelControlException(
1441: getLocalizedString("category.edit.error.invalid_name"));
1442: }
1443: } else {
1444: throw new ModelControlException(
1445: getLocalizedString("category.edit.error.blank_taxonomy"));
1446: }
1447: }
1448:
1449: ////////////////////////////////////////
1450: // Class variables
1451: ////////////////////////////////////////
1452:
1453: public static final String FIELD_ID = "Id";
1454: public static final String FIELD_NAME = "name";
1455: public static final String FIELD_DESCRIPTION = "description";
1456: public static final String FIELD_CONCEPTS = "concepts";
1457: public static final String FIELD_FILES = "files";
1458: public static final String FIELD_VIEWBEAN_CLASS = "viewbean";
1459: public static final String MODEL_NAME = "TaxonomyTreeModel";
1460:
1461: ////////////////////////////////////////
1462: // Member variables
1463: ////////////////////////////////////////
1464:
1465: private String name;
1466: private RDMTaxonomy tax;
1467: private RDMClassification root;
1468: private String errorMsg = null;
1469: private String editErrorMsg = null;
1470: private boolean modified;
1471: private long lastModified;
1472: private Date lastActionDate;
1473: }
|