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