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