On Mon, Jan 5, 2009 at 00:05, Henning Schmiedehausen (JIRA)
<[email protected]> wrote:
> Stax based parser for GadgetSpec and MessageBundle
> --------------------------------------------------
>
> Key: SHINDIG-816
> URL: https://issues.apache.org/jira/browse/SHINDIG-816
> Project: Shindig
These are my release notes. It is probably easier to comment inline
than through a JIRA attachment
Stax-based parser for Shindig
=============================
Outline
=======
- Full fledged STAX based parser to parse gadget spec elements
- works with the JDK 6 Stax parser and woodstox
- reads and writes gadget spec files. Namespace aware, can register
multiple namespaces and pass non-parsed elements through.
- mostly feature and bug compatible to the existing parser (see below)
- passes all unit tests, adds a few more
Known Problems
==============
- Uses arbitrary
http://ns.opensocial.org/specs/0.8/ as namespace identifier for the gadgets
http://ns.opensocial.org/messagebundle/0.8/ as namespace identifier
for the message bundles.
- misses most of the documentation; will be added once it gets close
to inclusion
Changes to the code base
========================
Maven Build
-----------
- Add wstx-asl 3.2.7 as dependency, replacing wstx-1.0.7 needed by
some unit tests.
Common Code
-----------
modifies org.apache.shindig.common.uri.Uri:
- adds Constant "EMPTY_URI"
- represents Uri.parse("")
- adds static Method toUri(String value, Uri defaultValue)
- converts String to Uri, if this fails, returns the defaultValue
- adds Method isHttpUri()
- returns true if scheme is http or https
adds org.apache.shindig.common.util.Pair:
- Immutable container to hold two values.
- is handy in many places, used by the UserPref Enums.
Gadget Code
===========
- DefaultGadgetSpecFactory split into two classes,
AbstractGadgetSpecFactory and ShindigGadgetSpecFactory
- AbstractGadgetSpecFactory contains the caching code, requires a
buildGadgetSpec(Uri, String) to be implemented.
- TODO: Removed the rawxml shortcut in
getGadgetSpec(GadgetContext), looked like cruft - Still needed
somewhere?
- TODO: Make the Gadget XML streamable into the parser from cache / fetcher
- All Gadget spec elements are represented by a SpecElement derived class
- Gadget spec: ShindigGadgetSpec as the root, ModulePrefs, Content,
EnumValue, Feature (Optional, Require), FeatureParam, Icon,
LinkSpec, LocaleMsg, LocaleSpec, OAuthAuthorization, OAuthElement
(OAuthAccess, OAuthRequest), OAuthService, OAuthSpec, Preload,
UserPref).
- MessageBundle spec: MessageBundleSpec Spec and LocaleMsg
- All gadget spec element are immutable when they are handed out (by
their parsers) (see below)
- All GadgetElements treat three types of attributes:
- Attributes processed by the element: Registered using
register(ATTR_XXX, ...)
- Attributes in the same namespace as the Element, but not registered are
passed through "as is"
- Attributes in another namespace are passed through "as is"
- The latter two types are processed by code in SpecElement
- Type parsers (AuthType, Content.Type, Icon.Mode,
MessageBundle.Direction, OAuthElement.Location, OAuthElement.Method)
all tolerate whitespace padding around their values.
Known bugs / deviations from the spec
=====================================
- The parser requires (similar to the DOM parser), that a ModulePrefs
element is present. This is actually not required by the GadgetSpec;
this is a valid gadget spec:
<?xml version="1.0" encoding="UTF-8"?>
<Module><Content/></Module>
Some places inside Shindig assume that ModulePrefs is always
present, so rewriting this requires rewrites of the code beyond the
parser.
Shindig fails on parsing spec compliant gadgets without ModulePrefs.
- The gadget spec actually differs between the gadget.xsd and
gadget-extended.xsd for the param_location values. gadgets.xsd
accepts "auth-header, uri-query, post-body", gadget-extended.xsd
accepts "header, url, body". In fact, no gadget that uses oauth can
comply to 2) of the rendering description of
http://www.opensocial.org/Technical-Resources/opensocial-spec-v08/gadget-spec.
It
is impossible that a gadget complies to the gadget-extended.xsd
using header,url oder body and then the parser (who only needs to
parse what is in the canonical spec) gets the values right.
The parser accepts all values from both specs.
In fact, a gadget *must* violate the gadget-extended.xsd by using
the values from the gadget.xsd.
- The gadget spec defines only a "name" attribute for the "OAuth", not
for the "service" element inside OAuth (where it would make
sense). The Shindig parser assumes that the "name" attribute is part
of the service element, not the OAuth element.
Actually a gadget that has a "name" attribute for service elements
is not spec compliant.
- Shindig only manages a single OAuth child for ModulePrefs. This
behaviour has been copied by the parser, however, according to the
gadget-extended.xsd, there can be an arbitrary number of elements
(you should probably not wrap a 0..1 definition into a 0..n
definition.
The parser tries to adhere to the intention, not the spec; however
this makes it non-spec-compliant.
- According to the mailing list, the gadget spec requires arbitrary
elements on the Preload element (there even is a test in PreloadTest
to ensure this (arbitraryAttributes).
However, the spec does not allow it (there is an <xs:anyAttributes
missing), so this actually makes Shindig (and all gadgets using it)
non-spec compliant.
- Content element understands "authz", "quirks", "sign_owner",
"sign_viewer".
These are actually not present in the gadget spec, so a gadget using
these attributes is non-spec-compliant (again, an <xs:anyAttributes>
is missing).
- LinkSpec "rel" element is treated as a string, not an Enum. The xsd
is unclear here. Actually, the gadget.xsd and the gadget-extended.xsd
are both syntactically wrong here (they don't validate in an XML
Schema editor).
The parser tries to do the right thing. However, it is again,
non-spec-compliant.
- MessageBundle accepts "language_direction" element like the Locale
elements in the gadget spec. This is more a programming quirk than
anything else, because it makes not much sense (the language
direction is always on the Locale element, which in turn references
the MessageBundle. Having a single message bundle without a locale
is not possible (at least not in the gadget context). The spec
does not allow this element, so the default (LTR) is used with spec
compliant definitions.
If you use language_direction on a message bundle definition, it is
not spec compliant.
- Shindig accepts an empty OAuth element (and tests for it in
OAuthSpecTest, testOAuthSpec_noservice()). However, the spec does
not allow it. If it were intended, the minOccurs on the service
element would be set). It is arguable that this is in the spirit of
"be liberal what you accept, be strict in what you return"...
Basic pattern for a spec element
================================
This is the basic code pattern for a spec element:
========================================================================
public class Element {
/**
* Represents the Element name in the Gadget Spec. For the message
bundle element, this is
* MESSAGE_BUNDLE_NAMESPACE_URI
*/
public static final QName ELEMENT_NAME = new
QName(SpecElement.GADGET_SPEC_NAMESPACE_URI, "Element");
/** One ATTR_xxx definition for each defined attribute */
public static final String ATTR_XXX = "xxx";
/** C'tor for the parser to build a new object */
protected Element(final QName name, final Map<String, QName>
attrNames, final Uri base) {
super(name, attrNames, base);
}
/** C'tor for the substitute method to build a new object */
protected Element(final Element element, final Substitutions substituter) {
super(element, substituter);
}
@Override
public Element substitute(final Substitutions substituter) {
return new Element(this, substituter);
}
/**
* Getter for attribute. Possible attr-methods are:
* - attr (String, null when unset)
* - attrDefault (String, default value when unset)
* - attrBool (Boolean, false when unset)
* - attrBool (Boolean, default when unset)
* - attrDouble (Double, 0.0 when unset)
* - attrInt (int, 0 when unset)
* - attrUri (Uri, EMPTY_URI when unset)
* - attrUriNull (Uri, null when unset)
* - attrUriBase (Uri, when object base and uri value not null, tries to
* resolve relative Uris to the base)
*
* All methods that return collections or similar objects *must* wrap these
* into unmodifiable methods to ensure that they are immutable.
*
* Methods might return null for unset attributes, but these *must* be clearly
* marked as such. Methods not marked as "can return null" must
never return null!
*/
public <type> getXXX() {
return attr-method(ATTR_XXX);
}
// Attributes need no further processing, all code is in SpecElement.
/**
* Optional: Adds a child to this element
*/
private void addChild(Child child) {
...
}
/**
* Optional: Writes all attributes to the supplied stream writer.
*/
@Override
protected void writeAttributes(final XMLStreamWriter writer) throws
XMLStreamException {
}
/**
* Optional: Writes all children to the supplied stream writer.
*/
@Override
protected void writeChildren(final XMLStreamWriter writer) throws
XMLStreamException {
}
/**
* Optional: Gets called when the element is parsed completely. Must
throw a SpecParserException if
* the element is semantically incorrect.
@Override
public void validate() throws SpecParserException {
}
/**
* The parser creates new objects of the given type, parses the XML code and
* adds the values to the generated object: Once an Object leaves
the parser, it
* is immutable.
public static class Parser extends SpecElement.Parser<Element> {
/**
* Creates a new parser for this element based on the default
* namespace.
*/
public Parser(final Uri base) {
this(ELEMENT_NAME, base);
}
/**
* Creates a new parser for this element using the given QName object
* as its namespace identifier.
*/
public Parser(final QName name, final Uri base) {
super(name, base);
// Optional: Register all children to this parser.
register(new Child.Parser(name, null, base));
// Register all attributes to this parser.
register(ATTR_XXX, ...);
}
/**
* C'tor called to register a child in the same namespace as the parent.
*/
public Parser(final QName parent, final QName child, final Uri base) {
this(buildChildName(parent, child, ELEMENT_NAME), base);
}
/**
* Returns a new element prepared for the parser.
*/
@Override
protected Element newElement() {
return new Element(name(), getAttrNames(), getBase());
}
/**
* Optional: Add content.
*/
@Override
protected void addText(final XMLStreamReader reader, final
MessageBundleSpec messageBundle) {
... process text ...
}
/**
* Optional: Add children.
*/
@Override
protected void addChild(XMLStreamReader reader, final Element
element, final SpecElement child) throws GadgetException {
if (child instanceof Child) {
element.addChild((Child) child);
} else if (... more children...) {
} else {
super.addChild(reader, element, child);
}
}
========================================================================