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}