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}