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