package com.temenos.interaction.media.odata.xml.atom;
/*
* #%L
* interaction-media-odata-xml
* %%
* Copyright (C) 2012 - 2013 Temenos Holdings N.V.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.io.Writer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import javax.xml.namespace.QName;
import org.apache.abdera.Abdera;
import org.apache.abdera.model.Text.Type;
import org.apache.abdera.writer.StreamWriter;
import org.apache.commons.io.output.WriterOutputStream;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.odata4j.core.OAtomEntity;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmType;
import org.odata4j.internal.InternalUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.temenos.interaction.core.entity.Entity;
import com.temenos.interaction.core.entity.EntityMetadata;
import com.temenos.interaction.core.entity.EntityProperties;
import com.temenos.interaction.core.entity.EntityProperty;
import com.temenos.interaction.core.entity.Metadata;
import com.temenos.interaction.core.entity.vocabulary.terms.TermValueType;
import com.temenos.interaction.core.hypermedia.CollectionResourceState;
import com.temenos.interaction.core.hypermedia.Link;
import com.temenos.interaction.core.hypermedia.ResourceState;
import com.temenos.interaction.core.hypermedia.Transition;
import com.temenos.interaction.core.resource.CollectionResource;
import com.temenos.interaction.core.resource.EntityResource;
import com.temenos.interaction.core.resource.RESTResource;
import com.temenos.interaction.odataext.entity.MetadataOData4j;
/**
* Writes an IRIS Entity out to Atom XML format using the Apache Abdera library.
*
* @author srushworth
*
*/
public class AtomEntityEntryFormatWriter {
private final static Logger logger = LoggerFactory.getLogger(AtomEntityEntryFormatWriter.class);
// Constants for OData
public static final String d = "http://schemas.microsoft.com/ado/2007/08/dataservices";
public static final String m = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";
public static final String scheme = "http://schemas.microsoft.com/ado/2007/08/dataservices/scheme";
public static final String atom_feed_content_type = "application/atom+xml;type=feed";
public static final String atom_entry_content_type = "application/atom+xml;type=entry";
public static final String href_lang = "en";
private ResourceState serviceDocument;
private Metadata metadata;
public AtomEntityEntryFormatWriter(ResourceState serviceDocument, Metadata metadata) {
this.serviceDocument = serviceDocument;
this.metadata = metadata;
}
public void write(UriInfo uriInfo, Writer w,
String entityName,
Entity entity,
Collection<Link> links,
Map<Transition,RESTResource> embeddedResources) {
String baseUri = AtomXMLProvider.getBaseUri(serviceDocument, uriInfo);
String absoluteId = getAbsoluteId(uriInfo, links);
DateTime utc = new DateTime().withZone(DateTimeZone.UTC);
String updated = InternalUtil.toString(utc);
Abdera abdera = new Abdera();
StreamWriter writer = abdera.newStreamWriter();
writer.setOutputStream(new WriterOutputStream(w));
writer.setAutoflush(false);
writer.setAutoIndent(true);
writer.startDocument();
writer.startEntry();
writer.writeNamespace("d", d);
writer.writeNamespace("m", m);
writer.writeAttribute("xml:base", baseUri);
writeEntry(writer, entityName, entity, links, embeddedResources, baseUri, absoluteId, updated);
writer.endEntry();
writer.endDocument();
writer.flush();
}
/**
* @param uriInfo
* @param links
* @return
*/
private String getAbsoluteId(UriInfo uriInfo, Collection<Link> links) {
String absoluteId = "";
for(Link link: links) {
if("self".equals(link.getRel())) {
absoluteId = link.getHref();
break;
}
}
if(absoluteId.isEmpty()) {
absoluteId = uriInfo.getBaseUri() + uriInfo.getPath();
}
return absoluteId;
}
public void writeEntry(StreamWriter writer, String entitySetName, String entityName, Entity entity,
Collection<Link> entityLinks, Map<Transition,RESTResource> embeddedResources,
UriInfo uriInfo, String updated) {
EntityMetadata entityMetadata = metadata.getEntityMetadata(entityName);
String baseUri = AtomXMLProvider.getBaseUri(serviceDocument, uriInfo);
String absoluteId = getAbsoluteId(baseUri, entitySetName, entity, entityMetadata);
writer.startEntry();
writeEntry(writer, entityName, entity, entityLinks, embeddedResources, baseUri, absoluteId, updated);
writer.endEntry();
}
protected void writeEntry(StreamWriter writer,
String entityName,
Entity entity,
Collection<Link> entityLinks,
Map<Transition,RESTResource> embeddedResources,
String baseUri, String absoluteId, String updated) {
assert(entityName != null);
// entity name could be different between entity resource and underlying entity
// e.g., for Errors entity, entity resource would have the request entity name
String entityMetadataName = entity != null ? entity.getName() : entityName;
EntityMetadata entityMetadata = metadata.getEntityMetadata(entityMetadataName);
String modelName = metadata.getModelName();
writer.writeId(absoluteId);
OAtomEntity oae = getAtomInfo(entity);
writer.writeTitle(oae.getAtomEntityTitle());
String summary = oae.getAtomEntitySummary();
if (!summary.isEmpty()) {
writer.writeSummary(summary);
}
LocalDateTime updatedTime = oae.getAtomEntityUpdated();
if (updatedTime != null) {
updated = InternalUtil.toString(updatedTime
.toDateTime(DateTimeZone.UTC));
}
writer.writeUpdated(updated);
writer.writeAuthor(oae.getAtomEntityAuthor());
if (entityLinks != null) {
for (Link link : entityLinks) {
String type = (link.getTransition().getTarget() instanceof CollectionResourceState)
? atom_feed_content_type
: atom_entry_content_type;
String href = link.getRelativeHref(baseUri);
String rel = link.getRel();
writer.startLink(href, rel);
if("self".equals(link.getRel())) {
ResourceState target = link.getTransition().getTarget();
writer.writeAttribute("profile", target.getRel());
}
if (!"self".equals(link.getRel()) &&
!"edit".equals(link.getRel())) {
writer.writeAttribute("type", type);
}
writer.writeAttribute("title", link.getTitle());
if (embeddedResources != null && embeddedResources.get(link.getTransition()) != null) {
String embeddedAbsoluteId = link.getHref();
writeLinkInline(writer, metadata, link, embeddedResources.get(link.getTransition()), link.getHref(), baseUri, embeddedAbsoluteId, updated);
}
String linkId = link.getLinkId();
if( linkId != null && linkId.length() > 0 ) {
writer.writeAttribute("id", linkId);
}
writer.endLink();
}
}
writer.writeCategory(modelName + Metadata.MODEL_SUFFIX + "." + entityName, scheme);
writer.flush();
writer.startContent(MediaType.APPLICATION_XML);
writer.startElement(new QName(m, "properties", "m"));
if (entity != null) {
writeProperties(writer, entityMetadata, entity.getProperties(), modelName);
}
writer.endElement();
writer.endContent();
}
private void writeEntityResource(StreamWriter writer, EntityResource<Entity> entityResource, String baseUri, String absoluteId, String updated) {
Entity entity = null;
try {
entity = entityResource.getEntity();
} catch(Exception e) {
logger.error("Failed to get entity", e);
}
writer.startEntry();
writeEntry(writer, entityResource.getEntityName(), entity, entityResource.getLinks(), entityResource.getEmbedded(), baseUri, absoluteId, updated);
writer.endEntry();
}
@SuppressWarnings("unchecked")
protected void writeLinkInline(StreamWriter writer, Metadata metadata, Link linkToInline, RESTResource embeddedResource,
String href, String baseUri, String absoluteId, String updated) {
writer.startElement(new QName(m, "inline", "m"));
if (embeddedResource instanceof CollectionResource) {
CollectionResource<Entity> collectionResource = (CollectionResource<Entity>) embeddedResource;
Collection<EntityResource<Entity>> entities = collectionResource.getEntities();
if (entities != null && !entities.isEmpty()) {
writer.startFeed();
writer.writeTitle(Type.TEXT, linkToInline.getTitle());
writer.writeId(baseUri + href);
writer.writeUpdated(updated);
writer.startLink(href, "self");
writer.writeAttribute("title", linkToInline.getTitle());
writer.endLink();
for (EntityResource<Entity> entityResource : entities) {
writeEntityResource(writer, entityResource, baseUri, absoluteId, updated);
}
writer.endFeed();
}
} else if (embeddedResource instanceof EntityResource) {
EntityResource<Entity> entityResource = (EntityResource<Entity>) embeddedResource;
writeEntityResource(writer, entityResource, baseUri, absoluteId, updated);
} else {
throw new RuntimeException("Unknown OLink type " + linkToInline.getClass());
}
writer.endElement(); // end inline
}
private String getAbsoluteId(String baseUri, String entitySetName, Entity entity, EntityMetadata entityMetadata) {
String absId = "";
for(String key : entityMetadata.getIdFields()) {
EntityProperty prop = entity.getProperties().getProperty(entityMetadata.getSimplePropertyName(key));
if(prop != null) {
absId += absId.isEmpty() ? (!baseUri.endsWith("/") ? baseUri + "/" : baseUri) + entitySetName : ",";
if(entityMetadata.isPropertyNumber(prop.getFullyQualifiedName())) {
absId += "(" + entityMetadata.getPropertyValueAsString(prop) + ")";
} else {
absId += "('" + entityMetadata.getPropertyValueAsString(prop) + "')";
}
}
}
return absId;
}
private void writeProperties(StreamWriter writer, EntityMetadata entityMetadata, EntityProperties entityProperties, String modelName) {
if ( entityMetadata == null ) logger.error( "No entityMetadata" );
// Loop round all properties writing out fields and MV and SV sets
Map<String, EntityProperty> properties = entityProperties.getProperties();
for (String propertyName : properties.keySet()) {
// Work out what the property looks like by looking at the metadata
EntityProperty property = (EntityProperty) properties.get(propertyName);
if ( property == null ) logger.error( "Property " + propertyName + " listed but missing" );
boolean isComplex = entityMetadata.isPropertyComplex(property.getFullyQualifiedName());
if( !isComplex ) {
// Simple field
writeProperty( writer, entityMetadata, property);
} else {
// Complex List
writePropertyComplexList( writer, entityMetadata, property, modelName);
}
}
}
private void writeProperty( StreamWriter writer, EntityMetadata entityMetadata, EntityProperty property ) {
String elementText = entityMetadata.getPropertyValueAsString( property );
writer.startElement(new QName(d, property.getName(), "d"));
EdmType type = MetadataOData4j.termValueToEdmType(entityMetadata.getTermValue(property.getFullyQualifiedName(), TermValueType.TERM_NAME));
boolean isNullable = entityMetadata.isPropertyNullable(property.getFullyQualifiedName());
// Append Type Attribute
if(!type.equals(EdmSimpleType.STRING)) {
writer.writeAttribute(new QName(m, "type", "m"), type.getFullyQualifiedTypeName());
}
// Append Null attribute
if ( isNullable && (elementText.isEmpty()) && !type.equals(EdmSimpleType.STRING)) {
writer.writeAttribute(new QName(m, "null", "m"), "true");
}
//Write the property text
if(type.equals(EdmSimpleType.DATETIME) && !elementText.isEmpty()) {
writer.writeElementText(AtomXMLProviderHelper.checkAndConvertDateTimeToUTC(property));
}
else if (elementText != null) {
writer.writeElementText(elementText);
}
writer.endElement();
}
/**
* Method to prepare Complex type representation.
* @param writer
* @param entityMetadata
* @param propertyName
* @param propertiesList
* @param modelName
*/
private void writePropertyComplexList( StreamWriter writer, EntityMetadata entityMetadata, EntityProperty property, String modelName) {
@SuppressWarnings("unchecked")
List<EntityProperties> propertiesList = (List<EntityProperties>) property.getValue();
String name = entityMetadata.getEntityName() + "_" + property.getName();
int parseCount = 0;
for ( EntityProperties properties : propertiesList ) {
String fqTypeName = modelName + Metadata.MODEL_SUFFIX + "." + name;
// We should be able to differentiate List<ComplexType> with regular ComplexType
if (entityMetadata.isPropertyList(property.getFullyQualifiedName())) {
if (parseCount == 0) {
writer.startElement(new QName(d, name, "d"));
writer.writeAttribute(new QName(m, "type", "m"), "Bag(" + fqTypeName + ")");
writer.startElement(new QName(d, "element", "d"));
parseCount++;
} else {
writer.startElement(new QName(d, "element", "d"));
}
} else {
writer.startElement(new QName(d, name, "d"));
writer.writeAttribute(new QName(m, "type", "m"), fqTypeName);
}
writeProperties( writer, entityMetadata, properties, modelName );
writer.endElement();
}
// For List<ComplexTypes> we should end the complex node here
if (!propertiesList.isEmpty() && entityMetadata.isPropertyList(property.getFullyQualifiedName())) {
writer.endElement();
}
}
private OAtomEntity getAtomInfo(Entity e) {
return new OAtomEntity() {
@Override
public String getAtomEntityTitle() {
return "";
}
@Override
public String getAtomEntitySummary() {
return "";
}
@Override
public String getAtomEntityAuthor() {
return "";
}
@Override
public LocalDateTime getAtomEntityUpdated() {
return null;
}
};
}
}