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