/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.ext.odata.internal;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import org.restlet.Context;
import org.restlet.ext.atom.Content;
import org.restlet.ext.atom.Entry;
import org.restlet.ext.atom.Feed;
import org.restlet.ext.atom.FeedReader;
import org.restlet.ext.atom.Link;
import org.restlet.ext.odata.Service;
import org.restlet.ext.odata.internal.edm.EntityType;
import org.restlet.ext.odata.internal.edm.Metadata;
import org.restlet.ext.odata.internal.reflect.ReflectUtils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* Generic Content handler for Atom Feed that takes care of OData specific
* needs, such as parsing XML content from other namespaces than Atom. It
* generates entities based on the values discovered in the feed.
*
* @author Thierry Boileau
* @param <T>
* The type of the parsed entities.
*/
public class FeedContentHandler<T> extends FeedReader {
/** The value retrieved from the "count" tag. */
private int count = -1;
/** The list of entities to complete. */
List<T> entities;
/** The class of the entity targeted by this feed. */
private Class<?> entityClass;
/** The OData type of the parsed entities. */
private EntityType entityType;
/** Used to parsed Atom entry elements. */
EntryContentHandler<T> entryHandler;
/** This is true if we are inside our own 'feed' element. */
private boolean isInFeed;
/** Internal logger. */
private Logger logger;
/** The metadata of the OData service. */
private Metadata metadata;
/** Are we parsing the count tag? */
private boolean parseCount;
/** Are we parsing an entry? */
private boolean parseEntry;
/** Used to glean text content. */
StringBuilder sb = null;
/**
* Constructor.
*
* @param entityClass
* The class of the parsed entities.
* @param entityType
* The entity type of the parsed entities.
* @param metadata
* The metadata of the remote OData service.
* @param logger
* The logger.
*/
public FeedContentHandler(Class<?> entityClass, EntityType entityType,
Metadata metadata, Logger logger) {
this.entityClass = entityClass;
this.entities = new ArrayList<T>();
this.entityType = entityType;
this.entryHandler = new EntryContentHandler<T>(entityClass, entityType,
metadata, logger);
this.logger = logger;
this.metadata = metadata;
}
/**
* Constructor.
*
* @param entityClass
* The class of the parsed entities.
* @param metadata
* The metadata of the remote OData service.
* @param logger
* The logger.
*/
public FeedContentHandler(Class<?> entityClass, Metadata metadata,
Logger logger) {
super();
this.entityClass = entityClass;
this.entities = new ArrayList<T>();
this.entryHandler = new EntryContentHandler<T>(entityClass, metadata,
logger);
this.logger = logger;
this.metadata = metadata;
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
if (parseCount) {
sb.append(ch, start, length);
} else if (parseEntry) {
// Delegate to the Entry reader
entryHandler.characters(ch, start, length);
}
}
/**
* Handle the end of a "link" element. Doesn't close link that belongs to
* inner associations!
*
* @return Returns true if the caller should not pop state.
* @author Emmanuel Liossis
*/
public boolean closeLink() {
if (parseEntry) {
// Cascade to any expanded associations. If someone from inner
// expanded associations has matched the close of the "link" and has
// popped state, inhibit my callers from popping state.
return entryHandler.closeLink();
}
// If we have passed 'feed' tag but haven't met any 'entry' tag, inhibit
// callers from popping state when link closes, because in this case the
// link pertains to the feed.
return isInFeed;
}
@Override
public void endContent(Content content) {
if (parseEntry) {
// Delegate to the Entry reader
entryHandler.endContent(content);
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (parseCount) {
this.count = Integer.parseInt(sb.toString());
parseCount = false;
}
// Allow also the "entry" end handling to cascade.
else if (parseEntry
|| (entryHandler != null && localName.equals("entry"))) {
// Delegate to the Entry reader
entryHandler.endElement(uri, localName, qName);
}
if (!parseEntry && uri.equals("http://www.w3.org/2005/Atom")
&& localName.equals("feed")) {
// Mark the end of 'feed' element pertaining to this handler.
isInFeed = false;
}
}
@Override
public void endEntry(Entry entry) {
// Only add the entity to the feed if it is our entry closing,
// not an inner entry of an expanded association.
if (entryHandler.closeEntry(entry)) {
parseEntry = false;
T entity = entryHandler.getEntity();
if (entity != null) {
entities.add(entity);
} else {
getLogger().warning("Can't add a null entity.");
}
}
}
@Override
public void endLink(Link link) {
if (parseEntry) {
// Delegate to the Entry reader
entryHandler.endLink(link);
}
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
if (parseEntry) {
// Delegate to the Entry reader
entryHandler.endPrefixMapping(prefix);
}
}
/**
* Returns the value of the "count" tag, that is to say the size of the
* current entity set.
*
* @return The size of the current entity set, as specified by the Atom
* document.
*/
public int getCount() {
return count;
}
/**
* Returns the list of discovered entities.
*
* @return The list of discovered entities.
*/
public List<T> getEntities() {
return entities;
}
/**
* Returns the current logger.
*
* @return The current logger.
*/
private Logger getLogger() {
if (logger == null) {
logger = Context.getCurrentLogger();
}
return logger;
}
@Override
public void startContent(Content content) {
if (parseEntry) {
// Delegate to the Entry reader
entryHandler.startContent(content);
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attrs) throws SAXException {
if (!parseEntry
&& Service.WCF_DATASERVICES_METADATA_NAMESPACE.equals(uri)
&& "count".equals(localName)) {
sb = new StringBuilder();
parseCount = true;
} else if (parseEntry) {
// Delegate to the Entry reader
entryHandler.startElement(uri, localName, qName, attrs);
} else if (uri.equals("http://www.w3.org/2005/Atom")
&& localName.equals("feed")) {
// Mark the start of the 'feed' element pertaining to this handler.
isInFeed = true;
}
}
@Override
public void startEntry(Entry entry) {
parseEntry = true;
// Delegate to the Entry reader
entryHandler.startEntry(entry);
}
@Override
public void startFeed(Feed feed) {
if (this.entityClass == null) {
this.entityClass = ReflectUtils.getEntryClass(feed);
}
if (this.entityType == null && metadata != null) {
entityType = metadata.getEntityType(entityClass);
}
}
@Override
public void startLink(Link link) {
if (parseEntry) {
// Delegate to the Entry reader
entryHandler.startLink(link);
}
}
@Override
public void startPrefixMapping(String prefix, String uri)
throws SAXException {
if (parseEntry) {
// Delegate to the Entry reader
entryHandler.startPrefixMapping(prefix, uri);
}
}
}