001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019package org.apache.isis.viewer.restfulobjects.rendering.service.conneg;
020
021import java.util.List;
022
023import javax.inject.Inject;
024import javax.inject.Named;
025import javax.ws.rs.core.MediaType;
026import javax.ws.rs.core.Response;
027
028import org.springframework.beans.factory.annotation.Qualifier;
029import org.springframework.core.annotation.Order;
030import org.springframework.stereotype.Service;
031
032import org.apache.isis.applib.annotation.OrderPrecedence;
033import org.apache.isis.applib.services.conmap.ContentMappingService;
034import org.apache.isis.core.metamodel.spec.ManagedObject;
035import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
036import org.apache.isis.viewer.restfulobjects.applib.RestfulResponse;
037import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext;
038import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
039import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
040
041/**
042 * Handles content negotiation for accept headers requiring <code>application/json</code> or <code>application/xml</code>and specifying an x-ro-domain-type; will delegate to
043 * any available {@link ContentMappingService}s to (try to) map the result object into the required representation if possible.
044 *
045 * <p>
046 *     In the accept header the profile is also checked dependent on the resource being invoked; either <code>profile="urn:org.restfulobjects:repr-types/object"</code> for an object representation, or <code>profile="profile=urn:org.restfulobjects:repr-types/action-result"</code> for an action result.
047 * </p>
048 *
049 * <p>
050 *     If the accept header specifies <code>application/xml</code> then the service additionally verifies that the (mapped) domain object's
051 *     runtime type is annotated with the JAXB {@link javax.xml.bind.annotation.XmlRootElement} annotation so that RestEasy is able to
052 *     unambiguously serialize it.
053 * </p>
054 */
055@Service
056@Named("isisRoRendering.ContentNegotiationServiceXRoDomainType")
057@Order(OrderPrecedence.MIDPOINT - 100)
058@Qualifier("XRoDomainType")
059public class ContentNegotiationServiceXRoDomainType extends ContentNegotiationServiceAbstract {
060
061    public static final String X_RO_DOMAIN_TYPE = "x-ro-domain-type";
062
063    @Inject private List<ContentMappingService> contentMappingServices;
064    
065    /**
066     * search for an accept header in form <code>application/xml;profile=urn:org.restfulobjects:repr-types/object;x-ro-domain-type=todoapp.dto.module.todoitem.ToDoItemDto</code>
067     */
068    @Override
069    public Response.ResponseBuilder buildResponse(
070            final IResourceContext renderContext,
071            final ManagedObject objectAdapter) {
072
073        final Object domainObject = objectOf(objectAdapter);
074        final RepresentationType representationType = RepresentationType.DOMAIN_OBJECT;
075
076        final MediaType mediaType = mediaTypeFrom(renderContext, representationType);
077        if (mediaType == null) {
078            return null;
079        }
080
081        return buildResponse(renderContext, domainObject, representationType);
082    }
083
084    protected MediaType mediaTypeFrom(
085            final IResourceContext renderContext,
086            final RepresentationType representationType) {
087        final List<MediaType> acceptableMediaTypes = renderContext.getAcceptableMediaTypes();
088        MediaType mediaType =
089                representationType.matchesXmlProfileWithParameter(acceptableMediaTypes, X_RO_DOMAIN_TYPE);
090
091        if (mediaType == null) {
092            mediaType =
093                    representationType.matchesJsonProfileWithParameter(acceptableMediaTypes, X_RO_DOMAIN_TYPE);
094        }
095        return mediaType;
096    }
097
098    /**
099     * search for an accept header in form <code>application/xml;profile=urn:org.restfulobjects:repr-types/action-result;x-ro-domain-type=todoapp.dto.module.todoitem.ToDoItemDto</code>
100     */
101    @Override
102    public Response.ResponseBuilder buildResponse(
103            final IResourceContext renderContext,
104            final ObjectAndActionInvocation objectAndActionInvocation) {
105
106        final RepresentationType representationType = RepresentationType.ACTION_RESULT;
107
108        final MediaType mediaType = mediaTypeFrom(renderContext, representationType);
109        if (mediaType == null) {
110            return null;
111        }
112
113        final Object domainObject = returnedObjectOf(objectAndActionInvocation);
114        if(domainObject == null) {
115            throw RestfulObjectsApplicationException.create(RestfulResponse.HttpStatusCode.NOT_FOUND);
116        }
117        return buildResponse(renderContext, domainObject, representationType);
118    }
119
120    protected Response.ResponseBuilder buildResponse(
121            final IResourceContext renderContext,
122            final Object domainObject,
123            final RepresentationType representationType) {
124
125        final List<MediaType> acceptableMediaTypes = renderContext.getAcceptableMediaTypes();
126
127        final MediaType mediaType = mediaTypeFrom(renderContext, representationType);
128        if (mediaType == null) {
129            return null;
130        }
131
132        final String xRoDomainType = mediaType.getParameters().get(X_RO_DOMAIN_TYPE);
133        final Class<?> domainType = loadClass(xRoDomainType);
134
135        final Object mappedDomainObject = map(domainObject, acceptableMediaTypes);
136
137        ensureDomainObjectAssignable(xRoDomainType, domainType, mappedDomainObject);
138
139        if("xml".equals(mediaType.getSubtype())) {
140            ensureJaxbAnnotated(mappedDomainObject.getClass());
141        }
142
143        return Response.ok(mappedDomainObject, mediaType);
144    }
145
146    /**
147     * Delegates to either the applib {@link ContentMappingService}.
148     */
149    protected Object map(
150            final Object domainObject,
151            final List<MediaType> acceptableMediaTypes) {
152
153        for (ContentMappingService contentMappingService : contentMappingServices) {
154            Object mappedObject = contentMappingService.map(domainObject, acceptableMediaTypes);
155            if(mappedObject != null) {
156                return mappedObject;
157            }
158        }
159
160        return domainObject;
161    }
162
163    
164
165}