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}