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