001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.isis.viewer.restfulobjects.rendering.domainobjects; 018 019import org.codehaus.jackson.node.NullNode; 020 021import org.apache.isis.applib.annotation.Where; 022import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 023import org.apache.isis.core.metamodel.consent.Consent; 024import org.apache.isis.core.metamodel.facetapi.Facet; 025import org.apache.isis.core.metamodel.spec.ObjectSpecification; 026import org.apache.isis.core.metamodel.spec.feature.ObjectMember; 027import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 028import org.apache.isis.viewer.restfulobjects.applib.Rel; 029import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; 030import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs; 031import org.apache.isis.viewer.restfulobjects.rendering.RendererContext; 032import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract; 033 034public abstract class AbstractObjectMemberReprRenderer<R extends ReprRendererAbstract<R, ObjectAndMember<T>>, T extends ObjectMember> extends ReprRendererAbstract<R, ObjectAndMember<T>> { 035 036 protected enum Mode { 037 INLINE, FOLLOWED, STANDALONE, MUTATED, ARGUMENTS, EVENT_SERIALIZATION; 038 039 public boolean isInline() { 040 return this == INLINE; 041 } 042 043 public boolean isFollowed() { 044 return this == FOLLOWED; 045 } 046 047 public boolean isStandalone() { 048 return this == STANDALONE; 049 } 050 051 public boolean isMutated() { 052 return this == MUTATED; 053 } 054 055 public boolean isArguments() { 056 return this == ARGUMENTS; 057 } 058 059 public boolean isEventSerialization() { 060 return this == EVENT_SERIALIZATION; 061 } 062 } 063 064 protected ObjectAdapterLinkTo linkTo; 065 066 protected ObjectAdapter objectAdapter; 067 protected MemberType memberType; 068 protected T objectMember; 069 protected Mode mode = Mode.INLINE; // unless we determine otherwise 070 071 /** 072 * Not for rendering, but is the key that the representation being rendered will be held under. 073 * 074 * <p> 075 * Used to determine whether to follow links; only populated for {@link Mode#INLINE inline} Mode. 076 */ 077 private final String memberId; 078 private final Where where; 079 080 public AbstractObjectMemberReprRenderer(final RendererContext resourceContext, final LinkFollowSpecs linkFollower, String memberId, final RepresentationType representationType, final JsonRepresentation representation, Where where) { 081 super(resourceContext, linkFollower, representationType, representation); 082 this.memberId = memberId; 083 this.where = where; 084 } 085 086 protected String getMemberId() { 087 return memberId; 088 } 089 090 @Override 091 public R with(final ObjectAndMember<T> objectAndMember) { 092 this.objectAdapter = objectAndMember.getObjectAdapter(); 093 this.objectMember = objectAndMember.getMember(); 094 this.memberType = MemberType.determineFrom(objectMember); 095 usingLinkTo(new DomainObjectLinkTo()); 096 097 return cast(this); 098 } 099 100 /** 101 * Must be called after {@link #with(ObjectAndMember)} (which provides the 102 * {@link #objectAdapter}). 103 */ 104 public R usingLinkTo(final ObjectAdapterLinkTo linkTo) { 105 this.linkTo = linkTo.usingUrlBase(rendererContext).with(objectAdapter); 106 return cast(this); 107 } 108 109 /** 110 * Indicate that this is a standalone representation. 111 */ 112 public R asStandalone() { 113 mode = Mode.STANDALONE; 114 return cast(this); 115 } 116 117 public R asEventSerialization() { 118 mode = Mode.EVENT_SERIALIZATION; 119 return cast(this); 120 } 121 122 /** 123 * Indicate that this is a representation to include as the result of a 124 * followed link. 125 */ 126 public R asFollowed() { 127 mode = Mode.FOLLOWED; 128 return cast(this); 129 } 130 131 /** 132 * Indicates that the representation was produced as the result of a 133 * resource that mutated the state. 134 * 135 * <p> 136 * The effect of this is to suppress the link to self. 137 */ 138 public R asMutated() { 139 mode = Mode.MUTATED; 140 return cast(this); 141 } 142 143 public R asArguments() { 144 mode = Mode.ARGUMENTS; 145 return cast(this); 146 } 147 148 /** 149 * For subclasses to call from their {@link #render()} method. 150 */ 151 protected void renderMemberContent() { 152 representation.mapPut("id", objectMember.getId()); 153 154 if(!mode.isArguments()) { 155 representation.mapPut("memberType", memberType.getName()); 156 } 157 158 if (mode.isInline()) { 159 addDetailsLinkIfPersistent(); 160 } 161 162 if (mode.isStandalone()) { 163 addLinkToSelf(); 164 } 165 166 if (mode.isStandalone() || mode.isMutated()) { 167 addLinkToUp(); 168 } 169 170 if (mode.isFollowed() || mode.isStandalone() || mode.isMutated()) { 171 addMutatorsIfEnabled(); 172 173 putExtensionsIsisProprietary(); 174 addLinksToFormalDomainModel(); 175 addLinksIsisProprietary(); 176 } 177 } 178 179 private void addLinkToSelf() { 180 getLinks().arrayAdd(linkTo.memberBuilder(Rel.SELF, memberType, objectMember).build()); 181 } 182 183 private void addLinkToUp() { 184 getLinks().arrayAdd(linkTo.builder(Rel.UP).build()); 185 } 186 187 protected abstract void addMutatorsIfEnabled(); 188 189 /** 190 * For subclasses to call back to when {@link #addMutatorsIfEnabled() adding 191 * mutators}. 192 */ 193 protected void addLinkFor(final MutatorSpec mutatorSpec) { 194 if (!hasMemberFacet(mutatorSpec.mutatorFacetType)) { 195 return; 196 } 197 final JsonRepresentation arguments = mutatorArgs(mutatorSpec); 198 final RepresentationType representationType = memberType.getRepresentationType(); 199 final JsonRepresentation mutatorLink = linkToForMutatorInvoke().memberBuilder(mutatorSpec.rel, memberType, objectMember, representationType, mutatorSpec.suffix).withHttpMethod(mutatorSpec.httpMethod).withArguments(arguments).build(); 200 getLinks().arrayAdd(mutatorLink); 201 } 202 203 /** 204 * Hook to allow actions to render invoke links that point to the 205 * contributing service. 206 */ 207 protected ObjectAdapterLinkTo linkToForMutatorInvoke() { 208 return linkTo; 209 } 210 211 /** 212 * Default implementation (common to properties and collections) that can be 213 * overridden (ie by actions) if required. 214 */ 215 protected JsonRepresentation mutatorArgs(final MutatorSpec mutatorSpec) { 216 if (mutatorSpec.arguments.isNone()) { 217 return null; 218 } 219 if (mutatorSpec.arguments.isOne()) { 220 final JsonRepresentation repr = JsonRepresentation.newMap(); 221 repr.mapPut("value", NullNode.getInstance()); // force a null into 222 // the map 223 return repr; 224 } 225 // overridden by actions 226 throw new UnsupportedOperationException("override mutatorArgs() to populate for many arguments"); 227 } 228 229 private void addDetailsLinkIfPersistent() { 230 if (!objectAdapter.representsPersistent()) { 231 return; 232 } 233 final JsonRepresentation link = linkTo.memberBuilder(Rel.DETAILS, memberType, objectMember).build(); 234 getLinks().arrayAdd(link); 235 236 final LinkFollowSpecs membersLinkFollower = getLinkFollowSpecs(); 237 final LinkFollowSpecs detailsLinkFollower = membersLinkFollower.follow("links"); 238 239 // create a temporary map that looks the same as the member map we'll be following 240 final JsonRepresentation memberMap = JsonRepresentation.newMap(); 241 memberMap.mapPut(getMemberId(), this.representation); 242 if (membersLinkFollower.matches(memberMap) && detailsLinkFollower.matches(link)) { 243 followDetailsLink(link); 244 } 245 return; 246 } 247 248 protected abstract void followDetailsLink(JsonRepresentation detailsLink); 249 250 protected final void putDisabledReasonIfDisabled() { 251 final String disabledReasonRep = usability().getReason(); 252 representation.mapPut("disabledReason", disabledReasonRep); 253 } 254 255 protected abstract void putExtensionsIsisProprietary(); 256 257 protected abstract void addLinksToFormalDomainModel(); 258 259 protected abstract void addLinksIsisProprietary(); 260 261 /** 262 * Convenience method. 263 */ 264 public boolean isMemberVisible() { 265 return visibility().isAllowed(); 266 } 267 268 protected <F extends Facet> F getMemberSpecFacet(final Class<F> facetType) { 269 final ObjectSpecification otoaSpec = objectMember.getSpecification(); 270 return otoaSpec.getFacet(facetType); 271 } 272 273 protected boolean hasMemberFacet(final Class<? extends Facet> facetType) { 274 return objectMember.getFacet(facetType) != null; 275 } 276 277 protected Consent usability() { 278 return objectMember.isUsable(getRendererContext().getAuthenticationSession(), objectAdapter, where); 279 } 280 281 protected Consent visibility() { 282 return objectMember.isVisible(getRendererContext().getAuthenticationSession(), objectAdapter, where); 283 } 284 285}