/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.
*
* $Id: MetaData.java 511426 2007-02-25 03:25:02Z vgritsenko $
*/
package org.apache.xindice.core.meta;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.xml.TextWriter;
import org.apache.xindice.xml.XMLSerializable;
import org.apache.xindice.xml.dom.DocumentImpl;
import org.w3c.dom.Comment;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
/**
* Implements the MetaData interface.
*
* <pre>
* <meta>
* <system [type="doc|col"] [href="uri"]/>
* <attrs>
* <attr name="name" value="value"/>
* <attr name="name" value="value"/>
* </attrs>
* <custom>
* ...
* </custom>
* </meta>
* </pre>
*
* @author ku
* @author Dave Viner <dviner@apache.org>
* @version $Revision: 511426 $, $Date: 2007-02-24 22:25:02 -0500 (Sat, 24 Feb 2007) $
*/
public class MetaData implements XMLSerializable {
private static Log log = LogFactory.getLog(MetaData.class);
public final static short UNKNOWN = 0;
public final static short COLLECTION = 1;
public final static short DOCUMENT = 2;
public final static short LINK = 3;
public final static String NS_URI = "http://apache.org/xindice/metadata";
//public final static String NS_PREFIX = "md";
public final static boolean USE_NS = true;
private static final String E_META = "meta";
private static final String E_SYSTEM = "system";
private static final String E_ATTRS = "attrs";
private static final String E_ATTR = "attr";
private static final String E_CUSTOM = "custom";
private static final String E_CREATED = "created";
private static final String E_MODIFIED = "modified";
private static final String A_NAME = "name";
private static final String A_VALUE = "value";
private static final String A_TYPE = "type";
private static final String A_HREF = "href";
private static final Enumeration EMPTY =
(new Vector()).elements();
private transient boolean dirty;
private transient long created;
private transient long modified;
private transient String owner;
private short type = UNKNOWN;
private String link;
private Hashtable attrs;
private Document custom;
public MetaData() {
}
/**
* @param owner the owner of this metadata.
*/
public MetaData(String owner) {
this.owner = owner;
}
/**
* @param type the type of object for this metadata
* @param owner the owner of this metadata
* @param created the time when this was created
* @param modified the time when this was modified.
*/
public MetaData(short type, String owner, long created, long modified) {
this.type = type;
this.owner = owner;
this.created = created;
this.modified = modified;
}
/**
* @param elem an XML representation of the desired metadata.
*/
public MetaData(Element elem) {
streamFromXML(elem);
}
/**
* Returns whether this MetaData is dirty
*
* @return boolean
*/
public final boolean isDirty() {
return dirty;
}
/**
* Reset the dirtiness of this object
*
* @param dirty
*/
public final void setDirty(boolean dirty) {
this.dirty = dirty;
}
/**
* Returns the time of creation
* @return long creation time
*/
public long getCreatedTime() {
return created;
}
/**
* Returns the time of last modification
* @return long last modified time
*/
public long getLastModifiedTime() {
return modified;
}
/**
* Returns the type of the owner object, e.g., COLLECTION,
* DOCUMENT, or LINK
* @return short the object type
*/
public short getType() {
return type;
}
/**
* Returns the canonical name of the owner object.
* @return String the canonical name of the owner object
*/
public String getOwner() {
return owner;
}
/**
* Returns enumeration of all attributes
* @return Enumeration of attributes
*/
public Enumeration getAttributeKeys() {
if (null == attrs) {
return EMPTY;
}
return attrs.keys();
}
/**
* Retrieves the attribute by name
* @param name the attribute name
* @return String the value of attribute, or null if not found
*/
public Object getAttribute(final Object name) {
if (null == attrs) {
return null;
}
return attrs.get(name);
}
public Boolean getAttributeAsBoolean(final Object name) {
Object o = getAttribute(name);
if (null == o) {
return null;
}
if (o instanceof Boolean) {
return (Boolean) o;
}
if (o instanceof Number) {
Number n = (Number) o;
return new Boolean(n.intValue() != 0);
}
return Boolean.valueOf(o.toString());
}
public Integer getAttributeAsInteger(final Object name) {
Object o = getAttribute(name);
if (null == o) {
return null;
}
if (o instanceof Integer) {
return (Integer) o;
}
if (o instanceof Number) {
Number n = (Number) o;
return new Integer(n.intValue());
}
try {
int v = Integer.parseInt(o.toString());
return new Integer(v);
} catch (Exception e) {
return null;
}
}
public Long getAttributeAsLong(final Object name) {
Object o = getAttribute(name);
if (null == o) {
return null;
}
if (o instanceof Long) {
return (Long) o;
}
if (o instanceof Number) {
Number n = (Number) o;
return new Long(n.longValue());
}
try {
long v = Long.parseLong(o.toString());
return new Long(v);
} catch (Exception e) {
return null;
}
}
public Short getAttributeAsShort(final Object name) {
Object o = getAttribute(name);
if (null == o) {
return null;
}
if (o instanceof Short) {
return (Short) o;
}
if (o instanceof Number) {
Number n = (Number) o;
return new Short(n.shortValue());
}
try {
short v = Short.parseShort(o.toString());
return new Short(v);
} catch (Exception e) {
return null;
}
}
/**
* Sets the attribute by name
* @param name the attribute name
* @param value the attribute value
* @return Object the old value, if any
*/
public Object setAttribute(final Object name, final Object value) {
if (null == attrs) {
attrs = new Hashtable();
dirty = true;
}
Object prev = attrs.put(name, value);
if (prev == null || !prev.equals(value)) {
dirty = true;
}
return prev;
}
/**
* Remove an attribute by name
* @param name the attribute to remove
* @return Object the removed value
*/
public Object removeAttribute(final Object name) {
if (null == attrs) {
return null;
}
Object prev = attrs.remove(name);
if (null != prev) {
dirty = true;
}
return prev;
}
/**
* Returns the (optional) custom meta document
* @return Document the custom meta document
*/
public Document getCustomDocument() {
return custom;
}
/**
* Sets the (optional) custom meta document
* @param doc the Document
*/
public void setCustomDocument(Document doc) {
this.custom = doc;
this.dirty = true;
}
/**
* Copies from another meta data
* @param meta the meta to copy from
*/
public void copyFrom(final MetaData meta) {
this.created = meta.getCreatedTime();
this.modified = meta.getLastModifiedTime();
this.owner = meta.getOwner();
this.type = meta.getType();
copyDataFrom(meta);
}
/**
* Copies only user data (link, attributes, custom document) from another meta data
* @param meta the meta to copy from
*/
public void copyDataFrom(final MetaData meta) {
this.link = meta.getLinkTargetURI();
this.attrs = null;
for (Enumeration e = meta.getAttributeKeys(); e.hasMoreElements();) {
Object key = e.nextElement();
Object value = meta.getAttribute(key);
if (null == this.attrs) {
this.attrs = new Hashtable();
}
this.attrs.put(key, value);
}
this.custom = null;
Document doc = meta.getCustomDocument();
if (null != doc) {
this.custom = new DocumentImpl();
this.custom.appendChild(
this.custom.importNode(doc.getDocumentElement(), true));
}
this.dirty = false;
}
/**
* Returns the linked target URI, if this is a link
* @return String the URI of the linked target
*/
public String getLinkTargetURI() {
return link;
}
//
// Serialization to/from XML
//
/**
* Stream this MetaData into an xml document
* @param doc the xml document to be populated.
*/
public final Element streamToXML(Document doc) throws DOMException {
return streamToXML(doc, true);
}
/**
* Stream this MetaData into an xml document
* @param doc the xml document to populate
* @param includeTime whether or not to include the create/modified times
* @return Element the root element
*/
public final Element streamToXML(Document doc, boolean includeTime)
throws DOMException {
Element root = null;
if (!USE_NS) {
root = doc.createElement(E_META);
} else {
root = doc.createElementNS(NS_URI, E_META);
root.setAttribute("xmlns", NS_URI);
}
Element systemElement = null;
if (!USE_NS) {
systemElement = doc.createElement(E_SYSTEM);
} else {
systemElement = doc.createElementNS(NS_URI, E_SYSTEM);
}
systemElement.setAttribute(A_TYPE, getTypeString(type));
root.appendChild(systemElement);
if (includeTime) {
if (created > 0) {
String message = "Create time is " + (new Date(created));
Comment comment = doc.createComment(message);
systemElement.appendChild(comment);
}
Element timeElement = null;
if (!USE_NS) {
timeElement = doc.createElement(E_ATTR);
} else {
timeElement = doc.createElementNS(NS_URI, E_ATTR);
}
timeElement.setAttribute(A_NAME, E_CREATED);
timeElement.setAttribute(A_VALUE, Long.toString(created));
systemElement.appendChild(timeElement);
if (modified > 0) {
String message = "Modified time is " + (new Date(modified));
Comment comment = doc.createComment(message);
systemElement.appendChild(comment);
}
if (!USE_NS) {
timeElement = doc.createElement(E_ATTR);
} else {
timeElement = doc.createElementNS(NS_URI, E_ATTR);
}
timeElement.setAttribute(A_NAME, E_MODIFIED);
timeElement.setAttribute(A_VALUE, Long.toString(modified));
systemElement.appendChild(timeElement);
}
if (null != link) {
systemElement.setAttribute(A_HREF, link);
}
if (null != attrs && attrs.size() > 0) {
Element attrsElement = null;
if (!USE_NS) {
attrsElement = doc.createElement(E_ATTRS);
} else {
attrsElement = doc.createElementNS(NS_URI, E_ATTRS);
}
root.appendChild(attrsElement);
for (Enumeration e = attrs.keys(); e.hasMoreElements();) {
Object key = e.nextElement();
Object value = attrs.get(key);
if (null == key) {
continue;
}
Element attrElement = null;
if (!USE_NS) {
attrElement = doc.createElement(E_ATTR);
} else {
attrElement = doc.createElementNS(NS_URI, E_ATTR);
}
attrsElement.appendChild(attrElement);
attrElement.setAttribute(A_NAME, key.toString());
if (null != value) {
attrElement.setAttribute(A_VALUE, value.toString());
}
}
}
if (null != custom) {
Element customElement = null;
if (!USE_NS) {
customElement = doc.createElement(E_CUSTOM);
} else {
customElement = doc.createElementNS(NS_URI, E_CUSTOM);
}
customElement.appendChild(
doc.importNode(custom.getDocumentElement(), true));
root.appendChild(customElement);
}
return root;
}
/**
* Given some xml, populate the metadata.
* @param source The xml to populate metadata with
*/
public final void streamFromXML(Element source)
throws DOMException {
streamFromXML(source, true);
}
/**
* Given some xml, populate the metadata.
* @param source The xml to populate the metadata with
* @param includeTime Whether or not to reset the create/modified times
*/
public final void streamFromXML(Element source, boolean includeTime)
throws DOMException {
this.link = null;
this.attrs = null;
this.custom = null;
NodeList list = source.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
Node node = list.item(i);
if (node.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
Element element = (Element) node;
String elementName = element.getNodeName();
// String prefix = element.getPrefix();
if (E_SYSTEM.equals(elementName)) {
String attrStr = element.getAttribute(A_TYPE);
if (null != attrStr) {
this.type = parseTypeString(attrStr);
}
if (this.type == LINK) {
this.link = element.getAttribute(A_HREF);
}
if (includeTime) {
NodeList children = element.getChildNodes();
for (int j = 0; j < children.getLength(); j++) {
Node childNode = children.item(j);
if (!(childNode instanceof Element)) {
continue;
}
Element child = (Element) childNode;
String childName = child.getNodeName();
if (E_ATTR.equals(childName)) {
String nameStr = child.getAttribute(A_NAME);
String valueStr = child.getAttribute(A_VALUE);
if (null != nameStr && null != valueStr) {
if (E_CREATED.equals(nameStr)) {
this.created = Long.parseLong(valueStr);
} else if (E_MODIFIED.equals(nameStr)) {
this.modified = Long.parseLong(valueStr);
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("Ignorning unknown child elem " + childName);
}
}
}
}
} else if (E_ATTRS.equals(elementName)) {
NodeList attrList = element.getElementsByTagName(E_ATTR);
for (int j = 0; j < attrList.getLength(); j++) {
Element e = (Element) attrList.item(j);
String attrName = e.getAttribute(A_NAME);
if (null == attrName) {
continue;
}
if (null == this.attrs) {
this.attrs = new Hashtable();
}
this.attrs.put(attrName, e.getAttribute(A_VALUE));
}
} else if (E_CUSTOM.equals(elementName)) {
this.custom = new DocumentImpl();
Node custdoc = this.custom.importNode(element.getFirstChild(), true);
/* if you want really verbose debugging try this */
if (log.isTraceEnabled()) {
log.trace("Appending custom element " + elementName
+ "\nwhose complete original content is: \n" + TextWriter.toString(source)
+ "\nwhose original content is: \n" + TextWriter.toString(element)
+ "\nwhose imported content is: \n" + TextWriter.toString(custdoc));
}
this.custom.appendChild(custdoc);
} else {
// ignore
if (log.isDebugEnabled()) {
log.debug("ignoring unknown xml element " + elementName);
}
}
}
}
//
// Support methods
//
/**
* Return a string representing which type passed in.
* It will be either doc, col, link, none.
* @param type The type you want translated.
* @return String representing this type
*/
public static String getTypeString(short type) {
switch (type) {
case DOCUMENT:
return "doc";
case COLLECTION:
return "col";
case LINK:
return "link";
default:
return "none";
}
}
/**
* Return a short representing the string given.
* The string should be doc, col, or link.
* @param str The string to parse
* @return short The short for this string.
*/
public static short parseTypeString(final String str) {
if (str.equalsIgnoreCase("doc")) {
return DOCUMENT;
}
if (str.equalsIgnoreCase("col")) {
return COLLECTION;
}
if (str.equalsIgnoreCase("link")) {
return LINK;
}
return UNKNOWN;
}
//
// Modifiers
//
/**
* Determine whether this obect has created or modified times set
* @return boolean
*/
public boolean hasContext() {
return (created > 0 || modified > 0);
}
/**
* Set the created and modified times. If 0 value is passed,
* time is not changed.
*
* @param created the new creation time
* @param modified the new modified time
*/
public void setContext(long created, long modified) {
if (created > 0) {
this.created = created;
}
if (modified > 0) {
this.modified = modified;
}
}
/**
* Set the type for this object.
* If the type is already set to the value passed in, nothing is changed.
* @param type the type for this object.
*/
public void setType(short type) {
if (this.type != type) {
this.type = type;
this.dirty = true;
}
}
/**
* Set the owner of this object
* @param owner the owner to be ass
*/
public void setOwner(String owner) {
if (owner != this.owner ||
null == owner ||
null == this.owner ||
!owner.equals(this.owner)) {
this.owner = owner;
this.dirty = true;
}
}
public void setLinkTargetURI(String link) {
if (link != this.link ||
null == link ||
null == this.link ||
!link.equals(this.link)) {
this.link = link;
this.dirty = true;
}
}
/**
* Return a string representing this MetaData
* It will look like:
* META[doc owner=OWNER created=CREATED modified=MODIFIED link=LINK
* attrs=ATTRS]
*
* @return String
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("META[");
buffer.append(getTypeString(type));
if (owner != null) {
buffer.append(" owner=").append(owner);
}
if (created > 0) {
buffer.append(" created=").append(new Date(created));
}
if (modified > 0) {
buffer.append(" modified=").append(new Date(modified));
}
if (link != null) {
buffer.append(" link=").append(link);
}
if (attrs != null) {
buffer.append(" attrs=").append(attrs);
}
buffer.append("]");
return buffer.toString();
}
}