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