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}