/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* CALFeatureMetadata.java
* Created: Apr 14, 2003
* By: Bo Ilic
*/
package org.openquark.cal.metadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.openquark.cal.services.CALFeatureName;
import org.openquark.cal.services.LocalizedResourceName;
import org.openquark.cal.services.ResourceName;
import org.openquark.util.xml.BadXMLDocumentException;
import org.openquark.util.xml.NamespaceInfo;
import org.openquark.util.xml.XMLPersistenceHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* CALFeatureMetadata is a common base class for features in CAL to which some metadata
* can be attached.
* <p>
* It also includes a mechanism by which arbitrary name-value pairs can be associated with a feature.
* <p>
* The CALFeatureMetadata hierarchy provides extra information about various CAL entities created by the CAL
* compiler such as those in the ScopedEntity hierarchy as well as ModuleTypeInfo. This is analogous
* to the relationship between the classes in the java.beans.FeatureDescriptor hierarchy and the
* java.lang.reflect.AccessibleObject hierarchy. In particular, most of the information in the metadata classes
* is optional, and null values may be returned if the information has not been supplied. In that case, a UI
* tool can use the lower-level information supplied by the ScopedEntity and ModuleTypeInfo classes to provide
* reasonable "default" behavior.
*
* @author Bo Ilic
*/
abstract public class CALFeatureMetadata {
/** The feature name and locale of the metadata. */
private final LocalizedResourceName resourceName;
/** localized name for this feature for use in UI displays. */
private String displayName;
/** localized short description of this feature. Generally a short phrase or single sentence. */
private String shortDescription;
/** localized long description of this feature. */
private String longDescription;
/** true if this feature is intended for expert users rather than normal users. */
private boolean expert = false;
/** true if this feature is mainly for tool use and should not be exposed to humans. */
private boolean hidden = false;
/** true if this feature is particularly important for presenting to humans. */
private boolean preferred = false;
/** a mechanism by which arbitrary name-value pairs can be associated with this feature. */
private final Map<String, String> attributes = new HashMap<String, String>();
/**
* List of CAL names that identify features related to this metadata object. This is useful for
* UI clients where we want to display a see-also list. Since related features are shared between metadata
* object we simply store the CAL name of the related feature. If a client request an actual metadata
* object for a related feature we fetch a fresh instance from the MetadataManager.
*/
private final List<CALFeatureName> relatedFeatures = new ArrayList<CALFeatureName>();
/** the date-time at which this feature was created. */
private Date creationDate;
/** the date-time at which this feature or its associated metadata was last modified. */
private Date modificationDate;
/** the version number or version identifier of this feature. */
private String version; //todoBI should this have a more structured format?
/** the name of the author, organization or group associated with this feature. */
private String author;
/**
* Constructor for a new CALFeatureMetadata object.
* @param featureName the feature name of the feature the metadata belongs to
* @param locale the locale associated with the metadata.
*/
public CALFeatureMetadata(CALFeatureName featureName, Locale locale) {
if (featureName == null || locale == null) {
throw new NullPointerException();
}
this.resourceName = new LocalizedResourceName(featureName, locale);
}
/**
* @return the ResourceName naming this metadata resource.
*/
public LocalizedResourceName getResourceName() {
return resourceName;
}
/**
* @return the name of the feature the metadata belongs to
*/
public CALFeatureName getFeatureName() {
return (CALFeatureName)resourceName.getFeatureName();
}
/**
* @return the locale associated with this metadata.
*/
public Locale getLocale() {
return resourceName.getLocale();
}
/**
* @return gets the localized name for this feature for use in UI displays.
*/
public String getDisplayName() {
return displayName;
}
/**
* @param displayName
*/
public void setDisplayName(String displayName) {
this.displayName = (displayName != null && displayName.trim().length() > 0) ? displayName : null;
}
/**
* @return gets the localized short description of this feature. Generally a short phrase or single sentence.
*/
public String getShortDescription() {
return shortDescription;
}
/**
* @param shortDescription
*/
public void setShortDescription(String shortDescription) {
this.shortDescription = (shortDescription != null && shortDescription.trim().length() > 0) ? shortDescription : null;
}
/**
* @return gets the localized long description of this feature.
*/
public String getLongDescription() {
return longDescription;
}
/**
* @param longDescription
*/
public void setLongDescription(String longDescription) {
this.longDescription = (longDescription != null && longDescription.trim().length() > 0) ? longDescription : null;
}
/**
* @return true if this feature is intended for expert users rather than normal users.
*/
public boolean isExpert() {
return expert;
}
/**
* @param expert
*/
public void setExpert(boolean expert) {
this.expert = expert;
}
/**
* @return true if this feature is mainly for tool use and should not be exposed to humans.
*/
public boolean isHidden() {
return hidden;
}
/**
* @param hidden
*/
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
/**
* @return true if this feature is particularly important for presenting to humans.
*/
public boolean isPreferred() {
return preferred;
}
/**
* @param preferred
*/
public void setPreferred(boolean preferred) {
this.preferred = preferred;
}
/**
* @param attributeName
* @return the value associated with the named attribute, or null if there is no such value.
*/
public String getAttribute(String attributeName) {
return attributes.get(attributeName);
}
/**
* @param attributeName
* @param value
*/
public void setAttribute(String attributeName, String value) {
attributes.put (attributeName, value);
}
/**
* @param attributeName
*/
public void clearAttribute(String attributeName) {
attributes.remove(attributeName);
}
/**
* The underlying collection cannot be modified through the iterator- this is for traversal purposes only.
* @return an Iterator (with String values) over all the named attributes associated with this feature.
*/
public Iterator<String> getAttributeNames(){
return Collections.unmodifiableSet(attributes.keySet()).iterator();
}
/**
* Clears all attributes of this metadata object
*/
public void clearAttributes() {
attributes.clear();
}
/**
* @param relatedFeature the feature name of the related feature to add
* @return boolean true if feature was added, false otherwise
*/
public boolean addRelatedFeature(CALFeatureName relatedFeature) {
return relatedFeatures.add(relatedFeature);
}
/**
* @param relatedFeature the feature name of the related feature to remove
* @return true if the feature was removed, false otherwise
*/
public boolean removeRelatedFeature(CALFeatureName relatedFeature) {
return relatedFeatures.remove(relatedFeature);
}
/**
* Clears the list of related features.
*/
public void clearRelatedFeatures() {
relatedFeatures.clear();
}
/**
* @return the number of related features
*/
public int getNRelatedFeatures() {
return relatedFeatures.size();
}
/**
* @param n the index of the related feature to get
* @return the feature name of the related feature
*/
public CALFeatureName getNthRelatedFeature(int n) {
return relatedFeatures.get(n);
}
/**
* @return gets the date-time at which this feature was created.
*/
public Date getCreationDate() {
return creationDate != null ? (Date) creationDate.clone() : null;
}
/**
* @param creationDate
*/
public void setCreationDate(Date creationDate) {
this.creationDate = creationDate != null ? (Date) creationDate.clone() : null;
}
/**
* @return gets the date-time at which this feature or its associated metadata was last modified.
*/
public Date getModificationDate() {
return modificationDate != null ? (Date) modificationDate.clone() : null;
}
/**
* @param modificationDate
*/
public void setModificationDate(Date modificationDate) {
this.modificationDate = modificationDate != null ? (Date) modificationDate.clone() : null;
}
/**
* @return gets the version number or version identifier of this feature.
*/
public String getVersion() {
return version;
}
/**
* @param version
*/
public void setVersion(String version) {
this.version = (version != null && version.trim().length() > 0) ? version : null;
}
/**
* @return gets the name of the author, organization or group associated with this feature.
*/
public String getAuthor() {
return author;
}
/**
* @param author
*/
public void setAuthor(String author) {
this.author = (author != null && author.trim().length() > 0) ? author : null;
}
/**
* @return a new copy of this metadata object.
*/
public CALFeatureMetadata copy() {
return copy(getFeatureName(), getLocale());
}
/**
* @return a new copy of this metadata object with the given featureName and locale.
*/
public abstract CALFeatureMetadata copy(CALFeatureName featureName, Locale locale);
/**
* Copies this metadata object into the given metadata object.
* @param metadata the metadata object to copy to
*/
public CALFeatureMetadata copyTo(CALFeatureMetadata metadata) {
metadata.setAuthor(author);
metadata.setVersion(version);
metadata.setDisplayName(displayName);
metadata.setShortDescription(shortDescription);
metadata.setLongDescription(longDescription);
metadata.setExpert(expert);
metadata.setHidden(hidden);
metadata.setPreferred(preferred);
metadata.setCreationDate(creationDate);
metadata.setModificationDate(modificationDate);
metadata.clearAttributes();
for (final String key : attributes.keySet()) {
String attribute = attributes.get(key);
metadata.setAttribute(key, attribute);
}
metadata.clearRelatedFeatures();
for (final CALFeatureName featureName : relatedFeatures) {
metadata.addRelatedFeature(featureName);
}
return metadata;
}
/**
* Obtain the namespace info for metadata.
* @return the metadata xml namespace info.
*/
static NamespaceInfo getNamespaceInfo() {
return new NamespaceInfo(MetadataPersistenceConstants.METADATA_NS, MetadataPersistenceConstants.METADATA_NS_PREFIX);
}
/**
* Saves this metadata object as a child of the given node.
* @param parentNode the node to save the metadata object to
*/
public void saveXML(Node parentNode) {
Document document = (parentNode instanceof Document) ? (Document) parentNode : parentNode.getOwnerDocument();
Element metadataElement = document.createElementNS(MetadataPersistenceConstants.METADATA_NS, MetadataPersistenceConstants.FEATURE_METADATA_TAG);
parentNode.appendChild(metadataElement);
// initialize the creation date if needed
if (creationDate == null) {
creationDate = new Date();
}
// update the modification date
modificationDate = new Date();
getResourceName().saveXML(metadataElement);
XMLPersistenceHelper.addTextElement(metadataElement, MetadataPersistenceConstants.DISPLAY_NAME_TAG, displayName);
XMLPersistenceHelper.addTextElement(metadataElement, MetadataPersistenceConstants.VERSION_TAG, version);
XMLPersistenceHelper.addTextElement(metadataElement, MetadataPersistenceConstants.AUTHOR_TAG, author);
XMLPersistenceHelper.addTextElement(metadataElement, MetadataPersistenceConstants.SHORT_DESCRIPTION_TAG, shortDescription);
XMLPersistenceHelper.addTextElement(metadataElement, MetadataPersistenceConstants.LONG_DESCRIPTION_TAG, longDescription);
XMLPersistenceHelper.addDateElement(metadataElement, MetadataPersistenceConstants.MODIFICATION_DATE_TAG, modificationDate);
XMLPersistenceHelper.addDateElement(metadataElement, MetadataPersistenceConstants.CREATION_DATE_TAG, creationDate);
XMLPersistenceHelper.addBooleanElement(metadataElement, MetadataPersistenceConstants.EXPERT_FEATURE_TAG, isExpert());
XMLPersistenceHelper.addBooleanElement(metadataElement, MetadataPersistenceConstants.HIDDEN_FEATURE_TAG, isHidden());
XMLPersistenceHelper.addBooleanElement(metadataElement, MetadataPersistenceConstants.PREFERRED_FEATURE_TAG, isPreferred());
saveAttributesXML(metadataElement);
saveRelatedFeaturesXML(metadataElement);
}
/**
* Saves the attributes section of this metadata object as a child of the given element.
* @param parentElement the element to save the attributes section to
*/
private void saveAttributesXML(Node parentElement) {
Document document = parentElement.getOwnerDocument();
Element sectionElement = document.createElement(MetadataPersistenceConstants.ATTRIBUTES_SECTION_TAG);
parentElement.appendChild(sectionElement);
for (Iterator<String> attributeNames = getAttributeNames(); attributeNames.hasNext(); ) {
String name = attributeNames.next();
String value = getAttribute(name);
// Add an attribute section element
Element element = document.createElement(MetadataPersistenceConstants.ATTRIBUTE_SECTION_TAG);
sectionElement.appendChild(element);
XMLPersistenceHelper.addTextElement(element, MetadataPersistenceConstants.ATTRIBUTE_KEY_TAG, name);
XMLPersistenceHelper.addTextElement(element, MetadataPersistenceConstants.ATTRIBUTE_VALUE_TAG, value);
}
}
/**
* Saves the related features section of this metadata object as a child of the given element.
* @param parentElement the element to save the related features to
*/
private void saveRelatedFeaturesXML(Node parentElement) {
Document document = parentElement.getOwnerDocument();
Element sectionElement = document.createElement(MetadataPersistenceConstants.RELATED_FEATURES_SECTION_TAG);
parentElement.appendChild(sectionElement);
Iterator<CALFeatureName> relatedFeatures = this.relatedFeatures.iterator();
while (relatedFeatures.hasNext()) {
CALFeatureName featureName = relatedFeatures.next();
featureName.saveXML(sectionElement);
}
}
/**
* Loads this metadata object from the given node.
* @param metadataNode the node from which to start loading
* @throws BadXMLDocumentException
*/
public void loadXML(Node metadataNode) throws BadXMLDocumentException {
XMLPersistenceHelper.checkIsTagElement(metadataNode, MetadataPersistenceConstants.FEATURE_METADATA_TAG);
List<Element> elements = XMLPersistenceHelper.getChildElements(metadataNode);
Iterator<Element> it = elements.iterator();
Node element = it.next();
ResourceName persistedResourceName = ResourceName.getResourceNameWithCALFeatureNameFromXML(element);
if (!persistedResourceName.getFeatureName().equals(getFeatureName())) {
throw new BadXMLDocumentException(element, "CAL feature name " + getFeatureName() + " does not match the persisted feature name");
}
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.DISPLAY_NAME_TAG);
displayName = XMLPersistenceHelper.getElementStringValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.VERSION_TAG);
version = XMLPersistenceHelper.getElementStringValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.AUTHOR_TAG);
author = XMLPersistenceHelper.getElementStringValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.SHORT_DESCRIPTION_TAG);
shortDescription = XMLPersistenceHelper.getElementStringValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.LONG_DESCRIPTION_TAG);
longDescription = XMLPersistenceHelper.getElementStringValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.MODIFICATION_DATE_TAG);
modificationDate = XMLPersistenceHelper.getElementDateValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.CREATION_DATE_TAG);
creationDate = XMLPersistenceHelper.getElementDateValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.EXPERT_FEATURE_TAG);
expert = XMLPersistenceHelper.getElementBooleanValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.HIDDEN_FEATURE_TAG);
hidden = XMLPersistenceHelper.getElementBooleanValue(element);
element = it.next();
XMLPersistenceHelper.checkIsTagElement(element, MetadataPersistenceConstants.PREFERRED_FEATURE_TAG);
preferred = XMLPersistenceHelper.getElementBooleanValue(element);
// Load the attributes
element = it.next();
loadAttributesXML(element);
// Load the related features
element = it.next();
loadRelatedFeaturesXML(element);
}
/**
* Loads the attributes section from the given node.
* @param metadataNode the node from which to start loading
* @throws BadXMLDocumentException
*/
private void loadAttributesXML(Node metadataNode) throws BadXMLDocumentException {
XMLPersistenceHelper.checkIsTagElement(metadataNode, MetadataPersistenceConstants.ATTRIBUTES_SECTION_TAG);
List<Element> attributeNodes = XMLPersistenceHelper.getChildElements(metadataNode, MetadataPersistenceConstants.ATTRIBUTE_SECTION_TAG);
for (final Element attributeNode : attributeNodes) {
List<Element> elements = XMLPersistenceHelper.getChildElements(attributeNode);
Element element = elements.get(0);
XMLPersistenceHelper.checkTag(element, MetadataPersistenceConstants.ATTRIBUTE_KEY_TAG);
String name = XMLPersistenceHelper.getElementStringValue(element);
element = elements.get(1);
XMLPersistenceHelper.checkTag(element, MetadataPersistenceConstants.ATTRIBUTE_VALUE_TAG);
String value = XMLPersistenceHelper.getElementStringValue(element);
setAttribute(name, value);
}
}
/**
* Loads the related features section from the given node.
* @param metadataNode the node from which to start loading
* @throws BadXMLDocumentException
*/
private void loadRelatedFeaturesXML(Node metadataNode) throws BadXMLDocumentException {
XMLPersistenceHelper.checkIsTagElement(metadataNode, MetadataPersistenceConstants.RELATED_FEATURES_SECTION_TAG);
List<Element> featureNodes = XMLPersistenceHelper.getChildElements(metadataNode);
for (final Element featureElement : featureNodes) {
CALFeatureName featureName = CALFeatureName.getFromXML(featureElement);
relatedFeatures.add(featureName);
}
}
}