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.ActionSemantics;
023import org.apache.isis.applib.annotation.Where;
024import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
025import org.apache.isis.core.metamodel.spec.ObjectSpecification;
026import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
027import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
028import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
029import org.apache.isis.viewer.restfulobjects.applib.Rel;
030import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
031import org.apache.isis.viewer.restfulobjects.rendering.LinkFollowSpecs;
032import org.apache.isis.viewer.restfulobjects.rendering.RendererContext;
033import org.apache.isis.viewer.restfulobjects.rendering.domaintypes.ActionDescriptionReprRenderer;
034import org.codehaus.jackson.node.NullNode;
035
036import com.google.common.collect.Lists;
037
038public class ObjectActionReprRenderer extends AbstractObjectMemberReprRenderer<ObjectActionReprRenderer, ObjectAction> {
039
040    public ObjectActionReprRenderer(final RendererContext resourceContext, final LinkFollowSpecs linkFollowSpecs, String actionId, final JsonRepresentation representation) {
041        super(resourceContext, linkFollowSpecs, actionId, RepresentationType.OBJECT_ACTION, representation, Where.OBJECT_FORMS);
042    }
043
044    @Override
045    public JsonRepresentation render() {
046        // memberType is rendered eagerly
047
048        renderMemberContent();
049        putDisabledReasonIfDisabled();
050
051        if (mode.isStandalone() || mode.isMutated()) {
052            addParameterDetails();
053        }
054
055        return representation;
056    }
057
058    // ///////////////////////////////////////////////////
059    // details link
060    // ///////////////////////////////////////////////////
061
062    /**
063     * Mandatory hook method to support x-ro-follow-links
064     */
065    @Override
066    protected void followDetailsLink(final JsonRepresentation detailsLink) {
067        final ObjectActionReprRenderer renderer = new ObjectActionReprRenderer(getRendererContext(), getLinkFollowSpecs(), null, JsonRepresentation.newMap());
068        renderer.with(new ObjectAndAction(objectAdapter, objectMember)).usingLinkTo(linkTo).asFollowed();
069        detailsLink.mapPut("value", renderer.render());
070    }
071
072    // ///////////////////////////////////////////////////
073    // mutators
074    // ///////////////////////////////////////////////////
075
076    @Override
077    protected void addMutatorsIfEnabled() {
078        if (usability().isVetoed()) {
079            return;
080        }
081        final Map<String, MutatorSpec> mutators = memberType.getMutators();
082        
083        final ActionSemantics.Of actionSemantics = objectMember.getSemantics();
084        final String mutator = InvokeKeys.getKeyFor(actionSemantics);
085        final MutatorSpec mutatorSpec = mutators.get(mutator);
086
087        addLinkFor(mutatorSpec);
088    }
089
090    @Override
091    protected ObjectAdapterLinkTo linkToForMutatorInvoke() {
092        if (!objectMember.isContributed()) {
093            return super.linkToForMutatorInvoke();
094        }
095        final DomainServiceLinkTo linkTo = new DomainServiceLinkTo();
096        return linkTo.usingUrlBase(getRendererContext()).with(contributingServiceAdapter());
097    }
098
099    private ObjectAdapter contributingServiceAdapter() {
100        final ObjectSpecification serviceType = objectMember.getOnType();
101        final List<ObjectAdapter> serviceAdapters = getServiceAdapters();
102        for (final ObjectAdapter serviceAdapter : serviceAdapters) {
103            if (serviceAdapter.getSpecification() == serviceType) {
104                return serviceAdapter;
105            }
106        }
107        // fail fast
108        throw new IllegalStateException("Unable to locate contributing service");
109    }
110
111    @Override
112    protected JsonRepresentation mutatorArgs(final MutatorSpec mutatorSpec) {
113        final JsonRepresentation argMap = JsonRepresentation.newMap();
114        final List<ObjectActionParameter> parameters = objectMember.getParameters();
115        for (int i = 0; i < objectMember.getParameterCount(); i++) {
116            argMap.mapPut(parameters.get(i).getId() + ".value", argValueFor(i));
117        }
118        return argMap;
119    }
120
121    private Object argValueFor(final int i) {
122        if (objectMember.isContributed()) {
123            final ObjectActionParameter actionParameter = objectMember.getParameters().get(i);
124            if (actionParameter.getSpecification().isOfType(objectAdapter.getSpecification())) {
125                return DomainObjectReprRenderer.newLinkToBuilder(rendererContext, Rel.VALUE, objectAdapter).build();
126            }
127        }
128        // force a null into the map
129        return NullNode.getInstance();
130    }
131
132    // ///////////////////////////////////////////////////
133    // parameter details
134    // ///////////////////////////////////////////////////
135
136    private ObjectActionReprRenderer addParameterDetails() {
137        final List<Object> parameters = Lists.newArrayList();
138        for (int i = 0; i < objectMember.getParameterCount(); i++) {
139            final ObjectActionParameter param = objectMember.getParameters().get(i);
140            parameters.add(paramDetails(param));
141        }
142        representation.mapPut("parameters", parameters);
143        return this;
144    }
145
146    private Object paramDetails(final ObjectActionParameter param) {
147        final JsonRepresentation paramRep = JsonRepresentation.newMap();
148        paramRep.mapPut("num", param.getNumber());
149        paramRep.mapPut("id", param.getId());
150        paramRep.mapPut("name", param.getName());
151        paramRep.mapPut("description", param.getDescription());
152        final Object paramChoices = choicesFor(param);
153        if (paramChoices != null) {
154            paramRep.mapPut("choices", paramChoices);
155        }
156        final Object paramDefault = defaultFor(param);
157        if (paramDefault != null) {
158            paramRep.mapPut("default", paramDefault);
159        }
160        return paramRep;
161    }
162
163    private Object choicesFor(final ObjectActionParameter param) {
164        final ObjectAdapter[] choiceAdapters = param.getChoices(objectAdapter);
165        if (choiceAdapters == null || choiceAdapters.length == 0) {
166            return null;
167        }
168        final List<Object> list = Lists.newArrayList();
169        for (final ObjectAdapter choiceAdapter : choiceAdapters) {
170            // REVIEW: previously was using the spec of the parameter, but think instead it should be the spec of the adapter itself
171            // final ObjectSpecification choiceSpec = param.getSpecification();
172            final ObjectSpecification choiceSpec = choiceAdapter.getSpecification();
173            list.add(DomainObjectReprRenderer.valueOrRef(rendererContext, choiceAdapter, choiceSpec));
174        }
175        return list;
176    }
177
178    private Object defaultFor(final ObjectActionParameter param) {
179        final ObjectAdapter defaultAdapter = param.getDefault(objectAdapter);
180        if (defaultAdapter == null) {
181            return null;
182        }
183        // REVIEW: previously was using the spec of the parameter, but think instead it should be the spec of the adapter itself
184        // final ObjectSpecification defaultSpec = param.getSpecification();
185        final ObjectSpecification defaultSpec = defaultAdapter.getSpecification();
186        return DomainObjectReprRenderer.valueOrRef(rendererContext, defaultAdapter, defaultSpec);
187    }
188
189    // ///////////////////////////////////////////////////
190    // extensions and links
191    // ///////////////////////////////////////////////////
192
193    @Override
194    protected void addLinksToFormalDomainModel() {
195        getLinks().arrayAdd(ActionDescriptionReprRenderer.newLinkToBuilder(rendererContext, Rel.DESCRIBEDBY, objectAdapter.getSpecification(), objectMember).build());
196    }
197
198    @Override
199    protected void addLinksIsisProprietary() {
200        if (objectMember.isContributed()) {
201            final ObjectAdapter serviceAdapter = contributingServiceAdapter();
202            final JsonRepresentation contributedByLink = DomainObjectReprRenderer.newLinkToBuilder(rendererContext, Rel.CONTRIBUTED_BY, serviceAdapter).build();
203            getLinks().arrayAdd(contributedByLink);
204        }
205    }
206
207    @Override
208    protected void putExtensionsIsisProprietary() {
209        getExtensions().mapPut("actionType", objectMember.getType().name().toLowerCase());
210
211        final ActionSemantics.Of semantics = objectMember.getSemantics();
212        getExtensions().mapPut("actionSemantics", semantics.getCamelCaseName());
213    }
214
215}