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; } }; } }