001// Generated by delombok at Mon Oct 12 22:51:05 BST 2020 002/* 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 */ 020package org.apache.isis.viewer.restfulobjects.rendering.service.conneg; 021 022import java.util.Collection; 023import java.util.List; 024import java.util.stream.Collectors; 025import javax.inject.Inject; 026import javax.inject.Named; 027import javax.ws.rs.core.MediaType; 028import javax.ws.rs.core.Response; 029import javax.ws.rs.core.Response.ResponseBuilder; 030import org.springframework.beans.factory.annotation.Qualifier; 031import org.springframework.context.annotation.Primary; 032import org.springframework.core.annotation.Order; 033import org.springframework.stereotype.Service; 034import org.apache.isis.applib.annotation.OrderPrecedence; 035import org.apache.isis.applib.domain.DomainObjectList; 036import org.apache.isis.core.config.IsisConfiguration; 037import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacet; 038import org.apache.isis.core.metamodel.facets.collections.CollectionFacet; 039import org.apache.isis.core.metamodel.interactions.managed.ManagedAction; 040import org.apache.isis.core.metamodel.interactions.managed.ManagedCollection; 041import org.apache.isis.core.metamodel.interactions.managed.ManagedProperty; 042import org.apache.isis.core.metamodel.spec.ManagedObject; 043import org.apache.isis.core.metamodel.spec.ObjectSpecification; 044import org.apache.isis.core.metamodel.specloader.SpecificationLoader; 045import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 046import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; 047import org.apache.isis.viewer.restfulobjects.applib.RestfulResponse; 048import org.apache.isis.viewer.restfulobjects.rendering.Caching; 049import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext; 050import org.apache.isis.viewer.restfulobjects.rendering.Responses; 051import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException; 052import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ActionResultReprRenderer; 053import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.DomainObjectReprRenderer; 054import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectActionReprRenderer; 055import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation; 056import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectCollectionReprRenderer; 057import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectPropertyReprRenderer; 058import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService; 059 060@Service 061@Named("isisRoRendering.ContentNegotiationServiceForRestfulObjectsV1_0") 062@Order(OrderPrecedence.MIDPOINT) 063@Primary 064@Qualifier("RestfulObjectsV1_0") 065public class ContentNegotiationServiceForRestfulObjectsV1_0 implements ContentNegotiationService { 066 protected final IsisConfiguration configuration; 067 protected final SpecificationLoader specificationLoader; 068 private final boolean strictAcceptChecking; 069 070 @Inject 071 public ContentNegotiationServiceForRestfulObjectsV1_0(final IsisConfiguration configuration, final SpecificationLoader specificationLoader) { 072 this.configuration = configuration; 073 this.specificationLoader = specificationLoader; 074 this.strictAcceptChecking = configuration.getViewer().getRestfulobjects().isStrictAcceptChecking(); 075 } 076 077 @Override 078 public ResponseBuilder buildResponse(final IResourceContext resourceContext, final ManagedObject objectAdapter) { 079 final List<MediaType> list = resourceContext.getAcceptableMediaTypes(); 080 ensureCompatibleAcceptHeader(RepresentationType.DOMAIN_OBJECT, list); 081 final ResponseBuilder responseBuilder = buildResponseTo(resourceContext, objectAdapter, JsonRepresentation.newMap(), null); 082 return responseBuilder(responseBuilder); 083 } 084 085 /** 086 * Not API 087 */ 088 ResponseBuilder buildResponseTo(final IResourceContext resourceContext, final ManagedObject objectAdapter, final JsonRepresentation representationIfAnyRequired, final JsonRepresentation rootRepresentation) { 089 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(resourceContext, null, representationIfAnyRequired); 090 renderer.with(objectAdapter).includesSelf(); 091 final ResponseBuilder responseBuilder = Responses.ofOk(renderer, Caching.NONE, rootRepresentation); 092 { 093 final RepresentationService.Intent intent = resourceContext.getIntent(); 094 if (intent == RepresentationService.Intent.JUST_CREATED) { 095 responseBuilder.status(Response.Status.CREATED); 096 } 097 } 098 return responseBuilder; 099 } 100 101 @Override 102 public ResponseBuilder buildResponse(final IResourceContext resourceContext, final ManagedProperty objectAndProperty) { 103 final List<MediaType> list = resourceContext.getAcceptableMediaTypes(); 104 ensureCompatibleAcceptHeader(RepresentationType.OBJECT_PROPERTY, list); 105 final ObjectPropertyReprRenderer renderer = new ObjectPropertyReprRenderer(resourceContext); 106 renderer.with(objectAndProperty).usingLinkTo(resourceContext.getObjectAdapterLinkTo()); 107 final org.apache.isis.core.metamodel.interactions.managed.ManagedMember.RepresentationMode repMode = objectAndProperty.getRepresentationMode(); 108 if (repMode.isExplicit()) { 109 renderer.withMemberMode(repMode); 110 } 111 final ResponseBuilder responseBuilder = Responses.ofOk(renderer, Caching.NONE); 112 return responseBuilder; 113 } 114 115 @Override 116 public ResponseBuilder buildResponse(final IResourceContext resourceContext, final ManagedCollection objectAndCollection) { 117 final List<MediaType> list = resourceContext.getAcceptableMediaTypes(); 118 ensureCompatibleAcceptHeader(RepresentationType.OBJECT_COLLECTION, list); 119 final ResponseBuilder responseBuilder = buildResponseTo(resourceContext, objectAndCollection, JsonRepresentation.newMap(), null); 120 return responseBuilder(responseBuilder); 121 } 122 123 /** 124 * Not API 125 */ 126 ResponseBuilder buildResponseTo(final IResourceContext resourceContext, final ManagedCollection objectAndCollection, final JsonRepresentation representation, final JsonRepresentation rootRepresentation) { 127 final ObjectCollectionReprRenderer renderer = new ObjectCollectionReprRenderer(resourceContext, null, null, representation); 128 renderer.with(objectAndCollection).usingLinkTo(resourceContext.getObjectAdapterLinkTo()); 129 if (objectAndCollection.getRepresentationMode().isExplicit()) { 130 renderer.withMemberMode(objectAndCollection.getRepresentationMode()); 131 } 132 return Responses.ofOk(renderer, Caching.NONE, rootRepresentation); 133 } 134 135 @Override 136 public ResponseBuilder buildResponse(final IResourceContext resourceContext, final ManagedAction objectAndAction) { 137 final List<MediaType> list = resourceContext.getAcceptableMediaTypes(); 138 ensureCompatibleAcceptHeader(RepresentationType.OBJECT_ACTION, list); 139 final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(resourceContext); 140 renderer.with(objectAndAction).usingLinkTo(resourceContext.getObjectAdapterLinkTo()).asStandalone(); 141 final ResponseBuilder responseBuilder = Responses.ofOk(renderer, Caching.NONE); 142 return responseBuilder(responseBuilder); 143 } 144 145 @Override 146 public ResponseBuilder buildResponse(final IResourceContext resourceContext, final ObjectAndActionInvocation objectAndActionInvocation) { 147 final ResponseBuilder responseBuilder; 148 final List<MediaType> acceptableMediaTypes = resourceContext.getAcceptableMediaTypes(); 149 if (isAccepted(RepresentationType.DOMAIN_OBJECT, acceptableMediaTypes, true)) { 150 final ManagedObject adapter; 151 final Collection<ManagedObject> collectionAdapters = objectAdaptersFrom(objectAndActionInvocation); 152 if (collectionAdapters != null) { 153 final ObjectSpecification elementSpec = elementSpecFrom(objectAndActionInvocation); 154 final String actionOwningType = actionOwningTypeFrom(objectAndActionInvocation); 155 final String actionId = actionIdFrom(objectAndActionInvocation); 156 final String actionArguments = actionArgumentsFrom(objectAndActionInvocation); 157 final DomainObjectList list = domainObjectListFrom(collectionAdapters, elementSpec, actionOwningType, actionId, actionArguments); 158 final org.apache.isis.core.metamodel.spec.ObjectSpecification listSpec = resourceContext.getSpecificationLoader().loadSpecification(list.getClass()); 159 adapter = ManagedObject.of(listSpec, list); 160 } else { 161 adapter = objectAndActionInvocation.getReturnedAdapter(); 162 } 163 responseBuilder = buildResponse(resourceContext, adapter); 164 } else if (isAccepted(RepresentationType.ACTION_RESULT, acceptableMediaTypes)) { 165 responseBuilder = buildResponseTo(resourceContext, objectAndActionInvocation, JsonRepresentation.newMap(), null); 166 } else { 167 throw RestfulObjectsApplicationException.create(RestfulResponse.HttpStatusCode.NOT_ACCEPTABLE); 168 } 169 return responseBuilder(responseBuilder); 170 } 171 172 private static String actionOwningTypeFrom(final ObjectAndActionInvocation objectAndActionInvocation) { 173 return objectAndActionInvocation.getAction().getOnType().getSpecId().asString(); 174 } 175 176 private static String actionIdFrom(final ObjectAndActionInvocation objectAndActionInvocation) { 177 return objectAndActionInvocation.getAction().getId(); 178 } 179 180 private static String actionArgumentsFrom(final ObjectAndActionInvocation objectAndActionInvocation) { 181 final StringBuilder buf = new StringBuilder(); 182 final org.apache.isis.commons.collections.Can<org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter> parameters = objectAndActionInvocation.getAction().getParameters(); 183 final org.apache.isis.commons.collections.Can<org.apache.isis.core.metamodel.spec.ManagedObject> argAdapters = objectAndActionInvocation.getArgAdapters(); 184 if (parameters.size() == argAdapters.size()) { 185 for (int i = 0; i < parameters.size(); i++) { 186 final int paramIndex = i; 187 final org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter param = parameters.getElseFail(paramIndex); 188 final org.apache.isis.core.metamodel.spec.ManagedObject argAdapter = argAdapters.getElseFail(paramIndex); 189 if (buf.length() > 0) { 190 buf.append(","); 191 } 192 buf.append(param.getName()).append("="); 193 buf.append(abbreviated(titleOf(argAdapter), 8)); 194 } 195 } 196 return buf.toString(); 197 } 198 199 private static String titleOf(final ManagedObject argumentAdapter) { 200 return argumentAdapter != null ? argumentAdapter.titleString(null) : ""; 201 } 202 203 private static String abbreviated(final String str, final int maxLength) { 204 return str.length() < maxLength ? str : str.substring(0, maxLength - 3) + "..."; 205 } 206 207 private static DomainObjectList domainObjectListFrom(final Collection<ManagedObject> collectionAdapters, final ObjectSpecification elementSpec, final String actionOwningType, final String actionId, final String actionArguments) { 208 final String title = titleFrom(collectionAdapters, elementSpec); 209 final DomainObjectList list = new DomainObjectList(title, elementSpec.getSpecId().asString(), actionOwningType, actionId, actionArguments); 210 for (final org.apache.isis.core.metamodel.spec.ManagedObject adapter : collectionAdapters) { 211 list.getObjects().add(adapter.getPojo()); 212 } 213 return list; 214 } 215 216 private static String titleFrom(final Collection<ManagedObject> collectionAdapters, final ObjectSpecification elementSpec) { 217 final String singularName = elementSpec.getSingularName(); 218 final String pluralName = elementSpec.getPluralName(); 219 int size = collectionAdapters.size(); 220 final String title; 221 switch (size) { 222 case 0: 223 title = "0 " + pluralName; 224 break; 225 case 1: 226 title = "1 " + singularName; 227 break; 228 default: 229 title = size + " " + pluralName; 230 break; 231 } 232 return title; 233 } 234 235 private ObjectSpecification elementSpecFrom(final ObjectAndActionInvocation objectAndActionInvocation) { 236 final TypeOfFacet typeOfFacet = objectAndActionInvocation.getAction().getFacet(TypeOfFacet.class); 237 return typeOfFacet != null ? typeOfFacet.valueSpec() : specificationLoader.loadSpecification(Object.class); 238 } 239 240 private Collection<ManagedObject> objectAdaptersFrom(final ObjectAndActionInvocation objectAndActionInvocation) { 241 final ManagedObject returnedAdapter = objectAndActionInvocation.getReturnedAdapter(); 242 final ObjectSpecification returnType = objectAndActionInvocation.getAction().getReturnType(); 243 final CollectionFacet collectionFacet = returnType.getFacet(CollectionFacet.class); 244 return collectionFacet != null ? collectionFacet.stream(returnedAdapter).collect(Collectors.toList()) : null; 245 } 246 247 /** 248 * Not API 249 */ 250 ResponseBuilder buildResponseTo(final IResourceContext resourceContext, final ObjectAndActionInvocation objectAndActionInvocation, final JsonRepresentation representation, final JsonRepresentation rootRepresentation) { 251 final ActionResultReprRenderer renderer = new ActionResultReprRenderer(resourceContext, null, objectAndActionInvocation.getSelfLink(), representation); 252 renderer.with(objectAndActionInvocation).using(resourceContext.getObjectAdapterLinkTo()); 253 final ResponseBuilder responseBuilder = Responses.ofOk(renderer, Caching.NONE, rootRepresentation); 254 return responseBuilder; 255 } 256 257 /** 258 * For easy subclassing to further customize, eg additional headers 259 */ 260 protected ResponseBuilder responseBuilder(final ResponseBuilder responseBuilder) { 261 return responseBuilder; 262 } 263 264 private void ensureCompatibleAcceptHeader(final RepresentationType representationType, final List<MediaType> acceptableMediaTypes) { 265 if (!strictAcceptChecking) { 266 return; 267 } 268 if (representationType == null) { 269 return; 270 } 271 // RestEasy will check the basic media types... 272 // ... so we just need to check the profile paramter 273 final String producedProfile = representationType.getMediaTypeProfile(); 274 if (producedProfile == null) { 275 return; 276 } 277 boolean accepted = isAccepted(producedProfile, acceptableMediaTypes); 278 if (!accepted) { 279 throw RestfulObjectsApplicationException.create(RestfulResponse.HttpStatusCode.NOT_ACCEPTABLE); 280 } 281 } 282 283 private boolean isAccepted(final RepresentationType representationType, final List<MediaType> acceptableMediaTypes) { 284 return isAccepted(representationType, acceptableMediaTypes, strictAcceptChecking); 285 } 286 287 private boolean isAccepted(final RepresentationType representationType, final List<MediaType> acceptableMediaTypes, final boolean strictAcceptChecking) { 288 if (!strictAcceptChecking) { 289 return true; 290 } 291 final String producedProfile = representationType.getMediaTypeProfile(); 292 if (producedProfile == null) { 293 throw new IllegalArgumentException("RepresentationType " + representationType + " does not specify a \'profile\' parameter"); 294 } 295 return isAccepted(producedProfile, acceptableMediaTypes); 296 } 297 298 private static boolean isAccepted(final String producedProfile, final List<MediaType> acceptableMediaTypes) { 299 for (MediaType mediaType : acceptableMediaTypes) { 300 String acceptedProfileValue = mediaType.getParameters().get("profile"); 301 if (acceptedProfileValue == null) { 302 continue; 303 } 304 if (!producedProfile.equals(acceptedProfileValue)) { 305 return false; 306 } 307 } 308 return true; 309 } 310}