blob: 9ccebd97f7aa1fe4f2e042e8f3e7afaabf077937 [file] [log] [blame]
/*
* 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.
*/
package org.apache.vinci.transport;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.vinci.debug.Debug;
import org.apache.vinci.transport.util.XMLConverter;
/**
* Frame is an abstract class that is intended to be extended to implement a simple & lean
* (restricted) XML document model. A Frame is only capable of representing XML documents with no
* attributes or processing instructions. Applications which require attributes should use the
* org.apache.vinci.transport.document.AFrame document model instead.
*
* Frame "decorates" its descendents with several type-safe adder methods for building the XML
* document. It requires its descendents implement only a single generic adder method [add(String,
* FrameComponent)] and a getter for retreiving fields of the document by their position
* [getKeyValuePair(int)]. The Frame descendent QueryableFrame provides additional getter methods.
* QueryableFrame should be extended instead of Frame if this more powerful query support is
* necessary.
*
* Frame also implements the Transportable interface and provides a default marshaller for
* converting to and from XTalk wire format. The marshaller is pluggable to support marshalling to
* and from other formats if so desired, or to support optimized (e.g. native) implementation.
*
* Typically you will use VinciFrame, a concrete descendent of the (Queryable)Frame class, for most
* of your code. You may however also wish to implement specialized Frame descendents for optimized
* and/or type-safe handling of particular queries. For example, see ResolveResult and ServeonResult
* (in package org.apache.vinci.transport.vns.client), which do this for the two most common VNS
* queries. Automated stub generators may also wish to extend Frame to generate Java object to Vinci
* XML document adapters.
*
*/
public abstract class Frame extends FrameComponent implements Transportable {
static private final String XML_INDENT = " ";
static private FrameTransporter parser = new XTalkTransporter();
protected Frame() {
}
/**
* This method lets you replace the default XTalk marshaller with another one, for example if you
* want to use a different wire format (such as XML/SOAP), or if you want to easily exploit an
* optimized/native implementation.
*
* @param transporter
* The new marshaller to plug in.
*/
static public void setFrameTransporter(FrameTransporter transporter) {
parser = transporter;
}
/**
* Get the currently installed document marshaller.
*
* @return The currently installed document marshaller.
*/
static public FrameTransporter getFrameTransporter() {
return parser;
}
/**
* Add a tagged value to this frame (value is either a Frame or FrameLeaf). Frames that support
* marshalling from a stream must implement this method.
*
* While the method is conceptually abstract, I provide a default error-only implementation in
* cases where marshalling the document to a stream is not necessary (e.g. the document is used as
* an input source only).
*
* @param tag
* The tag name with which to associate the value.
* @param val
* The (Frame | FrameLeaf) value to associate with the tag.
* @throws UnsupportedOperationException not implemented
*/
/* abstract */public void add(String tag, FrameComponent val) {
throw new UnsupportedOperationException("not implemented");
}
/**
* Return the specified KeyValue pair. Frames that support marshalling to (but not from) a stream
* via the Transportable interface must implement this method. We chose this simple iteration
* method over providing an interator object since it doesn't require allocating any new objects.
*
* While conceptually abstract, I provide a default error-only implementation to avoid the need to
* define it in cases where alternative getters are provided for querying the document, and the
* object is never marshalled to a stream.
*
* @param which
* The index of the KeyValuePair to retrieve.
* @throws UnsupportedOperationException not implemented
* @return The requested KeyValuePair.
*/
/* abstract */public KeyValuePair getKeyValuePair(int which) {
throw new UnsupportedOperationException("not implemented");
}
/**
* Return the number of key/value pairs within this frame. Frames that support marshalling to (but
* not from) a stream via the Transportable interface must implement this method.
*
* While conceptually abstract, I provide a default error-only implementation to avoid the need to
* define it in cases where alternative getters are provided for querying the document, and the
* object is never marshalled to a stream.
*
* @throws UnsupportedOperationException not implemented
* @return The total number of key/value pairs in this frame.
*/
/* abstract */public int getKeyValuePairCount() {
throw new UnsupportedOperationException("not implemented");
}
/**
* Populate this document using the given InputStream and the installed marshaller.
*
* @param is
* The input stream to read from.
* @throws IOException
* Can come from the underlying input stream.
* @throws UnsupportedOperationException
* if this document model does not support key addition.
* @return The first key/value pair encountered, if it comes from the Vinci namespace.
*
* @pre is != null
*/
public KeyValuePair fromStream(InputStream is) throws IOException, EOFException {
return parser.fromStream(is, this);
}
/**
* Write this document to the given output stream using the installed marshaller.
*
* @param os
* The stream to where the document is written.
* @throws IOException
* Can come from the underlying output stream.
* @throws UnsupportedOperationException
* if this document model does not support key iteration.
*
* @pre os != null
*/
public void toStream(OutputStream os) throws IOException {
parser.toStream(os, this);
}
/**
* Represent the document as a string (equivlent to toXML()).
*
* @return The document in String format.
* @throws UnsupportedOperationException
* if this document model does not support key iteration.
*/
public String toString() {
return toXML();
}
/**
* Convert the document to XML. Performs limited pretty printing -- if you don't want any pretty
* printing, use toRawXML().
*
* @return The document in XML format as a string.
* @throws UnsupportedOperationException
* if this document model does not support key iteration.
*/
public String toXML() {
return toXML(new StringBuffer()).toString();
}
/**
* Convert the document to XML without any pretting printing.
*
* @return The document in XML format as a string.
* @throws UnsupportedOperationException
* if this document model does not support key iteration.
* @since 2.1.2
*/
public String toRawXML() {
return toRawXML(new StringBuffer()).toString();
}
/**
* Convert the document to XML without any pretting printing.
*
* @param buf
* The StringBuffer to append the document text to.
* @return The StringBuffer that was provided as input
* @throws UnsupportedOperationException
* if this document model does not support key iteration.
* @since 2.1.2
*/
public StringBuffer toRawXML(StringBuffer buf) {
buf.append("<vinci:FRAME").append(attributeString(this));
if (getAttributes() == null || getAttributes().fgetFirst("xmlns:vinci") == null) {
// Make sure vinci namespace declaration exists since it is implicitly assumed.
buf.append(" xmlns:vinci=\"").append(TransportConstants.VINCI_NAMESPACE_URI).append('\"');
}
buf.append(">");
toRawXMLWork(buf);
buf.append("</vinci:FRAME>");
return buf;
}
public void toRawXMLWork(StringBuffer rval) {
KeyValuePair keyVal = null;
int total = getKeyValuePairCount();
for (int i = 0; i < total; i++) {
keyVal = getKeyValuePair(i);
boolean pcdata = TransportConstants.PCDATA_KEY.equals(keyVal.key);
if (!pcdata) {
rval.append('<').append(keyVal.key).append(attributeString(keyVal.value));
if (keyVal.value instanceof Frame && ((Frame) keyVal.value).getKeyValuePairCount() == 0) {
rval.append("/>");
continue;
} else {
rval.append('>');
}
}
if (keyVal.value instanceof FrameLeaf) {
XMLConverter.convertStringToXMLString(((FrameLeaf) keyVal.value).toString(), rval);
} else {
((Frame) keyVal.value).toRawXMLWork(rval);
}
if (!pcdata) {
rval.append("</").append(keyVal.key).append('>');
}
}
}
/**
* Append the document, in XML format, to the provided StringBuffer.
*
* @param buf
* The StringBuffer where the document is appended.
* @return The same StringBuffer provided to this method using the buf argument.
* @throws UnsupportedOperationException
* if this document model does not support key iteration.
*
* @pre buf != null
*/
public StringBuffer toXML(StringBuffer buf) {
buf.append("<vinci:FRAME").append(attributeString(this));
if (getAttributes() == null || getAttributes().fgetFirst("xmlns:vinci") == null) {
// Make sure vinci namespace declaration exists since it is implicitly assumed.
buf.append(" xmlns:vinci=\"").append(TransportConstants.VINCI_NAMESPACE_URI).append('\"');
}
buf.append(">\n");
// ^^ Note that Frames do not support XML namespaces, however the above xml
// namespace is in some sense "implicit" for every Vinci document.
toXML(buf, 0);
buf.append("</vinci:FRAME>\n");
return buf;
}
/**
* Factory method used by fromStream when it needs to create a frame leaf. Default implementation
* creates a regular FrameLeaf.
*
* @param array -
* @return the created FrameLeaf.
*
* @pre array != null
*/
public FrameLeaf createFrameLeaf(byte[] array) {
return new FrameLeaf(array, false);
}
/**
* Factory method used by fromStream when it needs to create a sub-frame. Default implementation
* creates a subframe of the same type as the current frame.
* @param tag_name -
* @param initialCapacity -
* @return the created sub-frame.
* @throws UnsupportedOperationException
* if the getClass().newInstance() call on this object results in an exception.
*
* @pre tag_name != null
* @pre initialCapacity &ge; 0
*/
public Frame createSubFrame(String tag_name, int initialCapacity) {
try {
return (Frame) this.getClass().newInstance();
}
// Neither error case should take place, so we handle them by converting them to
// unchecked exceptions.
catch (Exception e) {
Debug.reportException(e);
throw new UnsupportedOperationException("createSubFrame() failed: " + e);
}
}
/**
* Helper method used by toXML()
*
* @pre c != null
*/
private String attributeString(FrameComponent c) {
Attributes attributes = c.getAttributes();
if (attributes == null) {
return "";
}
StringBuffer buf = new StringBuffer(80);
for (int i = 0; i < attributes.getKeyValuePairCount(); i++) {
KeyValuePair k = attributes.getKeyValuePair(i);
buf.append(" ");
buf.append(k.key);
buf.append("=\"");
XMLConverter.simpleConvertStringToXMLString(k.value.toString(), buf);
buf.append('\"');
}
return buf.toString();
}
/**
* Helper method for toXML(StringBuffer).
*
* @pre rval != null
* @pre offset &ge; 0
* @param rval -
* @param offset -
*/
protected void toXML(StringBuffer rval, int offset) {
KeyValuePair keyVal = null;
int total = getKeyValuePairCount();
for (int i = 0; i < total; i++) {
keyVal = getKeyValuePair(i);
for (int j = 0; j <= offset; j++) {
rval.append(XML_INDENT);
}
boolean pcdata = TransportConstants.PCDATA_KEY.equals(keyVal.key);
if (!pcdata) {
rval.append('<').append(keyVal.key).append(attributeString(keyVal.value));
if (keyVal.value instanceof Frame && ((Frame) keyVal.value).getKeyValuePairCount() == 0) {
rval.append("/>\n");
continue;
} else {
rval.append('>');
}
}
if (keyVal.value instanceof FrameLeaf) {
XMLConverter.convertStringToXMLString(((FrameLeaf) keyVal.value).toString(), rval);
} else {
rval.append('\n');
((Frame) keyVal.value).toXML(rval, offset + 1);
for (int j = 0; j <= offset; j++) {
rval.append(XML_INDENT);
}
}
if (!pcdata) {
rval.append("</").append(keyVal.key).append('>');
}
rval.append('\n');
}
}
/**
* Decorator method for adding float-valued tags.
*
* @param key
* The key to be associated with the value.
* @param val -
* @return This frame
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, float val) {
add(key, new FrameLeaf(val));
return this;
}
/**
* Decorator method for adding float-array valued tags.
*
* @param key
* The key to be associated with the value.
* @param val -
* @return This frame
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, float[] val) {
if (val != null) {
add(key, new FrameLeaf(val));
}
return this;
}
/**
* Decorator method for adding double valued tags.
*
* @param key
* The key to be associated with the value.
* @param val -
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
* @return This frame
*
* @pre key != null
*/
public Frame fadd(String key, double val) {
add(key, new FrameLeaf(val));
return this;
}
/**
* Decorator method for adding double-array valued tags. Adding a null array results in a no-op.
*
* @param key
* The key to be associated with the value.
* @param val
* The array to add. The array is immediately converted to string representation.
* @return This frame
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, double[] val) {
if (val != null) {
add(key, new FrameLeaf(val));
}
return this;
}
/**
* Decorator method for adding int valued tags.
*
* @param key
* The key to be associated with the value.
* @param val
* The int to add.
* @return This frame
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, int val) {
add(key, new FrameLeaf(val));
return this;
}
/**
* Decorator method for adding int-array valued tags. Adding a null value results in a no-op.
*
* @param key
* The key to be associated with the value.
* @param val
* The array to add. The array is immediately converted to string representation.
* @return This frame
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, int[] val) {
if (val != null) {
add(key, new FrameLeaf(val));
}
return this;
}
/**
* Decorator method for adding long valued tags.
*
* @param key
* The key to be associated with the value.
* @param val
* The long value to add.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, long val) {
add(key, new FrameLeaf(val));
return this;
}
/**
* Decorator method for adding long-array valued tags. Adding a null value results in a no-op.
*
* @param key
* The key to be associated with the value.
* @param val
* The array to add. The array is immediately converted to string representation.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, long[] val) {
if (val != null) {
add(key, new FrameLeaf(val));
}
return this;
}
/**
* Decorator method for adding String valued tags.
*
* @param key
* The key to be associated with the value.
* @param val
* The string to add.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, String val) {
if (val != null) {
add(key, new FrameLeaf(val));
}
return this;
}
/**
* Decorator method for adding String-array valued tags. Adding a null value results in a no-op.
*
* This implementation will use the '#' char as the string separator. If the '#' char is used in
* some string, then the implementation generates a separator that is not contained by any string
* and prepends the separator to the string for extraction. The generated separator always will
* begin and end with the character '#'. This allows arbitrary string arrays to be sent as a
* single string without separator conflicts.
*
* @param key
* The key to be associated with the value.
* @param val
* The string to add.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
* @return This frame.
*
* @pre key != null
* @pre { for (int i = 0; i &lt; val.length; i++) $assert(val[i] != null, "array elements are
* non-null"); }
*/
public Frame fadd(String key, String[] val) {
if (val != null) {
add(key, new FrameLeaf(val));
}
return this;
}
/**
* Decorator method for adding binary valued tags. Encodes the data in Base64. Adding a null value
* results in a no-op.
*
* @param key
* The key to be associated with the value.
* @param val
* The binary data to add (will be Base64 encoded).
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, byte[] val) {
if (val != null) {
add(key, new FrameLeaf(val, true));
}
return this;
}
/**
* Decorator method for adding boolean valued tags.
*
* @param key
* The key to be associated with the value.
* @param val
* The boolean value to add.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, boolean val) {
add(key, new FrameLeaf(val));
return this;
}
/**
* Decorator method for adding Frame-valued tags. Adding a null value results in a no-op.
*
* @param key
* The key to be associated with the value.
* @param val
* The sub-frame to add. Note this frame is not copied.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame fadd(String key, Frame val) {
if (val != null) {
add(key, val);
}
return this;
}
/**
* Decorator method for adding a valueless tag.
*
* @param key
* The key name.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition, or creation of empty sub-frames.
*
* @pre key != null
*/
public Frame fadd(String key) {
add(key, createSubFrame(key, 0));
return this;
}
/**
* This is a hack method which allows you to add binary-valued tags to Frames in a manner such
* that there is no textual encoding overhead of that binary data. This is NOT necessarily
* XTalk-1.0 compatible which formally requires only UTF-8, but it still works. Binary data added
* using this method can be retrieved using QueryableFrame/VinciFrame getter method
* fgetTrueBinary(String). Adding a null value results in a no-op.
*
* WARNING: if the default XTalk parser is replaced with another one, applications that depend on
* this method may break!
*
* WARNING #2: This method should only be used when performance hit of Base64 encoding binary data
* [as performed by fadd(String, byte[])] is unacceptable.
*
* WARNING #3: The byte array is NOT copied, thus it is up to the caller to ensure that the byte
* array cannot be modified by external code after passed in to this object.
*
* @param key
* The key to be associated with the value.
* @param val
* The byte array to be added to the frame. Note the array is NOT copied or converted in
* any way.
* @return This frame.
* @throws UnsupportedOperationException
* if this document model doesn't support key addition.
*
* @pre key != null
*/
public Frame faddTrueBinary(String key, byte[] val) {
if (val != null) {
add(key, new FrameLeaf(val, false));
}
return this;
}
}