All Downloads are FREE. Search and download functionalities are using the official Maven repository.

grails.rest.render.hal.HalXmlRenderer.groovy Maven / Gradle / Ivy

/*
 * Copyright 2013 the original author or authors.
 *
 * 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 grails.rest.render.hal

import grails.converters.XML
import grails.rest.Link
import grails.rest.render.RenderContext
import grails.rest.render.util.AbstractLinkingRenderer
import groovy.transform.CompileStatic
import grails.web.mime.MimeType
import org.grails.web.xml.PrettyPrintXMLStreamWriter
import org.grails.web.xml.StreamingMarkupWriter
import org.grails.web.xml.XMLStreamWriter
import org.grails.datastore.mapping.model.PersistentEntity
import org.grails.datastore.mapping.model.types.ToOne
import org.springframework.beans.PropertyAccessorFactory
import org.springframework.http.HttpMethod

/**
 * Renders domain instances in HAL XML format (see http://stateless.co/hal_specification.html)
 *
 * @author Graeme Rocher
 * @since 2.3
 */
@CompileStatic
class HalXmlRenderer extends AbstractLinkingRenderer {
    public static final MimeType MIME_TYPE = MimeType.HAL_XML
    public static final String RESOURCE_TAG = "resource"
    public static final String LINK_TAG = "link"
    public static final String RELATIONSHIP_ATTRIBUTE = "rel"

    private static final MimeType[] DEFAULT_MIME_TYPES = [MIME_TYPE] as MimeType[]

    HalXmlRenderer(Class targetType) {
        super(targetType, DEFAULT_MIME_TYPES)
    }

    HalXmlRenderer(Class targetType, MimeType mimeType) {
        super(targetType, mimeType)
    }

    HalXmlRenderer(Class targetType, MimeType[] mimeTypes) {
        super(targetType, mimeTypes)
    }

    @Override
    void renderInternal(T object, RenderContext context) {
        final streamingWriter = new StreamingMarkupWriter(context.writer, encoding)
        XMLStreamWriter w = prettyPrint ? new PrettyPrintXMLStreamWriter(streamingWriter) : new XMLStreamWriter(streamingWriter)
        XML xml = new XML(w)


        final entity = mappingContext.getPersistentEntity(object.class.name)
        boolean isDomain = entity != null

        Set writtenObjects = []
        w.startDocument(encoding, "1.0")

        if (isDomain) {
            writeDomainWithEmbeddedAndLinks(entity, object, context, xml, writtenObjects)
        }
        else if (object instanceof Collection) {
            XMLStreamWriter writer = xml.getWriter()
            startResourceTagForCurrentPath(context, writer)
            for(o in ((Collection)object)) {
                final currentEntity = mappingContext.getPersistentEntity(o.class.name)
                if (currentEntity) {
                    writeDomainWithEmbeddedAndLinks(currentEntity, o, context, xml, writtenObjects)
                }
            }
            writer.end()
        }
        else {
            XMLStreamWriter writer = xml.getWriter()
            startResourceTagForCurrentPath(context, writer)
            writeExtraLinks(object, context.locale, xml)
            final bean = PropertyAccessorFactory.forBeanPropertyAccess(object)
            final propertyDescriptors = bean.propertyDescriptors
            for(pd in propertyDescriptors) {
                final propertyName = pd.name
                if (DEFAULT_EXCLUDES.contains(propertyName)) continue
                if (shouldIncludeProperty(context, object, propertyName)) {
                    if (pd.readMethod && pd.writeMethod) {
                        writer.startNode(propertyName)
                        xml.convertAnother(bean.getPropertyValue(propertyName))
                        writer.end()
                    }
                }
            }
            writer.end()

        }


    }

    protected void startResourceTagForCurrentPath(RenderContext context, XMLStreamWriter writer) {
        final locale = context.locale
        String resourceHref = linkGenerator.link(uri: context.resourcePath, method: HttpMethod.GET, absolute: absoluteLinks)
        final title = getResourceTitle(context.resourcePath, locale)
        startResourceTag(writer, resourceHref, locale, title)
    }

    protected void writeDomainWithEmbeddedAndLinks(PersistentEntity entity, object, RenderContext context, XML xml, Set writtenObjects) {
        final locale = context.locale
        String resourceHref = linkGenerator.link(resource: object, method: HttpMethod.GET, absolute: absoluteLinks)
        final title = getLinkTitle(entity, locale)
        XMLStreamWriter writer = xml.getWriter()
        startResourceTag(writer, resourceHref, locale, title)
        final metaClass = GroovySystem.metaClassRegistry.getMetaClass(entity.javaClass)
        final associationMap = writeAssociationLinks(context,object, locale, xml, entity, metaClass)
        writeDomain(context, metaClass, entity, object, xml)

        if (associationMap) {
            for (entry in associationMap.entrySet()) {
                final property = entry.key
                final isSingleEnded = property instanceof ToOne
                if (isSingleEnded) {
                    Object value = entry.value
                    if (writtenObjects.contains(value)) {
                        continue
                    }

                    if (value != null) {
                        final associatedEntity = property.associatedEntity
                        if (associatedEntity) {
                            writtenObjects << value
                            writeDomainWithEmbeddedAndLinks(associatedEntity, value, context, xml, writtenObjects)
                        }
                    }
                } else {
                    final associatedEntity = property.associatedEntity
                    if (associatedEntity) {
                        for (obj in entry.value) {
                            writtenObjects << obj
                            writeDomainWithEmbeddedAndLinks(associatedEntity, obj, context, xml, writtenObjects)
                        }
                    }
                }

            }
        }
        writer.end()
    }

    protected void startResourceTag(XMLStreamWriter writer, String resourceHref, Locale locale, String title) {
        writer.startNode(RESOURCE_TAG)
            .attribute(HREF_ATTRIBUTE, resourceHref)
            .attribute(HREFLANG_ATTRIBUTE, locale.language)

        if (title) {
            writer.attribute(TITLE_ATTRIBUTE, title)
        }
    }

    void writeLink(Link link, Locale locale, writerObject) {
        XMLStreamWriter writer = ((XML) writerObject).getWriter()
        writer.startNode(LINK_TAG)
            .attribute(RELATIONSHIP_ATTRIBUTE, link.rel)
            .attribute(HREF_ATTRIBUTE, link.href)
            .attribute(HREFLANG_ATTRIBUTE, (link.hreflang ?: locale).language)

        final title = link.title
        if (title) {
            writer.attribute(TITLE_ATTRIBUTE, title)
        }
        final contentType = link.contentType
        if (contentType) {
            writer.attribute(TYPE_ATTRIBUTE, contentType)
        }

        if (link.templated) {
            writer.attribute(TEMPLATED_ATTRIBUTE,"true")
        }
        if (link.deprecated) {
            writer.attribute(DEPRECATED_ATTRIBUTE,"true")
        }
        writer.end()
    }

    @Override
    protected void writeDomainProperty(value, String propertyName, writerObject) {
        final xml = (XML) writerObject
        XMLStreamWriter writer = xml.getWriter()

        writer.startNode(propertyName)
        xml.convertAnother(value)
        writer.end()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy