package org.odata4j.format.json;
import java.io.Reader;
import org.odata4j.core.OCollection;
import org.odata4j.core.OCollections;
import org.odata4j.core.OComplexObject;
import org.odata4j.core.ODataVersion;
import org.odata4j.core.OEntity;
import org.odata4j.core.OObject;
import org.odata4j.core.OSimpleObjects;
import org.odata4j.edm.EdmCollectionType;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmType;
import org.odata4j.exceptions.NotImplementedException;
import org.odata4j.format.Entry;
import org.odata4j.format.FormatParser;
import org.odata4j.format.FormatParserFactory;
import org.odata4j.format.FormatType;
import org.odata4j.format.Settings;
import org.odata4j.format.json.JsonFeedFormatParser.JsonFeed;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader.JsonEvent;
import org.odata4j.format.json.JsonStreamReaderFactory.JsonStreamReader.JsonValueEvent;
/**
* Parses an OCollection in JSON format.
*
* <pre>Collection types handled so far:
* - OComplexObject
*
* TODO:
* - all other types
* </pre>
*/
public class JsonCollectionFormatParser extends JsonFormatParser implements FormatParser<OCollection<? extends OObject>> {
private final EdmCollectionType returnType;
public JsonCollectionFormatParser(Settings s) {
super(s);
returnType = (EdmCollectionType) (s == null ? null : s.parseType);
}
public JsonCollectionFormatParser(EdmCollectionType collectionType, EdmDataServices md) {
super(null);
this.metadata = md;
returnType = collectionType;
}
@Override
public OCollection<? extends OObject> parse(Reader reader) {
if (this.returnType.getItemType().getClass().isAssignableFrom(EdmEntityType.class)) {
return parseFunctionFeed(reader);
}
JsonStreamReader jsr = JsonStreamReaderFactory.createJsonStreamReader(reader);
try {
if (isResponse) {
ensureNext(jsr);
ensureStartObject(jsr.nextEvent()); // the response object
// "d" property
ensureNext(jsr);
ensureStartProperty(jsr.nextEvent(), DATA_PROPERTY);
// "aresult" for DataServiceVersion > 1.0
if (version.compareTo(ODataVersion.V1) > 0) {
ensureNext(jsr);
ensureStartObject(jsr.nextEvent());
ensureNext(jsr);
ensureStartProperty(jsr.nextEvent(), RESULTS_PROPERTY);
}
}
// parse the entry
OCollection<? extends OObject> o = parseCollection(jsr);
if (isResponse) {
// the "d" property was our object...it is also a property.
ensureNext(jsr);
ensureEndProperty(jsr.nextEvent());
if (version.compareTo(ODataVersion.V1) > 0) {
ensureNext(jsr);
ensureEndObject(jsr.nextEvent());
ensureNext(jsr);
ensureEndProperty(jsr.nextEvent()); // "results"
}
ensureNext(jsr);
ensureEndObject(jsr.nextEvent()); // the response object
}
return o;
} finally {
jsr.close();
}
}
protected OCollection<? extends OObject> parseFunctionFeed(Reader reader) {
// entitySetName is a function
// this really reveals a fundamental flaw in the parsers being driven by EdmEntitySets
// instead of EdmEntityTypes.
EdmEntitySet entitySet = this.metadata.getEdmEntitySet((EdmEntityType) returnType.getItemType());
Settings settings = new Settings(
// someone really needs to spend some time on service version negotiation....
ODataVersion.V2,
this.metadata,
entitySet.getName(),
this.entityKey,
null, // feed customization mapping
this.isResponse,
this.returnType.getItemType());
JsonFeedFormatParser parser = new JsonFeedFormatParser(settings);
JsonFeed feed = parser.parse(reader);
OCollection.Builder<OObject> c = newCollectionBuilder();
for (Entry e : feed.getEntries()) {
c.add(e.getEntity());
}
return c.build();
}
/**
* we make this public so that the JsonParametersFormatParser can use it to read parameter whose type is collection.
* Also add code to handle a collection of entity.
* @param jsr the stream reader
* @return the collection object
*/
public OCollection<? extends OObject> parseCollection(JsonStreamReader jsr) {
/* in V3 VJson, the collection, will looks like this, so we will skip till we see startArray('['
* begin-object
[collMetadataNVP value-seperator]
resultsNVP
end-object
*/
int startObjCount = 0;
// an array of objects:
ensureNext(jsr);
JsonEvent event = jsr.nextEvent();
while (!event.isStartArray()) {
if (event.isStartObject()) {
startObjCount ++;
} else if (event.isEndObject()) {
startObjCount --;
}
ensureNext(jsr);
event = jsr.nextEvent();
}
ensureStartArray(event);
OCollection.Builder<OObject> c = newCollectionBuilder();
if (this.returnType.getItemType().isSimple()) {
parseCollectionOfSimple(c, jsr);
} else {
FormatParser<? extends OObject> parser = createItemParser(this.returnType.getItemType());
while (jsr.hasNext()) {
// this is what I really want to do next:
// OObject o = parser.parse(jsr);
// however, the FormatParser api would have to be genericized, we would need an interface for
// the event-oriented parsers (JsonStreamReader, XMLStreamReader).
// I just don't have the time at this momement...
if (parser instanceof JsonComplexObjectFormatParser) {
OComplexObject obj = ((JsonComplexObjectFormatParser) parser).parseSingleObject(jsr);
// null if not there
if (obj != null) {
c = c.add(obj);
} else {
break;
}
} else if (parser instanceof JsonEntityFormatParser) {
OEntity obj = ((JsonEntityFormatParser)parser).parseSingleEntity(jsr);
if (obj != null) {
c = c.add(obj);
} else {
break;
}
}
else {
throw new NotImplementedException("collections of type: " + this.returnType.getItemType().getFullyQualifiedTypeName() + " not implemented");
}
}
}
// we should see the end of the array
ensureEndArray(jsr.previousEvent());
while (startObjCount > 0){
event = jsr.nextEvent();
if (event.isStartObject()) {
startObjCount++;
} else if (event.isEndObject()) {
startObjCount --;
}
}
// make sure we match
return c.build();
}
protected void parseCollectionOfSimple(OCollection.Builder<OObject> builder, JsonStreamReader jsr) {
while (jsr.hasNext()) {
JsonEvent e = jsr.nextEvent();
if (e.isValue()) {
JsonValueEvent ve = e.asValue();
builder.add(OSimpleObjects.parse((EdmSimpleType<?>) this.returnType.getItemType(), ve.getValue()));
} else if (e.isEndArray()) {
break;
} else {
throw new RuntimeException("invalid JSON content");
}
}
}
protected OCollection.Builder<OObject> newCollectionBuilder() {
return OCollections.<OObject> newBuilder(this.returnType.getItemType());
}
protected FormatParser<? extends OObject> createItemParser(EdmType edmType) {
// each item is parsed as a standalone item, not a response item
Settings s = new Settings(
this.version,
this.metadata,
this.entitySetName,
this.entityKey,
null, // FeedCustomizationMapping fcMapping,
false, // boolean isResponse);
edmType); // expected type
return FormatParserFactory.getParser(EdmType.getInstanceType(edmType), FormatType.JSONVERBOSE, s);
}
}