/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenFlexo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.drm; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Map; import java.util.TreeMap; import java.util.Vector; import java.util.logging.Logger; import org.openflexo.drm.action.ApproveVersion; import org.openflexo.drm.dm.StructureModified; import org.openflexo.drm.helpset.HelpSetConfiguration; import org.openflexo.foundation.Inspectors; import org.openflexo.foundation.validation.FixProposal; import org.openflexo.foundation.validation.ProblemIssue; import org.openflexo.foundation.validation.Validable; import org.openflexo.foundation.validation.ValidationError; import org.openflexo.foundation.validation.ValidationIssue; import org.openflexo.foundation.validation.ValidationRule; import org.openflexo.foundation.validation.ValidationWarning; import org.openflexo.inspector.InspectableObject; import org.openflexo.localization.FlexoLocalization; public class DocItem extends DRMObject implements InspectableObject { static final Logger logger = Logger.getLogger(DocItem.class.getPackage().getName()); public static final int NO_DOCUMENTED = 0; public static final int APPROVING_PENDING = 1; public static final int AVAILABLE_NEWER_VERSION_PENDING = 2; public static final int AVAILABLE_UP_TO_DATE = 3; // String identifier for this item: name will be used as folder name in FS private String identifier; // Parent DocItemFolder private DocItemFolder folder; // Parent DocItem, related to inheritance private DocItem inheritanceParentItem; // Vector of DocItem: childs of this item, related to inheritance private Vector<DocItem> inheritanceChildItems; // Parent DocItem, related to embedding private DocItem embeddingParentItem; // Vector of DocItem: childs of this item, related to embedding private Vector<DocItem> embeddingChildItems; // Vector of DocItem: related to items private Vector<DocItem> relatedToItems; // Description for this item private String description; // Localized titles for those items, where key is the identifier of the // language, as String private Map<String, String> titles; // Versions registered for this item, as a Vector of DocItemVersion private Vector<DocItemVersion> versions; // History for this item, as a Vector of DocItemAction private Vector<DocItemAction> actions; private boolean _isEmbedded = false; private boolean _isHidden = false; public boolean getIsEmbedded() { return _isEmbedded; } public void setIsEmbedded(boolean isEmbedded) { _isEmbedded = isEmbedded; } public boolean getIsHidden() { return _isHidden; } public boolean isPublished() { return !getIsHidden() && titles.size() > 0 && hasBeenApproved() && (getFolder() == null || getFolder().getPrimaryDocItem() == this || getFolder().isPublished()); } public void setIsHidden(boolean isHidden) { _isHidden = isHidden; } public DocItem(DocResourceCenter docResourceCenter) { super(docResourceCenter); identifier = null; description = null; folder = null; inheritanceParentItem = null; inheritanceChildItems = new Vector<DocItem>(); embeddingParentItem = null; embeddingChildItems = new Vector<DocItem>(); relatedToItems = new Vector<DocItem>(); titles = new TreeMap<String, String>(); versions = new Vector<DocItemVersion>(); actions = new Vector<DocItemAction>(); } public DocItem(DRMBuilder builder) { this(builder.docResourceCenter); initializeDeserialization(builder); } public static DocItem createDocItem(String anIdentifier, String aDescription, DocItemFolder folder, DocResourceCenter docResourceCenter, boolean isEmbedded) { if (docResourceCenter.getItemNamed(anIdentifier) != null) { logger.warning("Could not create doc item " + anIdentifier + ": duplicated identifier"); return docResourceCenter.getItemNamed(anIdentifier); } logger.fine("Create DocItem " + anIdentifier + " in " + folder.getIdentifier()); DocItem returned = new DocItem(docResourceCenter); returned.identifier = anIdentifier; returned.description = aDescription; if (folder != null) { folder.addToItems(returned); } returned._isEmbedded = isEmbedded; returned._isHidden = true; return returned; } /** * Overrides delete * * @see org.openflexo.foundation.FlexoModelObject#delete() */ @Override public void delete() { Enumeration en = ((Vector) embeddingChildItems.clone()).elements(); while (en.hasMoreElements()) { DocItem it = (DocItem) en.nextElement(); it.delete(); } en = ((Vector) relatedToItems.clone()).elements(); while (en.hasMoreElements()) { DocItem it = (DocItem) en.nextElement(); it.removeFromRelatedToItems(this); } en = ((Vector) inheritanceChildItems.clone()).elements(); while (en.hasMoreElements()) { DocItem it = (DocItem) en.nextElement(); it.removeFromInheritanceChildItems(this); } if (embeddingParentItem != null) { embeddingParentItem.removeFromEmbeddingChildItems(this); } if (inheritanceParentItem != null) { inheritanceParentItem.removeFromInheritanceChildItems(this); } if (folder != null) { folder.removeFromItems(this); } super.delete(); } @Override public String getInspectorName() { return Inspectors.DRE.DOC_ITEM_INSPECTOR; } @Override public String getDescription() { return description; } @Override public void setDescription(String description) { this.description = description; setChanged(); } public DocItemFolder getFolder() { return folder; } public void setFolder(DocItemFolder folder) { this.folder = folder; setChanged(); } @Override public String getIdentifier() { return identifier; } public void setIdentifier(String identifier) { String old = this.identifier; this.identifier = identifier; setChanged(); if (getFolder() != null) { getFolder().notifyItemHasBeenRenamedTo(this, old, identifier); } } @Override public String getFullyQualifiedName() { return getIdentifier(); } private boolean embeddingItemsNeedsReordering = true; private Vector<DocItem> orderedEmbeddingChildItems; public Vector<DocItem> getOrderedEmbeddingChildItems() { if (embeddingItemsNeedsReordering) { orderedEmbeddingChildItems = new Vector<DocItem>(); orderedEmbeddingChildItems.addAll(embeddingChildItems); Collections.sort(orderedEmbeddingChildItems, DocItem.COMPARATOR); embeddingItemsNeedsReordering = false; } return orderedEmbeddingChildItems; } public Vector<DocItem> getEmbeddingChildItems() { return embeddingChildItems; } public void setEmbeddingChildItems(Vector<DocItem> embeddingChildItems) { this.embeddingChildItems = embeddingChildItems; setChanged(); embeddingItemsNeedsReordering = true; } public void addToEmbeddingChildItems(DocItem item) { if (!embeddingChildItems.contains(item)) { embeddingChildItems.add(item); item.setEmbeddingParentItem(this); setChanged(); notifyObservers(new StructureModified()); embeddingItemsNeedsReordering = true; } } public void removeFromEmbeddingChildItems(DocItem item) { if (embeddingChildItems.contains(item)) { item.setEmbeddingParentItem(null); embeddingChildItems.remove(item); setChanged(); notifyObservers(new StructureModified()); embeddingItemsNeedsReordering = true; } } public DocItem getEmbeddingParentItem() { return embeddingParentItem; } public void setEmbeddingParentItem(DocItem newParent) { DocItem oldParent = embeddingParentItem; if (newParent != oldParent) { embeddingParentItem = newParent; if (oldParent != null) { oldParent.removeFromEmbeddingChildItems(this); } if (newParent != null) { newParent.addToEmbeddingChildItems(this); } } setChanged(); } /** * Returns direct childs relating to inheritance Take isHidden property under account by searching deeply * * @return */ public Vector<DocItem> getDerivedInheritanceChildItems() { Vector<DocItem> returned = new Vector<DocItem>(); for (Enumeration en = getInheritanceChildItems().elements(); en.hasMoreElements();) { DocItem next = (DocItem) en.nextElement(); if (!next.isPublished()) { returned.addAll(next.getDerivedInheritanceChildItems()); } else { returned.add(next); } } return returned; } public Vector<DocItem> getInheritanceChildItems() { return inheritanceChildItems; } public void setInheritanceChildItems(Vector<DocItem> inheritanceChildItems) { this.inheritanceChildItems = inheritanceChildItems; } public DocItem getInheritanceParentItem() { return inheritanceParentItem; } public void setInheritanceParentItem(DocItem newParent) { DocItem oldParent = inheritanceParentItem; if (newParent != oldParent) { inheritanceParentItem = newParent; if (oldParent != null) { oldParent.removeFromInheritanceChildItems(this); } if (newParent != null) { newParent.addToInheritanceChildItems(this); } setChanged(); } } public void addToInheritanceChildItems(DocItem item) { if (!inheritanceChildItems.contains(item)) { inheritanceChildItems.add(item); item.setInheritanceParentItem(this); setChanged(); notifyObservers(new StructureModified()); } } public void removeFromInheritanceChildItems(DocItem item) { if (inheritanceChildItems.contains(item)) { item.setInheritanceParentItem(null); inheritanceChildItems.remove(item); setChanged(); notifyObservers(new StructureModified()); } } public Vector<DocItem> getRelatedToItems() { return relatedToItems; } public void setRelatedToItems(Vector<DocItem> relatedToItems) { this.relatedToItems = relatedToItems; } public void addToRelatedToItems(DocItem item) { if (!relatedToItems.contains(item)) { relatedToItems.add(item); item.relatedToItems.add(this); setChanged(); } } public void removeFromRelatedToItems(DocItem item) { relatedToItems.remove(item); item.relatedToItems.remove(this); setChanged(); } public Map<String, String> _getTitles() { return titles; } public void _setTitles(Map<String, String> titles) { this.titles = new TreeMap<String, String>(titles); } public void _setTitleForKey(String title, String languageId) { if (getFolder() != null) { getFolder().reorderItems(); } titles.put(languageId, title); } public void _removeTitleWithKey(String languageId) { titles.remove(languageId); } public String getTitle(Language language) { return titles.get(language.getIdentifier()); } public void setTitle(String title, Language language) { _setTitleForKey(title, language.getIdentifier()); setChanged(); } public Vector<DocItemVersion> getVersions() { return versions; } public void setVersions(Vector<DocItemVersion> versions) { this.versions = versions; setChanged(); } public void addToVersions(DocItemVersion version) { versions.add(version); version.setItem(this); setChanged(); } public void removeFromVersions(DocItemVersion version) { version.setItem(null); versions.remove(version); setChanged(); } public Vector<DocItemAction> getActions() { return actions; } public void setActions(Vector<DocItemAction> actions) { this.actions = actions; } public void addToActions(DocItemAction action) { actions.add(action); action.setItem(this); setChanged(); notifyObservers(new StructureModified()); if (getFolder() != null) { getFolder().notifyStructureChanged(); } } public void removeFromActions(DocItemAction action) { action.setItem(null); actions.remove(action); setChanged(); notifyObservers(new StructureModified()); if (getFolder() != null) { getFolder().notifyStructureChanged(); } } public DocItemVersion getVersion(DocItemVersion.Version aVersion) { for (Enumeration en = getVersions().elements(); en.hasMoreElements();) { DocItemVersion next = (DocItemVersion) en.nextElement(); if (next.getVersion().equals(aVersion)) { return next; } } return null; } public DocItemAction submitVersion(DocItemVersion version, Author author, DocResourceCenter docResourceCenter) { DocItemAction action = DocItemAction.createSubmitAction(version, author, docResourceCenter); addToActions(action); addToVersions(version); return action; } public DocItemAction reviewVersion(DocItemVersion version, Author author, DocResourceCenter docResourceCenter) { DocItemAction action = DocItemAction.createReviewAction(version, author, docResourceCenter); addToActions(action); addToVersions(version); return action; } public DocItemAction approveVersion(DocItemVersion version, Author author, DocResourceCenter docResourceCenter) { DocItemAction action = DocItemAction.createApproveAction(version, author, docResourceCenter); addToActions(action); return action; } public DocItemAction refuseVersion(DocItemVersion version, Author author, DocResourceCenter docResourceCenter) { DocItemAction action = DocItemAction.createRefuseAction(version, author, docResourceCenter); addToActions(action); return action; } @Override public String toString() { return "item:" + getIdentifier(); } public String getRelativePath() { return getFolder().getRelativePath() + "/" + getIdentifier(); } public String getRelativePathFrom(DocItem aStartPosition) { String prefixPath = ""; // boolean prefixPathIsFirst = true; String postfixPath = ""; // boolean postfixPathIsFirst = true; DocItemFolder commonAncestor = aStartPosition.getFolder(); while (!commonAncestor.isAncestorOf(this)) { prefixPath += "../"; commonAncestor = commonAncestor.getParentFolder(); // prefixPathIsFirst = false; } // OK, common ancestor is found DocItemFolder currentFolder = getFolder(); while (currentFolder != commonAncestor) { postfixPath = currentFolder.getIdentifier() + "/" + postfixPath; currentFolder = currentFolder.getParentFolder(); // postfixPathIsFirst = false; } // Now, need to concatenate return prefixPath + postfixPath + getIdentifier(); } public String getHTMLLinkFrom(DocItem aStartPosition, Language language) { StringBuffer returned = new StringBuffer(); String title = getTitle(language); if (title == null) { title = getIdentifier(); } returned.append("<a href=\""); returned.append(getRelativePathFrom(aStartPosition) + ".html"); returned.append("\">"); returned.append(title); returned.append("</a>"); return returned.toString(); } public int getStatusForLanguage(Language language) { DocItemAction lastAction = getLastActionForLanguage(language); DocItemAction lastApprovedAction = getLastApprovedActionForLanguage(language); DocItemAction lastPendingAction = getLastPendingActionForLanguage(language); if (lastAction == null) { return NO_DOCUMENTED; } if (lastApprovedAction == null) { return APPROVING_PENDING; } if (lastPendingAction != null && lastPendingAction.getVersion().getVersion().isGreaterThan(lastApprovedAction.getVersion().getVersion())) { return AVAILABLE_NEWER_VERSION_PENDING; } else { return AVAILABLE_UP_TO_DATE; } } public String getLocalizedStatusForLanguage(Language language) { switch (getStatusForLanguage(language)) { case NO_DOCUMENTED: return FlexoLocalization.localizedForKey("no_documentation"); case APPROVING_PENDING: return FlexoLocalization.localizedForKey("approving_pending"); case AVAILABLE_NEWER_VERSION_PENDING: return FlexoLocalization.localizedForKey("available_newer_version_pending"); case AVAILABLE_UP_TO_DATE: return FlexoLocalization.localizedForKey("available_and_up_to_date"); default: return "???"; } } public boolean hasBeenApproved() { for (Enumeration en = getActions().elements(); en.hasMoreElements();) { DocItemAction next = (DocItemAction) en.nextElement(); if (next.isApproved()) { return true; } } return false; } public DocItemAction getLastApprovedActionForLanguage(Language language) { DocItemAction returned = null; for (Enumeration en = getActions().elements(); en.hasMoreElements();) { DocItemAction next = (DocItemAction) en.nextElement(); if (next.getVersion().getLanguage() == language && next.isApproved()) { returned = next; } } return returned; } public DocItemAction getLastPendingActionForLanguage(Language language) { DocItemAction returned = null; for (Enumeration en = getActions().elements(); en.hasMoreElements();) { DocItemAction next = (DocItemAction) en.nextElement(); if (next.getVersion().getLanguage() == language && next.isPending()) { returned = next; } } return returned; } public DocItemAction getLastActionForLanguage(Language language) { DocItemAction returned = null; for (Enumeration en = getActions().elements(); en.hasMoreElements();) { DocItemAction next = (DocItemAction) en.nextElement(); if (next.getVersion().getLanguage() == language) { returned = next; } } return returned; } public DocItemVersion getValidVersionForLanguage(Language language) { DocItemAction lastApproved = getLastApprovedActionForLanguage(language); if (lastApproved != null) { return lastApproved.getVersion(); } return null; } @Override public String getClassNameKey() { return "doc_item"; } public static DocItemComparator COMPARATOR = new DocItemComparator(); public static class DocItemComparator implements Comparator<DocItem> { public Language currentLanguage; @Override public int compare(DocItem docItem1, DocItem docItem2) { if (currentLanguage != null) { String string1 = docItem1.getTitle(currentLanguage); if (string1 == null) { string1 = docItem1.getIdentifier(); } String string2 = docItem2.getTitle(currentLanguage); if (string2 == null) { string2 = docItem2.getIdentifier(); } return string1.compareTo(string2); } else { return docItem1.getIdentifier().compareTo(docItem2.getIdentifier()); } } } // ========================================================================== // ============================= Validation // ================================= // ========================================================================== public static class DocumentationShouldBeUpToDate extends ValidationRule { private Language _language; public DocumentationShouldBeUpToDate(Language language) { super(DocItem.class, "documentation_should_be_up_to_date_for_language"); _language = language; } public String getLanguageIdentifier() { if (_language != null) { return _language.getIdentifier(); } return ""; } @Override public String getLocalizedName() { return FlexoLocalization.localizedForKeyWithParams("documentation_should_be_up_to_date_for_language_($languageIdentifier)", this); } @Override public ValidationIssue applyValidation(final Validable object) { final DocItem item = (DocItem) object; ProblemIssue issue = null; if (item.getStatusForLanguage(_language) == NO_DOCUMENTED) { issue = new ValidationError(this, object, "doc_item_($object.identifier)_has_no_documentation_for_language_($validationRule.languageIdentifier)"); } else if (item.getStatusForLanguage(_language) == APPROVING_PENDING) { issue = new ValidationError(this, object, "doc_item_($object.identifier)_has_unvalidated_documentation_proposal_for_language_($validationRule.languageIdentifier)"); issue.addToFixProposals(new ApprovePendingVersion(item.getLastPendingActionForLanguage(_language).getVersion())); } else if (item.getStatusForLanguage(_language) == AVAILABLE_NEWER_VERSION_PENDING) { issue = new ValidationWarning(this, object, "doc_item_($object.identifier)_is_available_for_language_($validationRule.languageIdentifier)_but_a_newer_version_is_pending"); issue.addToFixProposals(new ApprovePendingVersion(item.getLastPendingActionForLanguage(_language).getVersion())); } return issue; } public static class ApprovePendingVersion extends FixProposal { public DocItemVersion version; public ApprovePendingVersion(DocItemVersion aVersion) { super("approve_pending_version"); version = aVersion; } @Override protected void fixAction() { logger.warning("This implementation is not correct: you should not use FlexoAction primitive from the model !"); // TODO: Please implement this better later // Used editor will be null ApproveVersion approveAction = ApproveVersion.actionType.makeNewAction(version.getItem(), null); approveAction.setVersion(version); approveAction.doAction(); } } } public boolean isIncluded(HelpSetConfiguration configuration) { return getFolder().isIncluded(configuration); } }