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