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 java.util.List; 020import java.util.Map; 021import com.google.common.collect.Lists; 022import org.codehaus.jackson.node.NullNode; 023import org.apache.isis.applib.annotation.Render.Type; 024import org.apache.isis.applib.annotation.Where; 025import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 026import org.apache.isis.core.metamodel.facetapi.Facet; 027import org.apache.isis.core.metamodel.facetapi.FacetHolder; 028import org.apache.isis.core.metamodel.facets.maxlen.MaxLengthFacet; 029import org.apache.isis.core.metamodel.facets.members.resolve.RenderFacet; 030import org.apache.isis.core.metamodel.facets.object.title.TitleFacet; 031import org.apache.isis.core.metamodel.facets.object.value.ValueFacet; 032import org.apache.isis.core.metamodel.spec.ObjectSpecification; 033import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation; 034import org.apache.isis.core.progmodel.facets.value.bigdecimal.BigDecimalValueFacet; 035import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation; 036import org.apache.isis.viewer.restfulobjects.applib.Rel; 037import org.apache.isis.viewer.restfulobjects.applib.RepresentationType; 038import org.apache.isis.viewer.restfulobjects.rendering.LinkBuilder; 039import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs; 040import org.apache.isis.viewer.restfulobjects.rendering.RendererContext; 041import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.PropertyDescriptionReprRenderer; 042 043public class ObjectPropertyReprRenderer extends AbstractObjectMemberReprRenderer<ObjectPropertyReprRenderer, OneToOneAssociation> { 044 045 public ObjectPropertyReprRenderer(final RendererContext resourceContext, final LinkFollowSpecs linkFollower, final String propertyId, final JsonRepresentation representation) { 046 super(resourceContext, linkFollower, propertyId, RepresentationType.OBJECT_PROPERTY, representation, Where.OBJECT_FORMS); 047 } 048 049 @Override 050 public JsonRepresentation render() { 051 052 renderMemberContent(); 053 addValue(); 054 055 putDisabledReasonIfDisabled(); 056 057 if (mode.isStandalone() || mode.isMutated()) { 058 addChoices(); 059 addExtensionsIsisProprietaryChangedObjects(); 060 } 061 062 return representation; 063 } 064 065 // /////////////////////////////////////////////////// 066 // value 067 // /////////////////////////////////////////////////// 068 069 private void addValue() { 070 final ObjectAdapter valueAdapter = objectMember.get(objectAdapter); 071 072 // use the runtime type if we have a value, else the compile time type of the member otherwise 073 final ObjectSpecification spec = valueAdapter != null? valueAdapter.getSpecification(): objectMember.getSpecification(); 074 075 final ValueFacet valueFacet = spec.getFacet(ValueFacet.class); 076 if (valueFacet != null) { 077 String format = null; 078 final Class<?> specClass = spec.getCorrespondingClass(); 079 if(specClass == java.math.BigDecimal.class) { 080 // look for facet on member, else on the value's spec 081 final BigDecimalValueFacet bigDecimalValueFacet = 082 getFacet(BigDecimalValueFacet.class, 083 objectMember, 084 valueAdapter != null? valueAdapter.getSpecification(): null); 085 if(bigDecimalValueFacet != null) { 086 final Integer precision = bigDecimalValueFacet.getPrecision(); 087 final Integer scale = bigDecimalValueFacet.getScale(); 088 format = String.format("big-decimal(%d,%d)", precision, scale); 089 } 090 } else if(specClass == java.math.BigInteger.class) { 091 // TODO: need to extend BigIntegerValueFacet similar to BigDecimalValueFacet 092 } 093 JsonValueEncoder.appendValueAndFormat(spec, valueAdapter, representation, format); 094 return; 095 } 096 097 final RenderFacet renderFacet = objectMember.getFacet(RenderFacet.class); 098 boolean eagerlyRender = renderFacet != null && renderFacet.value() == Type.EAGERLY && rendererContext.canEagerlyRender(valueAdapter); 099 100 if(valueAdapter == null) { 101 representation.mapPut("value", NullNode.getInstance()); 102 } else { 103 final TitleFacet titleFacet = spec.getFacet(TitleFacet.class); 104 final String title = titleFacet.title(valueAdapter, rendererContext.getLocalization()); 105 106 final LinkBuilder valueLinkBuilder = DomainObjectReprRenderer.newLinkToBuilder(rendererContext, Rel.VALUE, valueAdapter).withTitle(title); 107 if(eagerlyRender) { 108 final DomainObjectReprRenderer renderer = new DomainObjectReprRenderer(rendererContext, getLinkFollowSpecs(), JsonRepresentation.newMap()); 109 renderer.with(valueAdapter); 110 if(mode.isEventSerialization()) { 111 renderer.asEventSerialization(); 112 } 113 114 valueLinkBuilder.withValue(renderer.render()); 115 } 116 117 representation.mapPut("value", valueLinkBuilder.build()); 118 } 119 } 120 121 private static <T extends Facet> T getFacet(Class<T> facetType, FacetHolder... holders) { 122 for (FacetHolder holder : holders) { 123 if(holder == null) { 124 continue; 125 } 126 final T facet = holder.getFacet(facetType); 127 if(facet != null) { 128 return facet; 129 } 130 } 131 return null; 132 } 133 134 135 136 // /////////////////////////////////////////////////// 137 // details link 138 // /////////////////////////////////////////////////// 139 140 /** 141 * Mandatory hook method to support x-ro-follow-links 142 */ 143 @Override 144 protected void followDetailsLink(final JsonRepresentation detailsLink) { 145 final ObjectPropertyReprRenderer renderer = new ObjectPropertyReprRenderer(getRendererContext(), getLinkFollowSpecs(), null, JsonRepresentation.newMap()); 146 renderer.with(new ObjectAndProperty(objectAdapter, objectMember)).asFollowed(); 147 detailsLink.mapPut("value", renderer.render()); 148 } 149 150 // /////////////////////////////////////////////////// 151 // mutators 152 // /////////////////////////////////////////////////// 153 154 @Override 155 protected void addMutatorsIfEnabled() { 156 if (usability().isVetoed()) { 157 return; 158 } 159 final Map<String, MutatorSpec> mutators = objectMemberType.getMutators(); 160 for (final String mutator : mutators.keySet()) { 161 final MutatorSpec mutatorSpec = mutators.get(mutator); 162 addLinkFor(mutatorSpec); 163 } 164 return; 165 } 166 167 // /////////////////////////////////////////////////// 168 // choices 169 // /////////////////////////////////////////////////// 170 171 private ObjectPropertyReprRenderer addChoices() { 172 final Object propertyChoices = propertyChoices(); 173 if (propertyChoices != null) { 174 representation.mapPut("choices", propertyChoices); 175 } 176 return this; 177 } 178 179 private Object propertyChoices() { 180 final ObjectAdapter[] choiceAdapters = objectMember.getChoices(objectAdapter); 181 if (choiceAdapters == null || choiceAdapters.length == 0) { 182 return null; 183 } 184 final List<Object> list = Lists.newArrayList(); 185 for (final ObjectAdapter choiceAdapter : choiceAdapters) { 186 // REVIEW: previously was using the spec of the member, but think instead it should be the spec of the adapter itself 187 // final ObjectSpecification choiceSpec = objectMember.getSpecification(); 188 189 // REVIEW: check that it works for ToDoItem$Category, though... 190 final ObjectSpecification choiceSpec = objectAdapter.getSpecification(); 191 list.add(DomainObjectReprRenderer.valueOrRef(rendererContext, choiceAdapter, choiceSpec)); 192 } 193 return list; 194 } 195 196 // /////////////////////////////////////////////////// 197 // extensions and links 198 // /////////////////////////////////////////////////// 199 200 @Override 201 protected void addLinksToFormalDomainModel() { 202 getLinks().arrayAdd(PropertyDescriptionReprRenderer.newLinkToBuilder(getRendererContext(), Rel.DESCRIBEDBY, objectAdapter.getSpecification(), objectMember).build()); 203 } 204 205 @Override 206 protected void addLinksIsisProprietary() { 207 // none 208 } 209 210 @Override 211 protected void putExtensionsIsisProprietary() { 212 // none 213 } 214 215}