/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.isis.viewer.restfulobjects.rendering.service.conneg; import java.util.List; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.NatureOfService; import org.apache.isis.core.metamodel.adapter.ObjectAdapter; import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse; import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException; import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation; import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService; import org.apache.isis.viewer.restfulobjects.rendering.service.conmap.ContentMappingService; /** * 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 * any available {@link ContentMappingService}s to (try to) map the result object into the required representation if possible. * * <p> * 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. * </p> * * <p> * If the accept header specifies <code>application/xml</code> then the service additionally verifies that the (mapped) domain object's * runtime type is annotated with the JAXB {@link javax.xml.bind.annotation.XmlRootElement} annotation so that RestEasy is able to * unambiguously serialize it. * </p> */ @DomainService( nature = NatureOfService.DOMAIN, // in effect, is the relative priority (lower numbers have higher priority) menuOrder = "" + (Integer.MAX_VALUE - 20) ) public class ContentNegotiationServiceXRoDomainType extends ContentNegotiationServiceAbstract { public static final String X_RO_DOMAIN_TYPE = "x-ro-domain-type"; /** * 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> */ @Override public Response.ResponseBuilder buildResponse( final RepresentationService.Context2 renderContext2, final ObjectAdapter objectAdapter) { final Object domainObject = objectOf(objectAdapter); final RepresentationType representationType = RepresentationType.DOMAIN_OBJECT; final MediaType mediaType = mediaTypeFrom(renderContext2, representationType); if (mediaType == null) { return null; } return buildResponse(renderContext2, domainObject, representationType); } protected MediaType mediaTypeFrom( final RepresentationService.Context2 renderContext2, final RepresentationType representationType) { final List<MediaType> acceptableMediaTypes = renderContext2.getAcceptableMediaTypes(); MediaType mediaType = representationType.matchesXmlProfileWithParameter(acceptableMediaTypes, X_RO_DOMAIN_TYPE); if (mediaType == null) { mediaType = representationType.matchesJsonProfileWithParameter(acceptableMediaTypes, X_RO_DOMAIN_TYPE); } return mediaType; } /** * 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> */ @Override public Response.ResponseBuilder buildResponse( final RepresentationService.Context2 renderContext2, final ObjectAndActionInvocation objectAndActionInvocation) { final RepresentationType representationType = RepresentationType.ACTION_RESULT; final MediaType mediaType = mediaTypeFrom(renderContext2, representationType); if (mediaType == null) { return null; } final Object domainObject = returnedObjectOf(objectAndActionInvocation); if(domainObject == null) { throw RestfulObjectsApplicationException.create(RestfulResponse.HttpStatusCode.NOT_FOUND); } return buildResponse(renderContext2, domainObject, representationType); } protected Response.ResponseBuilder buildResponse( final RepresentationService.Context2 renderContext2, final Object domainObject, final RepresentationType representationType) { final List<MediaType> acceptableMediaTypes = renderContext2.getAcceptableMediaTypes(); final MediaType mediaType = mediaTypeFrom(renderContext2, representationType); if (mediaType == null) { return null; } final String xRoDomainType = mediaType.getParameters().get(X_RO_DOMAIN_TYPE); final Class<?> domainType = loadClass(xRoDomainType); final Object mappedDomainObject = map(domainObject, acceptableMediaTypes, representationType); ensureDomainObjectAssignable(xRoDomainType, domainType, mappedDomainObject); if("xml".equals(mediaType.getSubtype())) { ensureJaxbAnnotated(mappedDomainObject.getClass()); } return Response.ok(mappedDomainObject, mediaType); } /** * Delegates to either the applib {@link org.apache.isis.applib.conmap.ContentMappingService}, else the * original non-applib {@link ContentMappingService}. */ protected Object map( final Object domainObject, final List<MediaType> acceptableMediaTypes, final RepresentationType representationType) { for (org.apache.isis.applib.conmap.ContentMappingService contentMappingService : contentMappingServices) { Object mappedObject = contentMappingService.map(domainObject, acceptableMediaTypes); if(mappedObject != null) { return mappedObject; } } for (ContentMappingService contentMappingService : legacyContentMappingServices) { Object mappedObject = contentMappingService.map(domainObject, acceptableMediaTypes, representationType); if(mappedObject != null) { return mappedObject; } } return domainObject; } @javax.inject.Inject List<org.apache.isis.applib.conmap.ContentMappingService> contentMappingServices; @javax.inject.Inject List<ContentMappingService> legacyContentMappingServices; }