/******************************************************************************* * Copyright 2013 SAP AG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.sap.core.odata.core.ep; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.List; import java.util.Locale; import java.util.Map; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import com.sap.core.odata.api.ODataServiceVersion; import com.sap.core.odata.api.commons.HttpStatusCodes; import com.sap.core.odata.api.commons.ODataHttpHeaders; import com.sap.core.odata.api.edm.Edm; import com.sap.core.odata.api.edm.EdmEntitySet; import com.sap.core.odata.api.edm.EdmException; import com.sap.core.odata.api.edm.EdmFunctionImport; import com.sap.core.odata.api.edm.EdmMultiplicity; import com.sap.core.odata.api.edm.EdmProperty; import com.sap.core.odata.api.edm.EdmType; import com.sap.core.odata.api.edm.EdmTypeKind; import com.sap.core.odata.api.ep.EntityProviderException; import com.sap.core.odata.api.ep.EntityProviderReadProperties; import com.sap.core.odata.api.ep.EntityProviderWriteProperties; import com.sap.core.odata.api.ep.entry.ODataEntry; import com.sap.core.odata.api.ep.feed.ODataFeed; import com.sap.core.odata.api.processor.ODataResponse; import com.sap.core.odata.api.processor.ODataResponse.ODataResponseBuilder; import com.sap.core.odata.api.servicedocument.ServiceDocument; import com.sap.core.odata.core.commons.ContentType; import com.sap.core.odata.core.commons.ContentType.ODataFormat; import com.sap.core.odata.core.ep.aggregator.EntityInfoAggregator; import com.sap.core.odata.core.ep.aggregator.EntityPropertyInfo; import com.sap.core.odata.core.ep.consumer.AtomServiceDocumentConsumer; import com.sap.core.odata.core.ep.consumer.XmlEntityConsumer; import com.sap.core.odata.core.ep.producer.AtomEntryEntityProducer; import com.sap.core.odata.core.ep.producer.AtomFeedProducer; import com.sap.core.odata.core.ep.producer.AtomServiceDocumentProducer; import com.sap.core.odata.core.ep.producer.XmlCollectionEntityProducer; import com.sap.core.odata.core.ep.producer.XmlErrorDocumentProducer; import com.sap.core.odata.core.ep.producer.XmlLinkEntityProducer; import com.sap.core.odata.core.ep.producer.XmlLinksEntityProducer; import com.sap.core.odata.core.ep.producer.XmlPropertyEntityProducer; import com.sap.core.odata.core.ep.util.CircleStreamBuffer; import com.sap.core.odata.core.exception.ODataRuntimeException; /** * @author SAP AG */ public class AtomEntityProvider implements ContentTypeBasedEntityProvider { /** Default used charset for writer and response content header */ private static final String DEFAULT_CHARSET = ContentType.CHARSET_UTF_8; private static final String XML_VERSION = "1.0"; private final ODataFormat odataFormat; public AtomEntityProvider() throws EntityProviderException { this(ODataFormat.ATOM); } public AtomEntityProvider(final ContentType contentType) throws EntityProviderException { this(contentType.getODataFormat()); } public AtomEntityProvider(final ODataFormat odataFormat) throws EntityProviderException { switch (odataFormat) { case ATOM: case XML: this.odataFormat = odataFormat; break; default: throw new EntityProviderException(EntityProviderException.ILLEGAL_ARGUMENT.addContent("Got unsupported ODataFormat '" + odataFormat + "'.")); } } /** * <p>Serializes an error message according to the OData standard.</p> * <p>In case an error occurs, it is logged. * An exception is not thrown because this method is used in exception handling.</p> * @param status the {@link HttpStatusCodes} associated with this error * @param errorCode a String that serves as a substatus to the HTTP response code * @param message a human-readable message describing the error * @param locale the {@link Locale} that should be used to format the error message * @param innerError the inner error for this message. If it is null or an empty String no inner error tag is shown inside the response xml * @return an {@link ODataResponse} containing the serialized error message */ @Override public ODataResponse writeErrorDocument(final HttpStatusCodes status, final String errorCode, final String message, final Locale locale, final String innerError) { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); XmlErrorDocumentProducer producer = new XmlErrorDocumentProducer(); producer.writeErrorDocument(writer, errorCode, message, locale, innerError); writer.flush(); csb.closeWrite(); ODataResponseBuilder response = ODataResponse.entity(csb.getInputStream()) .contentHeader(ContentType.APPLICATION_XML.toContentTypeString()) .header(ODataHttpHeaders.DATASERVICEVERSION, ODataServiceVersion.V10) .status(status); return response.build(); } catch (Exception e) { csb.close(); throw new ODataRuntimeException(e); } } /** * Write service document based on given {@link Edm} and <code>service root</code> as * content type "<code>application/atomsvc+xml; charset=utf-8</code>". * * @param edm the Entity Data Model * @param serviceRoot the root URI of the service * @return resulting {@link ODataResponse} with written service document * @throws EntityProviderException */ @Override public ODataResponse writeServiceDocument(final Edm edm, final String serviceRoot) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStreamWriter writer = new OutputStreamWriter(csb.getOutputStream(), DEFAULT_CHARSET); AtomServiceDocumentProducer as = new AtomServiceDocumentProducer(edm, serviceRoot); as.writeServiceDocument(writer); csb.closeWrite(); ODataResponse response = ODataResponse.entity(csb.getInputStream()) .contentHeader(ContentType.APPLICATION_ATOM_SVC_CS_UTF_8.toContentTypeString()) .build(); return response; } catch (EntityProviderException e) { csb.close(); throw e; } catch (Exception e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } @Override public ODataResponse writeEntry(final EdmEntitySet entitySet, final Map<String, Object> data, final EntityProviderWriteProperties properties) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); writer.writeStartDocument(DEFAULT_CHARSET, XML_VERSION); AtomEntryEntityProducer as = new AtomEntryEntityProducer(properties); EntityInfoAggregator eia = EntityInfoAggregator.create(entitySet, properties.getExpandSelectTree()); as.append(writer, eia, data, true, false); writer.flush(); csb.closeWrite(); ODataResponseBuilder response = ODataResponse.entity(csb.getInputStream()) .contentHeader(getContentHeader(ContentType.APPLICATION_ATOM_XML_ENTRY)) .eTag(as.getETag()) .idLiteral(as.getLocation()); return response.build(); } catch (EntityProviderException e) { csb.close(); throw e; } catch (Exception e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } @Override public ODataResponse writeProperty(final EdmProperty edmProperty, final Object value) throws EntityProviderException { EntityPropertyInfo propertyInfo = EntityInfoAggregator.create(edmProperty); return writeSingleTypedElement(propertyInfo, value); } private ODataResponse writeSingleTypedElement(final EntityPropertyInfo propertyInfo, final Object value) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); writer.writeStartDocument(DEFAULT_CHARSET, XML_VERSION); XmlPropertyEntityProducer ps = new XmlPropertyEntityProducer(); ps.append(writer, propertyInfo, value); writer.flush(); csb.closeWrite(); return ODataResponse.entity(csb.getInputStream()).contentHeader(getContentHeader(ContentType.APPLICATION_XML)).build(); } catch (EntityProviderException e) { csb.close(); throw e; } catch (Exception e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } @Override public ODataResponse writeFeed(final EdmEntitySet entitySet, final List<Map<String, Object>> data, final EntityProviderWriteProperties properties) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); writer.writeStartDocument(DEFAULT_CHARSET, XML_VERSION); AtomFeedProducer atomFeedProvider = new AtomFeedProducer(properties); EntityInfoAggregator eia = EntityInfoAggregator.create(entitySet, properties.getExpandSelectTree()); atomFeedProvider.append(writer, eia, data, false); writer.flush(); csb.closeWrite(); ODataResponse response = ODataResponse.entity(csb.getInputStream()).contentHeader(getContentHeader(ContentType.APPLICATION_ATOM_XML_FEED)).build(); return response; } catch (EntityProviderException e) { csb.close(); throw e; } catch (XMLStreamException e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } private String getContentHeader(final ContentType mediaType) { if (odataFormat == ODataFormat.XML) { return ContentType.APPLICATION_XML_CS_UTF_8.toContentTypeString(); } return ContentType.create(mediaType, ContentType.PARAMETER_CHARSET, DEFAULT_CHARSET).toContentTypeString(); } @Override public ODataResponse writeLink(final EdmEntitySet entitySet, final Map<String, Object> data, final EntityProviderWriteProperties properties) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); writer.writeStartDocument(DEFAULT_CHARSET, XML_VERSION); XmlLinkEntityProducer entity = new XmlLinkEntityProducer(properties); final EntityInfoAggregator entityInfo = EntityInfoAggregator.create(entitySet, properties.getExpandSelectTree()); entity.append(writer, entityInfo, data, true); writer.flush(); csb.closeWrite(); return ODataResponse.entity(csb.getInputStream()).contentHeader(getContentHeader(ContentType.APPLICATION_XML)).build(); } catch (EntityProviderException e) { csb.close(); throw e; } catch (Exception e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } @Override public ODataResponse writeLinks(final EdmEntitySet entitySet, final List<Map<String, Object>> data, final EntityProviderWriteProperties properties) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); writer.writeStartDocument(DEFAULT_CHARSET, XML_VERSION); XmlLinksEntityProducer entity = new XmlLinksEntityProducer(properties); final EntityInfoAggregator entityInfo = EntityInfoAggregator.create(entitySet, properties.getExpandSelectTree()); entity.append(writer, entityInfo, data); writer.flush(); csb.closeWrite(); return ODataResponse.entity(csb.getInputStream()).contentHeader(getContentHeader(ContentType.APPLICATION_XML)).build(); } catch (EntityProviderException e) { csb.close(); throw e; } catch (Exception e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } private ODataResponse writeCollection(final EntityPropertyInfo propertyInfo, final List<?> data) throws EntityProviderException { CircleStreamBuffer csb = new CircleStreamBuffer(); try { OutputStream outStream = csb.getOutputStream(); XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(outStream, DEFAULT_CHARSET); writer.writeStartDocument(DEFAULT_CHARSET, XML_VERSION); XmlCollectionEntityProducer.append(writer, propertyInfo, data); writer.flush(); csb.closeWrite(); return ODataResponse.entity(csb.getInputStream()).contentHeader(getContentHeader(ContentType.APPLICATION_XML)).build(); } catch (EntityProviderException e) { csb.close(); throw e; } catch (Exception e) { csb.close(); throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } @Override public ODataResponse writeFunctionImport(final EdmFunctionImport functionImport, final Object data, final EntityProviderWriteProperties properties) throws EntityProviderException { try { final EdmType type = functionImport.getReturnType().getType(); final boolean isCollection = functionImport.getReturnType().getMultiplicity() == EdmMultiplicity.MANY; if (type.getKind() == EdmTypeKind.ENTITY) { @SuppressWarnings("unchecked") Map<String, Object> map = (Map<String, Object>) data; return writeEntry(functionImport.getEntitySet(), map, properties); } final EntityPropertyInfo info = EntityInfoAggregator.create(functionImport); if (isCollection) { return writeCollection(info, (List<?>) data); } else { return writeSingleTypedElement(info, data); } } catch (EdmException e) { throw new EntityProviderException(EntityProviderException.EXCEPTION_OCCURRED.addContent(e.getClass().getSimpleName()), e); } } @Override public ODataFeed readFeed(final EdmEntitySet entitySet, final InputStream content, final EntityProviderReadProperties properties) throws EntityProviderException { XmlEntityConsumer xec = new XmlEntityConsumer(); return xec.readFeed(entitySet, content, properties); } @Override public ODataEntry readEntry(final EdmEntitySet entitySet, final InputStream content, final EntityProviderReadProperties properties) throws EntityProviderException { XmlEntityConsumer xec = new XmlEntityConsumer(); return xec.readEntry(entitySet, content, properties); } @Override public Map<String, Object> readProperty(final EdmProperty edmProperty, final InputStream content, final EntityProviderReadProperties properties) throws EntityProviderException { XmlEntityConsumer xec = new XmlEntityConsumer(); return xec.readProperty(edmProperty, content, properties); } @Override public String readLink(final EdmEntitySet entitySet, final InputStream content) throws EntityProviderException { XmlEntityConsumer xec = new XmlEntityConsumer(); return xec.readLink(entitySet, content); } @Override public List<String> readLinks(final EdmEntitySet entitySet, final InputStream content) throws EntityProviderException { XmlEntityConsumer xec = new XmlEntityConsumer(); return xec.readLinks(entitySet, content); } @Override public ServiceDocument readServiceDocument(final InputStream serviceDocument) throws EntityProviderException { AtomServiceDocumentConsumer serviceDocConsumer = new AtomServiceDocumentConsumer(); return serviceDocConsumer.parseXml(serviceDocument); } }