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}