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.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.LocalDateTime; import org.odata4j.core.OAtomEntity; import org.odata4j.core.OAtomStreamEntity; import org.odata4j.core.ODataConstants; import org.odata4j.core.OEntities; import org.odata4j.core.OEntity; import org.odata4j.core.OLink; import org.odata4j.core.OProperty; import org.odata4j.edm.EdmEntitySet; import org.odata4j.edm.EdmProperty; import org.odata4j.format.FormatWriter; import org.odata4j.format.xml.XmlFormatWriter; import org.odata4j.internal.InternalUtil; import org.odata4j.producer.EntityResponse; import org.odata4j.stax2.QName2; import org.odata4j.stax2.XMLFactoryProvider2; import org.odata4j.stax2.XMLWriter2; import com.temenos.interaction.core.hypermedia.Link; import com.temenos.interaction.core.hypermedia.ResourceState; /** * Slightly modified version of @link{org.odata4j.format.xml.AtomEntryFormatWriter} that * is more aligned with JAX-RS. * @author aphethean * */ public class AtomEntryFormatWriter extends XmlFormatWriter implements FormatWriter<EntityResponse> { private ResourceState serviceDocument; public AtomEntryFormatWriter(ResourceState serviceDocument) { this.serviceDocument = serviceDocument; } @Override public String getContentType() { return ODataConstants.APPLICATION_ATOM_XML_CHARSET_UTF8; } public void write(UriInfo uriInfo, Writer w, EntityResponse target, EdmEntitySet entitySet, List<OLink> olinks) { String baseUri = AtomXMLProvider.getBaseUri(serviceDocument, uriInfo); DateTime utc = new DateTime().withZone(DateTimeZone.UTC); String updated = InternalUtil.toString(utc); XMLWriter2 writer = XMLFactoryProvider2.getInstance().newXMLWriterFactory2().createXMLWriter(w); writer.startDocument(); writer.startElement(new QName2("entry"), atom); writer.writeNamespace("m", m); writer.writeNamespace("d", d); writer.writeAttribute("xml:base", baseUri); // this darn writer sometimes uses the EdmEntitySet we pass in and sometimes uses OEntity.getEntitySet OEntity origOE = target.getEntity() ; OEntity newOE = OEntities.create(entitySet, origOE.getEntityKey(), origOE.getProperties(), olinks); writeEntry(writer, newOE, newOE.getProperties(), newOE.getLinks(), baseUri, updated, entitySet, true); writer.endDocument(); } @Override public String writeEntry(XMLWriter2 writer, OEntity oe, List<OProperty<?>> entityProperties, List<OLink> entityLinks, String baseUri, String updated, EdmEntitySet ees, boolean isResponse) { return writeEntry(writer, oe, entityProperties, entityLinks, baseUri, updated, ees, isResponse, null); } //OData olink doesn't have option to provide linkid, so writing linkid explicitly public String writeEntry(XMLWriter2 writer, OEntity oe, List<OProperty<?>> entityProperties, List<OLink> entityLinks, String baseUri, String updated, EdmEntitySet ees, boolean isResponse, Collection<Link> linkId) { String relid = null; String absid = null; if (isResponse) { relid = InternalUtil.getEntityRelId(oe); //Odata 4j creates IDs with an L suffix on Edm.Int types, quotes on Edm.String types - remove to conform to interaction links List<String> keys = oe.getEntityType().getKeys(); if(keys.size() > 0) { EdmProperty keyProperty = oe.getEntityType().findDeclaredProperty(keys.get(0)); if(keyProperty.getType().getFullyQualifiedTypeName().startsWith("Edm.Int") && relid.endsWith("L)")) { relid = relid.substring(0, relid.length()-2) + ")"; } } absid = (!baseUri.endsWith("/") ? baseUri + "/" : baseUri) + relid; writeElement(writer, "id", absid); } OAtomEntity oae = getAtomInfo(oe); writeElement(writer, "title", oae.getAtomEntityTitle(), "type", "text"); String summary = oae.getAtomEntitySummary(); if (summary != null) { writeElement(writer, "summary", summary, "type", "text"); } LocalDateTime updatedTime = oae.getAtomEntityUpdated(); if (updatedTime != null) { updated = InternalUtil.toString(updatedTime.toDateTime(DateTimeZone.UTC)); } writeElement(writer, "updated", updated); writer.startElement("author"); writeElement(writer, "name", oae.getAtomEntityAuthor()); writer.endElement("author"); if (entityLinks != null) { if (isResponse) { // the producer has populated the link collection, we just write what he gave us. Collection<Link> linkIdUsed = new HashSet<>(); for (OLink link : entityLinks) { String type = (link.isCollection()) ? atom_feed_content_type : atom_entry_content_type; String href = link.getHref(); if (link.isInline()) { writer.startElement("link"); writer.writeAttribute("rel", link.getRelation()); if (!"self".equals(link.getRelation()) && !"edit".equals(link.getRelation())) { writer.writeAttribute("type", type); } writer.writeAttribute("title", link.getTitle()); writer.writeAttribute("href", href); // write the inlined entities inside the link element writeLinkInline(writer, link, href, baseUri, updated, isResponse); writer.endElement("link"); } else { writeLink(writer, link, type, href, linkId, linkIdUsed); } } } else { // for requests we include only the provided links // Note: It seems that OLinks for responses are only built using the // title and OLinks for requests have the additional info in them // alread. I'm leaving that inconsistency in place for now but this // else and its preceding if could probably be unified. for (OLink olink : entityLinks) { String type = olink.isCollection() ? atom_feed_content_type : atom_entry_content_type; writer.startElement("link"); writer.writeAttribute("rel", olink.getRelation()); writer.writeAttribute("type", type); writer.writeAttribute("title", olink.getTitle()); writer.writeAttribute("href", olink.getHref()); if (olink.isInline()) { // write the inlined entities inside the link element writeLinkInline(writer, olink, olink.getHref(), baseUri, updated, isResponse); } writer.endElement("link"); } } } // else entityLinks null writeElement(writer, "category", null, // oe is null for creates "term", oe == null ? ees.getType().getFullyQualifiedTypeName() : oe.getEntityType().getFullyQualifiedTypeName(), "scheme", scheme); boolean hasStream = false; if (oe != null) { OAtomStreamEntity stream = oe.findExtension(OAtomStreamEntity.class); if (stream != null) { hasStream = true; writer.startElement("content"); writer.writeAttribute("type", stream.getAtomEntityType()); writer.writeAttribute("src", baseUri + stream.getAtomEntitySource()); writer.endElement("content"); } } if (!hasStream) { writer.startElement("content"); writer.writeAttribute("type", MediaType.APPLICATION_XML); } writer.startElement(new QName2(m, "properties", "m")); writeProperties(writer, entityProperties); writer.endElement("properties"); if (!hasStream) { writer.endElement("content"); } return absid; } /* * helper function to write customized and generic link */ private void writeLink(XMLWriter2 writer, OLink olink, String type, String href, Collection<Link> linkId, Collection<Link> linkIdUsed) { // deferred link. String rel = olink.getRelation(); String title = olink.getTitle(); String id = ""; if(linkId != null ) { Iterator<Link> iterator = linkId.iterator(); while(iterator.hasNext()) { Link tempLink = iterator.next(); if(tempLink.getHref().endsWith(href) && !linkIdUsed.contains(tempLink)) { id = tempLink.getLinkId(); linkIdUsed.add(tempLink); break; } } } if (!"self".equals(rel) && !"edit".equals(rel)) { if(id != null && id.length() > 0 ) { writeElement(writer, "link", null, "rel", rel, "type", type, "title", title, "href", href, "id", id); } else { writeElement(writer, "link", null, "rel", rel, "type", type, "title", title, "href", href); } } else { if(id != null && id.length() > 0 ) { writeElement(writer, "link", null, "rel", rel, "title", title, "href", href, "id", id); } else { List<StringBuilder> profileAndHref = createProfileForStateName(href); if("self".equals(rel) && null!=profileAndHref) { writeElement(writer, "link", null, "rel", rel, "profile" , profileAndHref.get(0).toString() , "title", title, "href", profileAndHref.get(1).toString()); } else { writeElement(writer, "link", null, "rel", rel, "title", title, "href", href); } profileAndHref = null; } } } private OAtomEntity getAtomInfo(OEntity oe) { if (oe != null) { OAtomEntity atomEntity = oe.findExtension(OAtomEntity.class); if (atomEntity != null) return atomEntity; } return new OAtomEntity() { @Override public String getAtomEntityTitle() { return null; } @Override public String getAtomEntitySummary() { return null; } @Override public String getAtomEntityAuthor() { return null; } @Override public LocalDateTime getAtomEntityUpdated() { return null; } }; } @Override // the original implementation that uses the OEntity EdmEntitySet public void write(UriInfo uriInfo, Writer w, EntityResponse target) { EdmEntitySet ees = target.getEntity().getEntitySet(); write(uriInfo, w, target, ees, target.getEntity().getLinks()); } private List<StringBuilder> createProfileForStateName(String href) { if (null!=href && href.contains("#@")) { List<StringBuilder> list = new ArrayList<StringBuilder>(); String [] hrefSplit = href.split("#@"); StringBuilder rel = new StringBuilder("http://schemas.microsoft.com/ado/2007/08/dataservices/related/"); rel.append(hrefSplit[1]); StringBuilder cleanHref = new StringBuilder(hrefSplit[0]); list.add(0, rel); list.add(1, cleanHref); return list; } else { return null; } } }