/**
* 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 java.util.List;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.facets.object.notpersistable.NotPersistableFacet;
import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
import org.apache.isis.core.metamodel.services.ServiceUtil;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.Contributed;
import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
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.applib.RestfulHttpMethod;
import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder;
import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs;
import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
import org.apache.isis.viewer.restfulobjects.rendering.ReprRendererAbstract;
import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.DomainTypeReprRenderer;
import org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils;
public class DomainObjectReprRenderer extends ReprRendererAbstract<DomainObjectReprRenderer, ObjectAdapter> {
private static final String X_RO_DOMAIN_TYPE = "x-ro-domain-type";
public static LinkBuilder newLinkToBuilder(final RendererContext rendererContext, final Rel rel, final ObjectAdapter objectAdapter) {
String domainType = OidUtils.getDomainType(objectAdapter);
String instanceId = OidUtils.getInstanceId(objectAdapter);
final String url = "objects/" + domainType + "/" + instanceId;
return LinkBuilder.newBuilder(rendererContext, rel.getName(), RepresentationType.DOMAIN_OBJECT, url).withTitle(objectAdapter.titleString(null));
}
private static enum Mode {
REGULAR,
PERSIST_LINK_ARGUMENTS,
UPDATE_PROPERTIES_LINK_ARGUMENTS,
EVENT_SERIALIZATION;
public boolean isRegular() {
return this == REGULAR;
}
public boolean isPersistLinkArgs() {
return this == PERSIST_LINK_ARGUMENTS;
}
public boolean isUpdatePropertiesLinkArgs() {
return this == UPDATE_PROPERTIES_LINK_ARGUMENTS;
}
public boolean isEventSerialization() {
return this == EVENT_SERIALIZATION;
}
public boolean includeDescribedBy() {
return isRegular() || isPersistLinkArgs();
}
public boolean includeUp() {
return isRegular();
}
public boolean checkVisibility() {
return isRegular() || isUpdatePropertiesLinkArgs();
}
public boolean isArgs() {
return isPersistLinkArgs() || isUpdatePropertiesLinkArgs();
}
}
private ObjectAdapterLinkTo linkToBuilder;
private ObjectAdapter objectAdapter;
private Mode mode = Mode.REGULAR;
public DomainObjectReprRenderer(
final RendererContext resourceContext,
final LinkFollowSpecs linkFollower,
final JsonRepresentation representation) {
super(resourceContext, linkFollower, RepresentationType.DOMAIN_OBJECT, representation);
usingLinkToBuilder(new DomainObjectLinkTo());
}
/**
* Override the default {@link ObjectAdapterLinkTo} (that is used for
* generating links.
*/
public DomainObjectReprRenderer usingLinkToBuilder(final ObjectAdapterLinkTo objectAdapterLinkToBuilder) {
this.linkToBuilder = objectAdapterLinkToBuilder.usingUrlBase(rendererContext);
return this;
}
@Override
public DomainObjectReprRenderer with(final ObjectAdapter objectAdapter) {
this.objectAdapter = objectAdapter;
String domainTypeHref = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DOMAIN_TYPE, objectAdapter.getSpecification()).build().getString("href");
addMediaTypeParams(X_RO_DOMAIN_TYPE, domainTypeHref);
return this;
}
@Override
public JsonRepresentation render() {
if(representation == null) {
return null;
}
final boolean isService = objectAdapter.getSpecification().isService();
if (!(mode.isArgs())) {
// self, extensions.oid
if (objectAdapter.representsPersistent()) {
if (includesSelf) {
addLinkToSelf();
}
getExtensions().mapPut("oid", getOidStr());
}
// title
final String title = objectAdapter.titleString(null);
representation.mapPut("title", title);
// serviceId or instance Id
if (isService) {
representation.mapPut("serviceId", ServiceUtil.id(objectAdapter.getObject()));
} else {
final String domainType = getDomainType();
final String instanceId = getInstanceId();
if (domainType != null) {
representation.mapPut("domainType", domainType);
representation.mapPut("instanceId", instanceId);
}
}
}
// members
if(!mode.isUpdatePropertiesLinkArgs()) {
withMembers(objectAdapter);
}
// described by
if (mode.includeDescribedBy() && !rendererContext.suppressDescribedByLinks()) {
addLinkToDescribedBy();
}
if(isService && mode.includeUp()) {
addLinkToUp();
}
if (!mode.isArgs() && !rendererContext.objectPropertyValuesOnly()) {
// update/persist
addPersistLinkIfTransientAndPersistable();
addUpdatePropertiesLinkIfRequired();
// extensions
getExtensions().mapPut("isService", isService);
getExtensions().mapPut("isPersistent", objectAdapter.representsPersistent());
}
return representation;
}
private void addLinkToSelf() {
final JsonRepresentation link = linkToBuilder.with(objectAdapter).builder(Rel.SELF).build();
final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links");
if (linkFollower.matches(link)) {
final DomainObjectReprRenderer renderer =
new DomainObjectReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap());
renderer.with(objectAdapter);
link.mapPut("value", renderer.render());
}
getLinks().arrayAdd(link);
}
private void addLinkToDescribedBy() {
final JsonRepresentation link = DomainTypeReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DESCRIBEDBY, objectAdapter.getSpecification()).build();
final LinkFollowSpecs linkFollower = getLinkFollowSpecs().follow("links");
if (linkFollower.matches(link)) {
final DomainTypeReprRenderer renderer = new DomainTypeReprRenderer(getRendererContext(), linkFollower, JsonRepresentation.newMap());
renderer.with(objectAdapter.getSpecification());
link.mapPut("value", renderer.render());
}
getLinks().arrayAdd(link);
}
private void addLinkToUp() {
final JsonRepresentation link = LinkBuilder.newBuilder(rendererContext, Rel.UP.getName(), RepresentationType.LIST, "services").build();
getLinks().arrayAdd(link);
}
private String getDomainType() {
return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getDomainType(objectAdapter);
}
private String getInstanceId() {
return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getInstanceId(objectAdapter);
}
private String getOidStr() {
return org.apache.isis.viewer.restfulobjects.rendering.util.OidUtils.getOidStr(objectAdapter);
}
private DomainObjectReprRenderer withMembers(final ObjectAdapter objectAdapter) {
final JsonRepresentation appendTo =
mode.isUpdatePropertiesLinkArgs() ? representation : JsonRepresentation.newMap();
final List<ObjectAssociation> associations = objectAdapter.getSpecification().getAssociations(Contributed.INCLUDED);
addProperties(objectAdapter, appendTo, associations);
if(!rendererContext.objectPropertyValuesOnly()) {
if (!mode.isArgs() ) {
addCollections(objectAdapter, appendTo, associations);
}
if (mode.isRegular()) {
final List<ObjectAction> actions = objectAdapter.getSpecification().getObjectActions(Contributed.INCLUDED);
addActions(objectAdapter, actions, appendTo);
}
}
if(!mode.isUpdatePropertiesLinkArgs()) {
representation.mapPut("members", appendTo);
}
return this;
}
private void addProperties(final ObjectAdapter objectAdapter, final JsonRepresentation members, final List<ObjectAssociation> associations) {
for (final ObjectAssociation assoc : associations) {
if (mode.checkVisibility()) {
final Consent visibility = assoc.isVisible(objectAdapter, getInteractionInitiatedBy(), rendererContext.getWhere());
if (!visibility.isAllowed()) {
continue;
}
}
if (!(assoc instanceof OneToOneAssociation)) {
continue;
}
final OneToOneAssociation property = (OneToOneAssociation) assoc;
final LinkFollowSpecs linkFollowerForProp = getLinkFollowSpecs().follow("members[" + property.getId() + "]");
final JsonRepresentation propertyRepresentation = JsonRepresentation.newMap();
final ObjectPropertyReprRenderer renderer =
new ObjectPropertyReprRenderer(getRendererContext(), linkFollowerForProp, property.getId(), propertyRepresentation);
renderer.with(new ObjectAndProperty(objectAdapter, property)).usingLinkTo(linkToBuilder);
if (mode.isArgs()) {
renderer.asArguments();
}
if(mode.isEventSerialization()) {
renderer.asEventSerialization();
}
final JsonRepresentation propertyValueRepresentation = renderer.render();
final JsonRepresentation propertyRepr = rendererContext.objectPropertyValuesOnly()
? propertyValueRepresentation.getRepresentation("value")
: propertyValueRepresentation;
members.mapPut(assoc.getId(), propertyRepr);
}
}
private void addCollections(final ObjectAdapter objectAdapter, final JsonRepresentation members, final List<ObjectAssociation> associations) {
for (final ObjectAssociation assoc : associations) {
if (mode.checkVisibility()) {
final Consent visibility = assoc.isVisible(objectAdapter, getInteractionInitiatedBy(), rendererContext.getWhere());
if (!visibility.isAllowed()) {
continue;
}
}
if (!(assoc instanceof OneToManyAssociation)) {
continue;
}
final OneToManyAssociation collection = (OneToManyAssociation) assoc;
final LinkFollowSpecs linkFollowerForColl = getLinkFollowSpecs().follow(
"members[" + collection.getId() + "]");
final JsonRepresentation collectionRepresentation = JsonRepresentation.newMap();
final ObjectCollectionReprRenderer renderer =
new ObjectCollectionReprRenderer(getRendererContext(), linkFollowerForColl, collection.getId(), collectionRepresentation);
renderer.with(new ObjectAndCollection(objectAdapter, collection)).usingLinkTo(linkToBuilder);
if(mode.isEventSerialization()) {
renderer.asEventSerialization();
}
members.mapPut(assoc.getId(), renderer.render());
}
}
private void addActions(final ObjectAdapter objectAdapter, final List<ObjectAction> actions, final JsonRepresentation members) {
for (final ObjectAction action : actions) {
final Consent visibility = action.isVisible(objectAdapter, getInteractionInitiatedBy(), rendererContext.getWhere());
if (!visibility.isAllowed()) {
continue;
}
final LinkFollowSpecs linkFollowSpecs = getLinkFollowSpecs().follow("members["+action.getId()+"]");
final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(getRendererContext(), linkFollowSpecs, action.getId(), JsonRepresentation.newMap());
renderer.with(new ObjectAndAction(objectAdapter, action)).usingLinkTo(linkToBuilder);
members.mapPut(action.getId(), renderer.render());
}
}
private void addPersistLinkIfTransientAndPersistable() {
if (objectAdapter.representsPersistent()) {
return;
}
if(objectAdapter.getSpecification().containsDoOpFacet(NotPersistableFacet.class)) {
return;
}
final DomainObjectReprRenderer renderer =
new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap());
final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asPersistLinkArguments().render();
final String domainType = objectAdapter.getSpecification().getSpecId().asString();
final LinkBuilder persistLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.PERSIST.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s", domainType).withHttpMethod(RestfulHttpMethod.POST).withArguments(domainObjectRepr);
getLinks().arrayAdd(persistLinkBuilder.build());
}
private DomainObjectReprRenderer asPersistLinkArguments() {
this.mode = Mode.PERSIST_LINK_ARGUMENTS;
return this;
}
private DomainObjectReprRenderer asUpdatePropertiesLinkArguments() {
this.mode = Mode.UPDATE_PROPERTIES_LINK_ARGUMENTS;
return this;
}
// not part of the spec
public DomainObjectReprRenderer asEventSerialization() {
this.mode = Mode.EVENT_SERIALIZATION;
return this;
}
private void addUpdatePropertiesLinkIfRequired() {
if(mode.isEventSerialization()) {
return;
}
if (!objectAdapter.representsPersistent()) {
return;
}
final boolean isService = objectAdapter.getSpecification().isService();
if(isService) {
return;
}
final DomainObjectReprRenderer renderer =
new DomainObjectReprRenderer(getRendererContext(), null, JsonRepresentation.newMap());
final JsonRepresentation domainObjectRepr = renderer.with(objectAdapter).asUpdatePropertiesLinkArguments().render();
if(!getRendererContext().suppressUpdateLink()) {
final LinkBuilder updateLinkBuilder = LinkBuilder.newBuilder(getRendererContext(), Rel.UPDATE.getName(), RepresentationType.DOMAIN_OBJECT, "objects/%s/%s", getDomainType(), getInstanceId()).withHttpMethod(RestfulHttpMethod.PUT).withArguments(domainObjectRepr);
getLinks().arrayAdd(updateLinkBuilder.build());
}
}
// ///////////////////////////////////////////////////////////////////
//
// ///////////////////////////////////////////////////////////////////
public static Object valueOrRef(final RendererContext resourceContext, final ObjectAdapter objectAdapter, final ObjectSpecification objectSpec) {
if(objectAdapter.isValue()) {
String format = null; // TODO
return JsonValueEncoder.asObject(objectAdapter, format);
}
final TitleFacet titleFacet = objectSpec.getFacet(TitleFacet.class);
final String title = titleFacet.title(objectAdapter);
return DomainObjectReprRenderer.newLinkToBuilder(resourceContext, Rel.VALUE, objectAdapter).withTitle(title).build();
}
}