/* See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* Esri Inc. licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.esri.gpt.catalog.schema;
import com.esri.gpt.framework.jsf.MessageBroker;
import com.esri.gpt.framework.util.DateProxy;
import com.esri.gpt.framework.util.Val;
import com.esri.gpt.framework.xml.DomUtil;
import java.util.ArrayList;
import java.util.logging.Logger;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Content component associated with a metadata schema.
* <p/>
* The component is configured from a node with a schema configuration
* XML document.
* <p/>
* Example:<br/>
* <content select="/gmd:MD_Metadata/gmd:fileIdentifier/gco:CharacterString"/>
*/
public class Content extends Component {
// class variables =============================================================
/** Logger */
private static Logger LOGGER = Logger.getLogger(Content.class.getName());
/** GCO List node type = "gcoList" */
public static final String NODETYPE_GCOLIST = "gcoList";
/**
* ISO code list value = "isoCodeListValue"
* <p/>
* This node type is represented by a duplication of an ISO code in
* both a codeListValue attribute and the text node of the
* attributes's parent element.
*/
public static final String NODETYPE_ISOCODELISTVALUE = "isoCodeListValue";
/** List node type = "list" */
public static final String NODETYPE_LIST = "list";
/** Single node type = "single" (this is the default) */
public static final String NODETYPE_SINGLE = "single";
/** Single node type = "single" (this is the default) */
public static final String NODETYPE_PAIRRIGHTVALUE = "pairRightValue";
/** Single node type = "single" (this is the default) */
public static final String NODETYPE_PAIRLEFTVALUE = "pairLeftValue";
// instance variables ==========================================================
private Codes _codes;
private String _delete = "";
private boolean _deleteIfEmpty = false;
private boolean _deleteParentIfEmpty = false;
private ContentValues _multipleValues;
private String _nilReasonPath = "";
private String _nodeType = Content.NODETYPE_SINGLE;
private String _select = "";
private ContentValue _singleValue;
private String _update = "";
private boolean _useSelectForUpdate = false;
// constructors ================================================================
/** Default constructor. */
public Content() {
this(null);
}
/**
* Construct by duplicating an existing object.
* @param objectToDuplicate the object to duplicate
*/
public Content(Content objectToDuplicate) {
super(objectToDuplicate);
if (objectToDuplicate == null) {
setCodes(new Codes());
setSingleValue(new ContentValue());
setMultipleValues(new ContentValues());
} else {
setCodes(new Codes(objectToDuplicate.getCodes()));
setNodeType(objectToDuplicate.getNodeType());
setSingleValue(objectToDuplicate.getSingleValue().duplicate());
setMultipleValues(new ContentValues(objectToDuplicate.getMultipleValues()));
setSelect(objectToDuplicate.getSelect());
setUpdate(objectToDuplicate.getUpdate());
setDelete(objectToDuplicate.getDelete());
setNilReasonPath(objectToDuplicate.getNilReasonPath());
setUseSelectForUpdate(objectToDuplicate.getUseSelectForUpdate());
setDeleteIfEmpty(objectToDuplicate.getDeleteIfEmpty());
setDeleteParentIfEmpty(objectToDuplicate.getDeleteParentIfEmpty());
}
}
// properties ==================================================================
/**
* Gets the codes for this parameter.
* @return the value codes
*/
public Codes getCodes() {
return _codes;
}
/**
* Sets the codes for this parameter.
* @param codes the value codes
*/
protected void setCodes(Codes codes) {
_codes = codes;
if (_codes == null) _codes = new Codes();
}
/**
* Gets the XPath expression used to select nodes for deletion when an updated value is empty.
* @return the delete expression
*/
public String getDelete() {
return _delete;
}
/**
* Sets the XPath expression used to select nodes for deletion when an updated value is empty.
* @param expression the delete expression
*/
public void setDelete(String expression) {
_delete = Val.chkStr(expression);
}
/**
* Indicates if the node associated with the update expression should be
* deleted if the updated value is empty.
* @return true if the node should be deleted when the updated value is empty
*/
public boolean getDeleteIfEmpty() {
return _deleteIfEmpty;
}
/**
* Indicates if the node(s) associated with the update expression should be
* deleted if the updated value is empty.
* @param deleteIfEmpty true if the node should be deleted when the updated value is empty
*/
public void setDeleteIfEmpty(boolean deleteIfEmpty) {
_deleteIfEmpty = deleteIfEmpty;
}
/**
* Indicates if the parent node associated with the update expression should be
* deleted if the updated value is empty.
* @return true if the parent node should be deleted when the updated value is empty
*/
public boolean getDeleteParentIfEmpty() {
return _deleteParentIfEmpty;
}
/**
* Indicates if the node(s) associated with the update expression should be deleted if the
* updated value is empty.
* @param deleteIfEmpty true if the node should be deleted when the updated value is empty
*/
public void setDeleteParentIfEmpty(boolean deleteIfEmpty) {
_deleteParentIfEmpty = deleteIfEmpty;
}
/**
* Gets the multiple value list.
* @return the multiple value list
*/
public ContentValues getMultipleValues() {
return _multipleValues;
}
/**
* Sets the multiple value list
* @param values the multiple value list
*/
protected void setMultipleValues(ContentValues values) {
_multipleValues = values;
if (_multipleValues == null) _multipleValues = new ContentValues();
}
/**
* Gets the XPath expression used to a ISO nil-reason attribute associated with this element.
* @return the nil-reason expression
*/
public String getNilReasonPath() {
return _nilReasonPath;
}
/**
* Sets the XPath expression used to a ISO nil-reason attribute associated with this element.
* @param expression the nil-reason expression
*/
public void setNilReasonPath(String expression) {
_nilReasonPath = Val.chkStr(expression);
}
/**
* Gets the node type.
* @return the node type
*/
public String getNodeType() {
return _nodeType;
}
/**
* Sets the node type.
* @param type the node type
*/
public void setNodeType(String type) {
type = Val.chkStr(type);
if (type.equals("")) {
_nodeType = Content.NODETYPE_SINGLE;
} else if (type.equalsIgnoreCase(Content.NODETYPE_GCOLIST)) {
_nodeType = Content.NODETYPE_GCOLIST;
} else if (type.equalsIgnoreCase(Content.NODETYPE_ISOCODELISTVALUE)) {
_nodeType = Content.NODETYPE_ISOCODELISTVALUE;
} else if (type.equalsIgnoreCase(Content.NODETYPE_LIST)) {
_nodeType = Content.NODETYPE_LIST;
} else if (type.equalsIgnoreCase(Content.NODETYPE_SINGLE)) {
_nodeType = Content.NODETYPE_SINGLE;
} else if (type.equalsIgnoreCase(Content.NODETYPE_PAIRLEFTVALUE)) {
_nodeType = Content.NODETYPE_PAIRLEFTVALUE;
} else if (type.equalsIgnoreCase(Content.NODETYPE_PAIRRIGHTVALUE)) {
_nodeType = Content.NODETYPE_PAIRRIGHTVALUE;
}else {
_nodeType = Content.NODETYPE_SINGLE;
}
}
/**
* Gets the XPath selection expression.
* @return the selection expression
*/
public String getSelect() {
return _select;
}
/**
* Sets the XPath selection expression.
* @param expression the selection expression
*/
public void setSelect(String expression) {
_select = Val.chkStr(expression);
}
/**
* Gets the single value.
* @return the single value
*/
public ContentValue getSingleValue() {
return _singleValue;
}
/**
* Sets the single value.
* @param value the single value
*/
protected void setSingleValue(ContentValue value) {
_singleValue = value;
if (_singleValue == null) _singleValue = new ContentValue();
}
/**
* Gets the XPath expression used to select nodes for update.
* @return the update expression
*/
public String getUpdate() {
return _update;
}
/**
* Sets the XPath expression used to select nodes for update.
* @param expression the update expression
*/
public void setUpdate(String expression) {
_update = Val.chkStr(expression);
}
/**
* Gets the status indicating if the selection expression should
* be used for locating nodes during the update process.
* @return true if the selection expression should be used for update
*/
public boolean getUseSelectForUpdate() {
return _useSelectForUpdate;
}
/**
* Sets the status indicating if the selection expression should
* be used for locating nodes during the update process.
* @param useSelectForUpdate true if the selection expression
* should be used for update
*/
public void setUseSelectForUpdate(boolean useSelectForUpdate) {
_useSelectForUpdate = useSelectForUpdate;
}
// methods =====================================================================
/**
* Clears single and multiple values.
*/
public void clearAllValues() {
getSingleValue().clear();
getMultipleValues().clear();
}
/**
* Configures the object based upon a node loaded from a
* schema configuration XML.
* <br/>The super.configure method should be invoked prior to any
* sub-class configuration.
* <p/>
* The following attributes are configured:
* <br/>nodeType useSelectForUpdate deleteIfEmpty deleteParentIfEmpty select update delete nilReason
* <p/>
* The following child nodes are configured:
* <br/>codes
* @param context the configuration context
* @param node the configuration node
* @param attributes the attributes of the configuration node
*/
@Override
public void configure(CfgContext context, Node node, NamedNodeMap attributes) {
super.configure(context,node,attributes);
// configure attributes
setNodeType(DomUtil.getAttributeValue(attributes,"nodeType"));
setSelect(DomUtil.getAttributeValue(attributes,"select"));
setUpdate(DomUtil.getAttributeValue(attributes,"update"));
setDelete(DomUtil.getAttributeValue(attributes,"delete"));
setNilReasonPath(DomUtil.getAttributeValue(attributes,"nilReason"));
setUseSelectForUpdate(Val.chkBool(
DomUtil.getAttributeValue(attributes,"useSelectForUpdate"),false));
setDeleteIfEmpty(Val.chkBool(
DomUtil.getAttributeValue(attributes,"deleteIfEmpty"),false));
setDeleteParentIfEmpty(Val.chkBool(
DomUtil.getAttributeValue(attributes,"deleteParentIfEmpty"),false));
// configure codes
Node ndCodes = DomUtil.findFirst(node,"codes");
if (ndCodes != null) {
Node[] aryCodes = DomUtil.findChildren(ndCodes,"code");
for (Node ndCode: aryCodes) {
getCodes().add(context.getFactory().newCode(context,ndCode));
}
}
// check to see if the selection expression should be used during
// the update process
if (getUseSelectForUpdate()) {
setUpdate(getSelect());
}
}
/**
* Deletes nodes from a document based upon an XPath expression.
* @param dom the metadata document template for the schema
* @param xpath an XPath object configured with an appropriate
* Namespace context for the schema
* @param expression the expression to use to delete the nodes
* @throws XPathExpressionException if an expression fails
* @throws SchemaException if the update fails
*/
public static void deleteNodes(Document dom, XPath xpath, String expression)
throws XPathExpressionException {
expression = Val.chkStr(expression);
if (expression.length() > 0) {
NodeList nl = (NodeList)xpath.evaluate(expression,dom,XPathConstants.NODESET);
for (int i=0;i<nl.getLength();i++) {
Node node = nl.item(i);
if (node.getParentNode() != null) {
node.getParentNode().removeChild(node);
}
}
}
}
/**
* Produces a deep clone of the object.
* <br/>The duplication constructor is invoked.
* <br/>return new Content(this);
*/
public Content duplicate() {
return new Content(this);
}
/**
* Appends property information for the component to a StringBuffer.
* <br/>The method is intended to support "FINEST" logging.
* <br/>super.echo should be invoked prior appending any local information.
* @param sb the StringBuffer to use when appending information
*/
@Override
public void echo(StringBuffer sb) {
super.echo(sb);
sb.append(" nodeType=\"").append(getNodeType()).append("\"");
sb.append(" useSelectForUpdate=\"").append(getUseSelectForUpdate()).append("\"");
sb.append(" deleteIfEmpty=\"").append(getDeleteIfEmpty()).append("\"");
sb.append(" deleteParentIfEmpty=\"").append(getDeleteParentIfEmpty()).append("\"");
sb.append("\n select=\"").append(getSelect()).append("\"");
sb.append("\n update=\"").append(getUpdate()).append("\"");
sb.append("\n delete=\"").append(getDelete()).append("\"");
if (this.getNilReasonPath().length() > 0) {
sb.append("\n nilReason=\"").append(this.getNilReasonPath()).append("\"");
}
if ((getCodes().size() > 0)) {
sb.append("\n").append(getCodes());
}
if (isSingleValue()) {
sb.append("\n").append(getSingleValue());
} else {
sb.append("\n").append(getMultipleValues());
}
}
/**
* Evaluates the XPath select expression associated with a parameter.
* @param dom the metadata document
* @param xpath an XPath object configured with an appropriate
* Namespace context for the schema
* @param parameter the associated parameter
* @throws XPathExpressionException if an evaluation expression fails
*/
public void evaluate(Document dom, XPath xpath, Parameter parameter)
throws XPathExpressionException {
clearAllValues();
String sSelect = getSelect();
LOGGER.finer("Evaluating xpath: "+sSelect);
if (sSelect.length() > 0) {
// evaluate single or multiple node values
if (isSingleValue()) {
String sValue = xpath.evaluate(sSelect,dom);
getSingleValue().setValue(sValue);
} else {
ContentValues values = getMultipleValues();
NodeList nl = (NodeList)xpath.evaluate(sSelect,dom,XPathConstants.NODESET);
for (int i=0;i<nl.getLength();i++) {
String sValue = nl.item(i).getTextContent();
values.add(new ContentValue(sValue));
}
}
}
// check for an ISO nil-reason
parameter.getValidation().setNilReasonValue("");
String sNil = this.getNilReasonPath();
if (sNil.length() > 0) {
LOGGER.finer("Evaluating nilReason xpath: "+sNil);
parameter.getValidation().setNilReasonValue(xpath.evaluate(sNil,dom));
}
}
/**
* Formats a value associated with a parameter.
* <p/>
* Currently, only Date type values are formatted.
* @param parameter the associated parameter
* @param value the value to format
*/
public String formatValue(Parameter parameter, String value) {
value = Val.chkStr(value);
if (value.length() > 0) {
if (parameter.getValidation().getValueType().equals(Validation.VALUETYPE_DATE)) {
DateProxy dp = new DateProxy();
dp.setDate(value);
value = (dp.getDate());
}
}
return value;
}
/**
* Determines if the node type is of single value.
* @return true if the node type is a single value type
*/
public boolean isSingleValue() {
return !getNodeType().equals(Content.NODETYPE_GCOLIST) &&
!getNodeType().equals(Content.NODETYPE_LIST);
}
/**
* Determines if the node value(s) is/are empty.
* @return true if the node node value(s) is/are empty
*/
public boolean isValueEmpty() {
boolean bIsEmpty = true;
if (isSingleValue()) {
bIsEmpty = (getSingleValue().getValue().length() == 0);
} else {
for (ContentValue value: getMultipleValues()) {
if (value.getValue().length() > 0) {
bIsEmpty = false;
break;
}
}
}
return bIsEmpty;
}
/**
* Makes the display value for a parameter.
* <p/>
* The output component is suitable for display on the
* metadata details page.
* @param messageBroker the message broker
* @param parameter the associated parameter
* @return the UI input component
*/
public String makeDisplayValue(MessageBroker messageBroker, Parameter parameter) {
Codes codes = getCodes();
if (isSingleValue()) {
String sValue = formatValue(parameter,getSingleValue().getValue());
sValue = codes.lookupDisplayValue(messageBroker,sValue);
return sValue;
} else {
StringBuffer sb = new StringBuffer();
String sDelimiter = ",";
for (ContentValue value: getMultipleValues()) {
String sValue = Val.chkStr(value.getValue());
sValue = codes.lookupDisplayValue(messageBroker,sValue);
if (sValue.length() > 0) {
if (sb.length() > 0) {
sb.append(sDelimiter).append(" ");
}
sb.append(sValue);
}
}
return sb.toString();
}
}
/**
* Generates a String array of value(s) associated with the parameter.
* @return the String array of values
*/
public String[] toValueArray() {
ArrayList<String> alValues = new ArrayList<String>();
if (isSingleValue()) {
alValues.add(getSingleValue().getValue());
} else {
for (ContentValue value: getMultipleValues()) {
alValues.add(value.getValue());
}
}
return alValues.toArray(new String[0]);
}
/**
* Updates the metadata document based upon entered parameter value(s).
* <p/>
* @param dom the metadata document template for the schema
* @param xpath an XPath object configured with an appropriate
* Namespace context for the schema
* @param parameter the associated parameter
* @throws XPathExpressionException if an expression fails
* @throws SchemaException if the update fails
*/
public void update(Document dom, XPath xpath, Parameter parameter)
throws XPathExpressionException, SchemaException {
// get the update expression, throw an exception if empty
String sUpdate = getUpdate();
LOGGER.finer("Updating xpath: "+sUpdate);
if (sUpdate.length() == 0) {
} else if (isSingleValue()) {
// get the value to set, search for the node to update
String sValue = getSingleValue().getValue();
Node node = (Node)xpath.evaluate(sUpdate,dom,XPathConstants.NODE);
// throw an exception if the node to update was not located
// within the template
if (node == null) {
} else {
// update the node
if (node.getNodeType() == Node.ELEMENT_NODE) {
if (getNodeType().equals(Content.NODETYPE_PAIRLEFTVALUE)) {
String sTextContent = Val.chkStr(node.getTextContent());
int spaceIndx = sTextContent.indexOf(' ');
if((spaceIndx == -1) && (sTextContent.length()>0)){
sValue = sValue + " " + sTextContent;
}
} else if(getNodeType().equals(Content.NODETYPE_PAIRRIGHTVALUE)){
String sTextContent = Val.chkStr(node.getTextContent());
int spaceIndx = sTextContent.indexOf(' ');
if((spaceIndx == -1) && (sTextContent.length()>0)){
sValue = sTextContent + " " + sValue;
}
}
node.setTextContent(sValue);
} else if (node.getNodeType() == Node.TEXT_NODE) {
node.setNodeValue(sValue);
} else if (node.getNodeType() == Node.ATTRIBUTE_NODE) {
node.setNodeValue(sValue);
// for an ISO code list value
// attempt to set the text node of the parent element
// to the code value
if (getNodeType().equals(Content.NODETYPE_ISOCODELISTVALUE)) {
if (node instanceof Attr) {
Attr attr = (Attr)node;
if (attr.getOwnerElement() != null) {
attr.getOwnerElement().setTextContent(sValue);
}
}
}
}
// delete if required
if (isValueEmpty() && getDeleteParentIfEmpty()) {
if (node instanceof Attr) {
Node ndToDelete = ((Attr)node).getOwnerElement();
ndToDelete.getParentNode().removeChild(ndToDelete);
} else {
node.getParentNode().getParentNode().removeChild(node.getParentNode());
}
} else if (isValueEmpty() && getDeleteIfEmpty()) {
if (node instanceof Attr) {
((Attr)node).getOwnerElement().removeAttributeNode((Attr)node);
} else {
node.getParentNode().removeChild(node);
}
}
}
} else {
Node node = (Node)xpath.evaluate(sUpdate,dom,XPathConstants.NODE);
// throw an exception if the node to update was not located
// within the template
if (node == null) {
} else {
Node ndToClone = node;
if (node instanceof Attr) {
Attr attr = (Attr)node;
ndToClone = attr.getOwnerElement();
} else if (getNodeType().equals(Content.NODETYPE_GCOLIST)) {
ndToClone = node.getParentNode();
}
Node ndInsertBefore = ndToClone.getNextSibling();
Node ndParent = ndToClone.getParentNode();
ArrayList<String> alValues = new ArrayList<String>();
int nCount = 0;
for (ContentValue value: getMultipleValues()) {
if (value.getValue().length() > 0) {
nCount++;
alValues.add(value.getValue());
if (nCount > 1) {
Node ndCloned = ndToClone.cloneNode(true);
ndParent.insertBefore(ndCloned,ndInsertBefore);
}
}
}
NodeList nl = (NodeList)xpath.evaluate(sUpdate,dom,XPathConstants.NODESET);
for (int i=0;i<nl.getLength();i++) {
String sValue = "";
if (i < alValues.size()) {
sValue = alValues.get(i);
}
Node ndActive = nl.item(i);
ndActive.setTextContent(sValue);
}
// delete if required
if (isValueEmpty() && getDeleteParentIfEmpty()) {
node.getParentNode().getParentNode().removeChild(node.getParentNode());
} else if (isValueEmpty() && getDeleteIfEmpty()) {
node.getParentNode().removeChild(node);
}
}
}
// delete if required
if (isValueEmpty() && (getDelete().length() > 0)) {
deleteNodes(dom,xpath,getDelete());
}
}
}