/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.isis.viewer.restfulobjects.rendering.domainobjects;
import com.fasterxml.jackson.databind.node.NullNode;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
import org.apache.isis.viewer.restfulobjects.applib.Rel;
import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs;
import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
public abstract class AbstractObjectMemberReprRenderer<R extends ReprRendererAbstract<R, ObjectAndMember<T>>, T extends ObjectMember> extends ReprRendererAbstract<R, ObjectAndMember<T>> {
protected enum Mode {
INLINE, FOLLOWED, STANDALONE, MUTATED, ARGUMENTS, EVENT_SERIALIZATION;
public boolean isInline() {
return this == INLINE;
}
public boolean isFollowed() {
return this == FOLLOWED;
}
public boolean isStandalone() {
return this == STANDALONE;
}
public boolean isMutated() {
return this == MUTATED;
}
public boolean isArguments() {
return this == ARGUMENTS;
}
public boolean isEventSerialization() {
return this == EVENT_SERIALIZATION;
}
}
protected ObjectAdapterLinkTo linkTo;
protected ObjectAdapter objectAdapter;
protected Mode mode = Mode.INLINE; // unless we determine otherwise
/**
* Derived from {@link #objectMember} using {@link org.apache.isis.viewer.restfulobjects.rendering.domainobjects.MemberType#determineFrom(org.apache.isis.core.metamodel.spec.feature.ObjectFeature)}
*/
protected MemberType objectMemberType;
protected T objectMember;
/**
* Not for rendering, but is the key that the representation being rendered will be held under.
*
* <p>
* Used to determine whether to follow links; only populated for {@link Mode#INLINE inline} Mode.
*/
private String memberId;
private final Where where;
public AbstractObjectMemberReprRenderer(
final RendererContext rendererContext,
final LinkFollowSpecs linkFollower,
final String memberId,
final RepresentationType representationType,
final JsonRepresentation representation,
final Where where) {
super(rendererContext, linkFollower, representationType, representation);
this.memberId = memberId;
this.where = where;
}
protected String getMemberId() {
return memberId;
}
@Override
public R with(final ObjectAndMember<T> objectAndMember) {
this.objectAdapter = objectAndMember.getObjectAdapter();
this.objectMember = objectAndMember.getMember();
this.objectMemberType = MemberType.determineFrom(objectMember);
this.memberId = objectMember.getId();
usingLinkTo(new DomainObjectLinkTo());
return cast(this);
}
/**
* Must be called after {@link #with(ObjectAndMember)} (which provides the
* {@link #objectAdapter}).
*/
public R usingLinkTo(final ObjectAdapterLinkTo linkTo) {
this.linkTo = linkTo.usingUrlBase(rendererContext).with(objectAdapter);
return cast(this);
}
/**
* Indicate that this is a standalone representation.
*/
public R asStandalone() {
mode = Mode.STANDALONE;
return cast(this);
}
public R asEventSerialization() {
mode = Mode.EVENT_SERIALIZATION;
return cast(this);
}
/**
* Indicate that this is a representation to include as the result of a
* followed link.
*/
public R asFollowed() {
mode = Mode.FOLLOWED;
return cast(this);
}
/**
* Indicates that the representation was produced as the result of a
* resource that mutated the state.
*
* <p>
* The effect of this is to suppress the link to self.
*/
public R asMutated() {
mode = Mode.MUTATED;
return cast(this);
}
public R asArguments() {
mode = Mode.ARGUMENTS;
return cast(this);
}
/**
* For subclasses to call from their {@link #render()} method.
*/
protected void renderMemberContent() {
if(!rendererContext.suppressMemberId()) {
representation.mapPut("id", objectMember.getId());
}
if(!mode.isArguments()) {
representation.mapPut("memberType", objectMemberType.getName());
}
if (mode.isInline() && !rendererContext.suppressMemberLinks()) {
addDetailsLinkIfPersistent();
}
if (mode.isStandalone()) {
addLinkToSelf();
}
if (mode.isStandalone() || mode.isMutated()) {
addLinkToUp();
}
if (mode.isFollowed() || mode.isStandalone() || mode.isMutated()) {
addMutatorLinksIfEnabled();
if(!mode.isInline() || !rendererContext.suppressUpdateLink()) {
putExtensionsIsisProprietary();
}
addLinksToFormalDomainModel();
addLinksIsisProprietary();
}
}
public void withMemberMode(MemberReprMode memberMode) {
if(memberMode == MemberReprMode.WRITE) {
this.asMutated();
} else {
this.asStandalone();
}
}
private void addLinkToSelf() {
getLinks().arrayAdd(linkTo.memberBuilder(Rel.SELF, objectMemberType, objectMember).build());
}
private void addLinkToUp() {
getLinks().arrayAdd(linkTo.builder(Rel.UP).build());
}
protected abstract void addMutatorLinksIfEnabled();
/**
* For subclasses to call back to when {@link #addMutatorLinksIfEnabled() adding
* mutators}.
*/
protected void addLinkFor(final MutatorSpec mutatorSpec) {
if (!hasMemberFacet(mutatorSpec.mutatorFacetType)) {
return;
}
final JsonRepresentation arguments = mutatorArgs(mutatorSpec);
final RepresentationType representationType = objectMemberType.getRepresentationType();
final JsonRepresentation mutatorLink = linkToForMutatorInvoke().memberBuilder(mutatorSpec.rel, objectMemberType, objectMember, representationType, mutatorSpec.suffix).withHttpMethod(mutatorSpec.httpMethod).withArguments(arguments).build();
getLinks().arrayAdd(mutatorLink);
}
/**
* Hook to allow actions to render invoke links that point to the
* contributing service.
*/
protected ObjectAdapterLinkTo linkToForMutatorInvoke() {
return linkTo;
}
/**
* Default implementation (common to properties and collections) that can be
* overridden (ie by actions) if required.
*/
protected JsonRepresentation mutatorArgs(final MutatorSpec mutatorSpec) {
if (mutatorSpec.arguments.isNone()) {
return null;
}
if (mutatorSpec.arguments.isOne()) {
final JsonRepresentation repr = JsonRepresentation.newMap();
repr.mapPut("value", NullNode.getInstance()); // force a null into
// the map
return repr;
}
// overridden by actions
throw new UnsupportedOperationException("override mutatorArgs() to populate for many arguments");
}
private void addDetailsLinkIfPersistent() {
if (!objectAdapter.representsPersistent()) {
return;
}
final JsonRepresentation link = linkTo.memberBuilder(Rel.DETAILS, objectMemberType, objectMember).build();
getLinks().arrayAdd(link);
final LinkFollowSpecs membersLinkFollower = getLinkFollowSpecs();
final LinkFollowSpecs detailsLinkFollower = membersLinkFollower.follow("links");
// create a temporary map that looks the same as the member map we'll be following
final JsonRepresentation memberMap = JsonRepresentation.newMap();
memberMap.mapPut(getMemberId(), representation);
if (membersLinkFollower.matches(memberMap) && detailsLinkFollower.matches(link)) {
followDetailsLink(link);
}
return;
}
protected abstract void followDetailsLink(JsonRepresentation detailsLink);
protected final void putDisabledReasonIfDisabled() {
if(rendererContext.suppressMemberDisabledReason()) {
return;
}
final String disabledReasonRep = usability().getReason();
representation.mapPut("disabledReason", disabledReasonRep);
}
protected abstract void putExtensionsIsisProprietary();
protected abstract void addLinksToFormalDomainModel();
protected abstract void addLinksIsisProprietary();
/**
* Convenience method.
*/
public boolean isMemberVisible() {
return visibility().isAllowed();
}
/**
* Convenience method.
*/
protected <F extends Facet> F getMemberSpecFacet(final Class<F> facetType) {
final ObjectSpecification objetMemberSpec = objectMember.getSpecification();
return objetMemberSpec.getFacet(facetType);
}
protected boolean hasMemberFacet(final Class<? extends Facet> facetType) {
return objectMember.getFacet(facetType) != null;
}
protected Consent usability() {
return objectMember.isUsable(objectAdapter, getInteractionInitiatedBy(), where);
}
protected Consent visibility() {
return objectMember.isVisible(objectAdapter, getInteractionInitiatedBy(), where);
}
}