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