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.domainobjects; 020 021import com.fasterxml.jackson.databind.node.NullNode; 022 023import org.apache.isis.applib.annotation.Where; 024import org.apache.isis.core.metamodel.spec.feature.ObjectFeature; 025import org.apache.isis.core.metamodel.consent.Consent; 026import org.apache.isis.core.metamodel.facetapi.Facet; 027import org.apache.isis.core.metamodel.spec.ManagedObject; 028import org.apache.isis.core.metamodel.spec.ObjectSpecification; 029import org.apache.isis.core.metamodel.spec.feature.ObjectMember; 030import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 031import org.apache.isis.viewer.restfulobjects.applib.Rel; 032import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; 033import org.apache.isis.viewer.restfulobjects.rendering.IResourceContext; 034import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs; 035import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract; 036 037public abstract class AbstractObjectMemberReprRenderer<R extends ReprRendererAbstract<R, ObjectAndMember<T>>, T extends ObjectMember> 038extends ReprRendererAbstract<R, ObjectAndMember<T>> { 039 040 041 protected enum Mode { 042 INLINE, FOLLOWED, STANDALONE, MUTATED, ARGUMENTS, EVENT_SERIALIZATION; 043 044 public boolean isInline() { 045 return this == INLINE; 046 } 047 048 public boolean isFollowed() { 049 return this == FOLLOWED; 050 } 051 052 public boolean isStandalone() { 053 return this == STANDALONE; 054 } 055 056 public boolean isMutated() { 057 return this == MUTATED; 058 } 059 060 public boolean isArguments() { 061 return this == ARGUMENTS; 062 } 063 064 public boolean isEventSerialization() { 065 return this == EVENT_SERIALIZATION; 066 } 067 } 068 069 protected ObjectAdapterLinkTo linkTo; 070 071 protected ManagedObject objectAdapter; 072 protected Mode mode = Mode.INLINE; // unless we determine otherwise 073 /** 074 * Derived from {@link #objectMember} using {@link org.apache.isis.viewer.restfulobjects.rendering.domainobjects.MemberType#determineFrom(ObjectFeature)} 075 */ 076 protected MemberType objectMemberType; 077 protected T objectMember; 078 079 /** 080 * Not for rendering, but is the key that the representation being rendered will be held under. 081 * 082 * <p> 083 * Used to determine whether to follow links; only populated for {@link Mode#INLINE inline} Mode. 084 */ 085 private String memberId; 086 private final Where where; 087 088 public AbstractObjectMemberReprRenderer( 089 final IResourceContext resourceContext, 090 final LinkFollowSpecs linkFollower, 091 final String memberId, 092 final RepresentationType representationType, 093 final JsonRepresentation representation, 094 final Where where) { 095 super(resourceContext, linkFollower, representationType, representation); 096 this.memberId = memberId; 097 this.where = where; 098 } 099 100 protected String getMemberId() { 101 return memberId; 102 } 103 104 105 @Override 106 public R with(final ObjectAndMember<T> objectAndMember) { 107 this.objectAdapter = objectAndMember.getObjectAdapter(); 108 this.objectMember = objectAndMember.getMember(); 109 this.objectMemberType = MemberType.determineFrom(objectMember); 110 this.memberId = objectMember.getId(); 111 usingLinkTo(new DomainObjectLinkTo()); 112 113 return cast(this); 114 } 115 116 /** 117 * Must be called after {@link #with(ObjectAndMember)} (which provides the 118 * {@link #objectAdapter}). 119 */ 120 public R usingLinkTo(final ObjectAdapterLinkTo linkTo) { 121 this.linkTo = linkTo.usingUrlBase(resourceContext).with(objectAdapter); 122 return cast(this); 123 } 124 125 /** 126 * Indicate that this is a standalone representation. 127 */ 128 public R asStandalone() { 129 mode = Mode.STANDALONE; 130 return cast(this); 131 } 132 133 public R asEventSerialization() { 134 mode = Mode.EVENT_SERIALIZATION; 135 return cast(this); 136 } 137 138 /** 139 * Indicate that this is a representation to include as the result of a 140 * followed link. 141 */ 142 public R asFollowed() { 143 mode = Mode.FOLLOWED; 144 return cast(this); 145 } 146 147 /** 148 * Indicates that the representation was produced as the result of a 149 * resource that mutated the state. 150 * 151 * <p> 152 * The effect of this is to suppress the link to self. 153 */ 154 public R asMutated() { 155 mode = Mode.MUTATED; 156 return cast(this); 157 } 158 159 public R asArguments() { 160 mode = Mode.ARGUMENTS; 161 return cast(this); 162 } 163 164 /** 165 * For subclasses to call from their {@link #render()} method. 166 */ 167 protected void renderMemberContent() { 168 169 if(!resourceContext.suppressMemberId()) { 170 representation.mapPut("id", objectMember.getId()); 171 } 172 173 if(!mode.isArguments()) { 174 representation.mapPut("memberType", objectMemberType.getName()); 175 } 176 177 if (mode.isInline() && !resourceContext.suppressMemberLinks()) { 178 addDetailsLinkIfPersistent(); 179 } 180 181 if (mode.isStandalone()) { 182 addLinkToSelf(); 183 } 184 185 if (mode.isStandalone() || mode.isMutated()) { 186 addLinkToUp(); 187 } 188 189 if (mode.isFollowed() || mode.isStandalone() || mode.isMutated()) { 190 addMutatorLinksIfEnabled(); 191 192 if(!mode.isInline() || !resourceContext.suppressUpdateLink()) { 193 putExtensionsIsisProprietary(); 194 } 195 addLinksToFormalDomainModel(); 196 addLinksIsisProprietary(); 197 } 198 } 199 200 public void withMemberMode(MemberReprMode memberMode) { 201 if(memberMode == MemberReprMode.WRITE) { 202 this.asMutated(); 203 } else { 204 this.asStandalone(); 205 } 206 } 207 208 private void addLinkToSelf() { 209 getLinks().arrayAdd(linkTo.memberBuilder(Rel.SELF, objectMemberType, objectMember).build()); 210 } 211 212 private void addLinkToUp() { 213 getLinks().arrayAdd(linkTo.builder(Rel.UP).build()); 214 } 215 216 protected abstract void addMutatorLinksIfEnabled(); 217 218 /** 219 * For subclasses to call back to when {@link #addMutatorLinksIfEnabled() adding 220 * mutators}. 221 */ 222 protected void addLinkFor(final MutatorSpec mutatorSpec) { 223 if (!hasMemberFacet(mutatorSpec.mutatorFacetType)) { 224 return; 225 } 226 final JsonRepresentation arguments = mutatorArgs(mutatorSpec); 227 final RepresentationType representationType = objectMemberType.getRepresentationType(); 228 final JsonRepresentation mutatorLink = linkToForMutatorInvoke().memberBuilder(mutatorSpec.rel, objectMemberType, objectMember, representationType, mutatorSpec.suffix).withHttpMethod(mutatorSpec.httpMethod).withArguments(arguments).build(); 229 getLinks().arrayAdd(mutatorLink); 230 } 231 232 /** 233 * Hook to allow actions to render invoke links that point to the 234 * contributing service. 235 */ 236 protected ObjectAdapterLinkTo linkToForMutatorInvoke() { 237 return linkTo; 238 } 239 240 /** 241 * Default implementation (common to properties and collections) that can be 242 * overridden (ie by actions) if required. 243 */ 244 protected JsonRepresentation mutatorArgs(final MutatorSpec mutatorSpec) { 245 if (mutatorSpec.arguments.isNone()) { 246 return null; 247 } 248 if (mutatorSpec.arguments.isOne()) { 249 final JsonRepresentation repr = JsonRepresentation.newMap(); 250 repr.mapPut("value", NullNode.getInstance()); // force a null into 251 // the map 252 return repr; 253 } 254 // overridden by actions 255 throw new UnsupportedOperationException("override mutatorArgs() to populate for many arguments"); 256 } 257 258 private void addDetailsLinkIfPersistent() { 259 if (!ManagedObject.isIdentifiable(objectAdapter)) { 260 return; 261 } 262 final JsonRepresentation link = linkTo.memberBuilder(Rel.DETAILS, objectMemberType, objectMember).build(); 263 getLinks().arrayAdd(link); 264 265 final LinkFollowSpecs membersLinkFollower = getLinkFollowSpecs(); 266 final LinkFollowSpecs detailsLinkFollower = membersLinkFollower.follow("links"); 267 268 // create a temporary map that looks the same as the member map we'll be following 269 final JsonRepresentation memberMap = JsonRepresentation.newMap(); 270 memberMap.mapPut(getMemberId(), representation); 271 if (membersLinkFollower.matches(memberMap) && detailsLinkFollower.matches(link)) { 272 followDetailsLink(link); 273 } 274 return; 275 } 276 277 protected abstract void followDetailsLink(JsonRepresentation detailsLink); 278 279 protected final void putDisabledReasonIfDisabled() { 280 if(resourceContext.suppressMemberDisabledReason()) { 281 return; 282 } 283 final String disabledReasonRep = usability().getReason(); 284 representation.mapPut("disabledReason", disabledReasonRep); 285 } 286 287 protected abstract void putExtensionsIsisProprietary(); 288 289 protected abstract void addLinksToFormalDomainModel(); 290 291 protected abstract void addLinksIsisProprietary(); 292 293 /** 294 * Convenience method. 295 */ 296 public boolean isMemberVisible() { 297 return visibility().isAllowed(); 298 } 299 300 /** 301 * Convenience method. 302 */ 303 protected <F extends Facet> F getMemberSpecFacet(final Class<F> facetType) { 304 final ObjectSpecification objetMemberSpec = objectMember.getSpecification(); 305 return objetMemberSpec.getFacet(facetType); 306 } 307 308 protected boolean hasMemberFacet(final Class<? extends Facet> facetType) { 309 return objectMember.getFacet(facetType) != null; 310 } 311 312 protected Consent usability() { 313 return objectMember.isUsable(objectAdapter, getInteractionInitiatedBy(), where); 314 } 315 316 protected Consent visibility() { 317 return objectMember.isVisible(objectAdapter, getInteractionInitiatedBy(), where); 318 } 319 320}