001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.metamodel.specloader.specimpl;
021
022import java.util.List;
023
024import com.google.common.base.Objects;
025import com.google.common.collect.Lists;
026
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030import org.apache.isis.applib.annotation.ActionSemantics;
031import org.apache.isis.applib.annotation.Where;
032import org.apache.isis.applib.filter.Filter;
033import org.apache.isis.core.commons.authentication.AuthenticationSession;
034import org.apache.isis.core.commons.debug.DebugString;
035import org.apache.isis.core.commons.exceptions.UnknownTypeException;
036import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
037import org.apache.isis.core.metamodel.consent.Consent;
038import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod;
039import org.apache.isis.core.metamodel.consent.InteractionResultSet;
040import org.apache.isis.core.metamodel.facetapi.Facet;
041import org.apache.isis.core.metamodel.facetapi.FacetHolder;
042import org.apache.isis.core.metamodel.facetapi.FeatureType;
043import org.apache.isis.core.metamodel.facets.FacetedMethod;
044import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
045import org.apache.isis.core.metamodel.facets.TypedHolder;
046import org.apache.isis.core.metamodel.facets.actions.choices.ActionChoicesFacet;
047import org.apache.isis.core.metamodel.facets.actions.debug.DebugFacet;
048import org.apache.isis.core.metamodel.facets.actions.defaults.ActionDefaultsFacet;
049import org.apache.isis.core.metamodel.facets.actions.exploration.ExplorationFacet;
050import org.apache.isis.core.metamodel.facets.actions.invoke.ActionInvocationFacet;
051import org.apache.isis.core.metamodel.facets.actions.prototype.PrototypeFacet;
052import org.apache.isis.core.metamodel.facets.actions.semantics.ActionSemanticsFacet;
053import org.apache.isis.core.metamodel.facets.param.choices.ActionParameterChoicesFacet;
054import org.apache.isis.core.metamodel.facets.param.defaults.ActionParameterDefaultsFacet;
055import org.apache.isis.core.metamodel.interactions.ActionInvocationContext;
056import org.apache.isis.core.metamodel.interactions.ActionUsabilityContext;
057import org.apache.isis.core.metamodel.interactions.ActionVisibilityContext;
058import org.apache.isis.core.metamodel.interactions.InteractionUtils;
059import org.apache.isis.core.metamodel.interactions.UsabilityContext;
060import org.apache.isis.core.metamodel.interactions.ValidityContext;
061import org.apache.isis.core.metamodel.interactions.VisibilityContext;
062import org.apache.isis.core.metamodel.spec.ActionType;
063import org.apache.isis.core.metamodel.spec.DomainModelException;
064import org.apache.isis.core.metamodel.spec.Instance;
065import org.apache.isis.core.metamodel.spec.ObjectSpecification;
066import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
067import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter;
068import org.apache.isis.core.metamodel.spec.feature.ObjectMemberContext;
069
070public class ObjectActionImpl extends ObjectMemberAbstract implements ObjectAction {
071    private final static Logger LOG = LoggerFactory.getLogger(ObjectActionImpl.class);
072
073    public static ActionType getType(final String typeStr) {
074        final ActionType type = ActionType.valueOf(typeStr);
075        if (type == null) {
076            throw new IllegalArgumentException();
077        }
078        return type;
079    }
080
081
082    /**
083     * Lazily initialized by {@link #getParameters()} (so don't use directly!)
084     */
085    private List<ObjectActionParameter> parameters;
086
087    
088    // //////////////////////////////////////////////////////////////////
089    // Constructors
090    // //////////////////////////////////////////////////////////////////
091
092    public ObjectActionImpl(final FacetedMethod facetedMethod, final ObjectMemberContext objectMemberContext) {
093        super(facetedMethod, FeatureType.ACTION, objectMemberContext);
094    }
095
096    // //////////////////////////////////////////////////////////////////
097    // ReturnType, OnType, Actions (set)
098    // //////////////////////////////////////////////////////////////////
099
100    /**
101     * Always returns <tt>null</tt>.
102     */
103    @Override
104    public ObjectSpecification getSpecification() {
105        return null;
106    }
107
108    @Override
109    public ObjectSpecification getReturnType() {
110        final ActionInvocationFacet facet = getActionInvocationFacet();
111        return facet.getReturnType();
112    }
113
114    /**
115     * Returns true if the represented action returns something, else returns
116     * false.
117     */
118    @Override
119    public boolean hasReturn() {
120        if(getReturnType() == null) {
121            // this shouldn't happen; return Type always defined, even if represents void.class
122            return false;
123        }
124        return getReturnType() != getSpecificationLookup().loadSpecification(void.class);
125    }
126
127
128    @Override
129    public ObjectSpecification getOnType() {
130        final ActionInvocationFacet facet = getActionInvocationFacet();
131        return facet.getOnType();
132    }
133
134    @Override
135    public ActionSemantics.Of getSemantics() {
136        final ActionSemanticsFacet facet = getFacet(ActionSemanticsFacet.class);
137        return facet != null? facet.value(): ActionSemantics.Of.NON_IDEMPOTENT;
138    }
139
140    // /////////////////////////////////////////////////////////////
141    // getInstance
142    // /////////////////////////////////////////////////////////////
143
144    @Override
145    public Instance getInstance(final ObjectAdapter adapter) {
146        final ObjectAction specification = this;
147        return adapter.getInstance(specification);
148    }
149
150    // /////////////////////////////////////////////////////////////
151    // Type, IsContributed
152    // /////////////////////////////////////////////////////////////
153
154    @Override
155    public ActionType getType() {
156        return getType(this);
157    }
158
159    private static ActionType getType(final FacetHolder facetHolder) {
160        Facet facet = facetHolder.getFacet(DebugFacet.class);
161        if (facet != null) {
162            return ActionType.DEBUG;
163        }
164        facet = facetHolder.getFacet(ExplorationFacet.class);
165        if (facet != null) {
166            return ActionType.EXPLORATION;
167        }
168        facet = facetHolder.getFacet(PrototypeFacet.class);
169        if (facet != null) {
170            return ActionType.PROTOTYPE;
171        }
172        return ActionType.USER;
173    }
174
175    // /////////////////////////////////////////////////////////////
176    // Parameters
177    // /////////////////////////////////////////////////////////////
178
179    @Override
180    public int getParameterCount() {
181        return getFacetedMethod().getParameters().size();
182    }
183
184    @Override
185    public boolean promptForParameters(final ObjectAdapter target) {
186        return getParameterCount() != 0;
187    }
188
189    /**
190     * Build lazily by {@link #getParameters()}.
191     * 
192     * <p>
193     * Although this is lazily loaded, the method is also <tt>synchronized</tt>
194     * so there shouldn't be any thread race conditions.
195     */
196    @Override
197    public synchronized List<ObjectActionParameter> getParameters() {
198        if (this.parameters == null) {
199            final int parameterCount = getParameterCount();
200            final List<ObjectActionParameter> parameters = Lists.newArrayList();
201            final List<FacetedMethodParameter> paramPeers = getFacetedMethod().getParameters();
202            for (int i = 0; i < parameterCount; i++) {
203                final TypedHolder paramPeer = paramPeers.get(i);
204                final ObjectSpecification specification = ObjectMemberAbstract.getSpecification(getSpecificationLookup(), paramPeer.getType());
205                
206                final ObjectActionParameter parameter;
207                if (specification.isParseable()) {
208                    parameter = new ObjectActionParameterParseable(i, this, paramPeer);
209                } else if (specification.isNotCollection()) {
210                    parameter = new OneToOneActionParameterImpl(i, this, paramPeer);
211                } else if (specification.isParentedOrFreeCollection()) {
212                    throw new UnknownTypeException("collections not supported as parameters: " + getIdentifier());
213                } else {
214                    throw new UnknownTypeException(specification);
215                }
216                parameters.add(parameter);
217            }
218            this.parameters = parameters;
219        }
220        return parameters;
221    }
222
223    @Override
224    public synchronized List<ObjectSpecification> getParameterTypes() {
225        final List<ObjectSpecification> parameterTypes = Lists.newArrayList();
226        final List<ObjectActionParameter> parameters = getParameters();
227        for (final ObjectActionParameter parameter : parameters) {
228            parameterTypes.add(parameter.getSpecification());
229        }
230        return parameterTypes;
231    }
232
233    @Override
234    public ObjectActionParameter getParameterById(final String paramId) {
235        final List<ObjectActionParameter> allParameters = getParameters();
236        for (int i = 0; i < allParameters.size(); i++) {
237            final ObjectActionParameter param = allParameters.get(i);
238            if (Objects.equal(paramId, param.getId())) {
239                return param;
240            }
241        }
242        return null;
243    }
244
245    @Override
246    public ObjectActionParameter getParameterByName(final String paramName) {
247        final List<ObjectActionParameter> allParameters = getParameters();
248        for (int i = 0; i < allParameters.size(); i++) {
249            final ObjectActionParameter param = allParameters.get(i);
250            if (Objects.equal(paramName, param.getName())) {
251                return param;
252            }
253        }
254        return null;
255    }
256
257    @Override
258    public List<ObjectActionParameter> getParameters(final Filter<ObjectActionParameter> filter) {
259        final List<ObjectActionParameter> allParameters = getParameters();
260        final List<ObjectActionParameter> selectedParameters = Lists.newArrayList();
261        for (int i = 0; i < allParameters.size(); i++) {
262            if (filter.accept(allParameters.get(i))) {
263                selectedParameters.add(allParameters.get(i));
264            }
265        }
266        return selectedParameters;
267    }
268
269    private ObjectActionParameter getParameter(final int position) {
270        final List<ObjectActionParameter> parameters = getParameters();
271        if (position >= parameters.size()) {
272            throw new IllegalArgumentException("getParameter(int): only " + parameters.size() + " parameters, position=" + position);
273        }
274        return parameters.get(position);
275    }
276
277    // /////////////////////////////////////////////////////////////
278    // Visible (or hidden)
279    // /////////////////////////////////////////////////////////////
280
281    @Override
282    public VisibilityContext<?> createVisibleInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter targetObjectAdapter, Where where) {
283        return new ActionVisibilityContext(getDeploymentCategory(), session, invocationMethod, targetObjectAdapter, getIdentifier(), where);
284    }
285
286    // /////////////////////////////////////////////////////////////
287    // Usable (or disabled)
288    // /////////////////////////////////////////////////////////////
289
290    @Override
291    public UsabilityContext<?> createUsableInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter targetObjectAdapter, Where where) {
292        return new ActionUsabilityContext(getDeploymentCategory(), session, invocationMethod, targetObjectAdapter, getIdentifier(), where);
293    }
294
295    // //////////////////////////////////////////////////////////////////
296    // validate
297    // //////////////////////////////////////////////////////////////////
298
299    /**
300     * TODO: currently this method is hard-coded to assume all interactions are
301     * initiated {@link InteractionInvocationMethod#BY_USER by user}.
302     */
303    @Override
304    public Consent isProposedArgumentSetValid(final ObjectAdapter target, final ObjectAdapter[] proposedArguments) {
305        return isProposedArgumentSetValidResultSet(target, proposedArguments).createConsent();
306    }
307
308    private InteractionResultSet isProposedArgumentSetValidResultSet(final ObjectAdapter object, final ObjectAdapter[] proposedArguments) {
309        final InteractionInvocationMethod invocationMethod = InteractionInvocationMethod.BY_USER;
310
311        final InteractionResultSet resultSet = new InteractionResultSet();
312        final List<ObjectActionParameter> actionParameters = getParameters();
313        if (proposedArguments != null) {
314            for (int i = 0; i < proposedArguments.length; i++) {
315                final ValidityContext<?> ic = actionParameters.get(i).createProposedArgumentInteractionContext(getAuthenticationSession(), invocationMethod, object, proposedArguments, i);
316                InteractionUtils.isValidResultSet(getParameter(i), ic, resultSet);
317            }
318        }
319        // only check the action's own validity if all the arguments are OK.
320        if (resultSet.isAllowed()) {
321            final ValidityContext<?> ic = createActionInvocationInteractionContext(getAuthenticationSession(), invocationMethod, object, proposedArguments);
322            InteractionUtils.isValidResultSet(this, ic, resultSet);
323        }
324        return resultSet;
325    }
326
327    @Override
328    public ActionInvocationContext createActionInvocationInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter targetObject, final ObjectAdapter[] proposedArguments) {
329        return new ActionInvocationContext(getDeploymentCategory(), getAuthenticationSession(), invocationMethod, targetObject, getIdentifier(), proposedArguments);
330    }
331
332    // //////////////////////////////////////////////////////////////////
333    // execute
334    // //////////////////////////////////////////////////////////////////
335
336    @Override
337    public ObjectAdapter execute(final ObjectAdapter target, final ObjectAdapter[] arguments) {
338        if(LOG.isDebugEnabled()) {
339            LOG.debug("execute action " + target + "." + getId());
340        }
341        final ActionInvocationFacet facet = getFacet(ActionInvocationFacet.class);
342        return facet.invoke(this, target, arguments);
343    }
344
345    protected ActionInvocationFacet getActionInvocationFacet() {
346        return getFacetedMethod().getFacet(ActionInvocationFacet.class);
347    }
348
349
350    // //////////////////////////////////////////////////////////////////
351    // defaults
352    // //////////////////////////////////////////////////////////////////
353
354    @Override
355    public ObjectAdapter[] getDefaults(final ObjectAdapter target) {
356
357        final int parameterCount = getParameterCount();
358        final List<ObjectActionParameter> parameters = getParameters();
359
360        final Object[] parameterDefaultPojos;
361
362        final ActionDefaultsFacet facet = getFacet(ActionDefaultsFacet.class);
363        if (!facet.isNoop()) {
364            // use the old defaultXxx approach
365            parameterDefaultPojos = facet.getDefaults(target);
366            if (parameterDefaultPojos.length != parameterCount) {
367                throw new DomainModelException("Defaults array of incompatible size; expected " + parameterCount + " elements, but was " + parameterDefaultPojos.length + " for " + facet);
368            }
369            for (int i = 0; i < parameterCount; i++) {
370                if (parameterDefaultPojos[i] != null) {
371                    final ObjectSpecification componentSpec = getSpecificationLookup().loadSpecification(parameterDefaultPojos[i].getClass());
372                    final ObjectSpecification parameterSpec = parameters.get(i).getSpecification();
373                    if (!componentSpec.isOfType(parameterSpec)) {
374                        throw new DomainModelException("Defaults type incompatible with parameter " + (i + 1) + " type; expected " + parameterSpec.getFullIdentifier() + ", but was " + componentSpec.getFullIdentifier());
375                    }
376                }
377            }
378        } else {
379            // use the new defaultNXxx approach for each param in turn
380            // (the reflector will have made sure both aren't installed).
381            parameterDefaultPojos = new Object[parameterCount];
382            for (int i = 0; i < parameterCount; i++) {
383                final ActionParameterDefaultsFacet paramFacet = parameters.get(i).getFacet(ActionParameterDefaultsFacet.class);
384                if (paramFacet != null && !paramFacet.isNoop()) {
385                    parameterDefaultPojos[i] = paramFacet.getDefault(target, null);
386                } else {
387                    parameterDefaultPojos[i] = null;
388                }
389            }
390        }
391
392        final ObjectAdapter[] parameterDefaultAdapters = new ObjectAdapter[parameterCount];
393        if (parameterDefaultPojos != null) {
394            for (int i = 0; i < parameterCount; i++) {
395                parameterDefaultAdapters[i] = adapterFor(parameterDefaultPojos[i]);
396            }
397        }
398
399        return parameterDefaultAdapters;
400    }
401
402    private ObjectAdapter adapterFor(final Object pojo) {
403        return pojo == null ? null : getAdapterManager().adapterFor(pojo);
404    }
405
406    // /////////////////////////////////////////////////////////////
407    // options (choices)
408    // /////////////////////////////////////////////////////////////
409
410    @Override
411    public ObjectAdapter[][] getChoices(final ObjectAdapter target) {
412
413        final int parameterCount = getParameterCount();
414        Object[][] parameterChoicesPojos;
415
416        final ActionChoicesFacet facet = getFacet(ActionChoicesFacet.class);
417        final List<ObjectActionParameter> parameters = getParameters();
418
419        if (!facet.isNoop()) {
420            // using the old choicesXxx() approach
421            parameterChoicesPojos = facet.getChoices(target);
422
423            // if no options, or not the right number of pojos, then default
424            if (parameterChoicesPojos == null) {
425                parameterChoicesPojos = new Object[parameterCount][];
426            } else if (parameterChoicesPojos.length != parameterCount) {
427                throw new DomainModelException("Choices array of incompatible size; expected " + parameterCount + " elements, but was " + parameterChoicesPojos.length + " for " + facet);
428            }
429        } else {
430            // use the new choicesNXxx approach for each param in turn
431            // (the reflector will have made sure both aren't installed).
432
433            parameterChoicesPojos = new Object[parameterCount][];
434            for (int i = 0; i < parameterCount; i++) {
435                final ActionParameterChoicesFacet paramFacet = parameters.get(i).getFacet(ActionParameterChoicesFacet.class);
436                if (paramFacet != null && !paramFacet.isNoop()) {
437                    parameterChoicesPojos[i] = paramFacet.getChoices(target, null);
438                } else {
439                    parameterChoicesPojos[i] = new Object[0];
440                }
441            }
442        }
443
444        final ObjectAdapter[][] parameterChoicesAdapters = new ObjectAdapter[parameterCount][];
445        for (int i = 0; i < parameterCount; i++) {
446            final ObjectSpecification paramSpec = parameters.get(i).getSpecification();
447
448            if (parameterChoicesPojos[i] != null && parameterChoicesPojos[i].length > 0) {
449                ObjectActionParameterAbstract.checkChoicesOrAutoCompleteType(getSpecificationLookup(), parameterChoicesPojos[i], paramSpec);
450                parameterChoicesAdapters[i] = new ObjectAdapter[parameterChoicesPojos[i].length];
451                for (int j = 0; j < parameterChoicesPojos[i].length; j++) {
452                    parameterChoicesAdapters[i][j] = adapterFor(parameterChoicesPojos[i][j]);
453                }
454            } else 
455                // now incorporated into above choices processing (BoundedFacet is no more) 
456                /*if (BoundedFacetUtils.isBoundedSet(paramSpec)) {
457                final QueryFindAllInstances<ObjectAdapter> query = new QueryFindAllInstances<ObjectAdapter>(paramSpec.getFullIdentifier());
458                final List<ObjectAdapter> allInstancesAdapter = getQuerySubmitter().allMatchingQuery(query);
459                parameterChoicesAdapters[i] = new ObjectAdapter[allInstancesAdapter.size()];
460                int j = 0;
461                for (final ObjectAdapter adapter : allInstancesAdapter) {
462                    parameterChoicesAdapters[i][j++] = adapter;
463                }
464            } else */ if (paramSpec.isNotCollection()) {
465                parameterChoicesAdapters[i] = new ObjectAdapter[0];
466            } else {
467                throw new UnknownTypeException(paramSpec);
468            }
469
470            if (parameterChoicesAdapters[i].length == 0) {
471                parameterChoicesAdapters[i] = null;
472            }
473        }
474
475        return parameterChoicesAdapters;
476    }
477
478    // //////////////////////////////////////////////////////////////////
479    // debug, toString
480    // //////////////////////////////////////////////////////////////////
481
482    @Override
483    public String debugData() {
484        final DebugString debugString = new DebugString();
485        getFacetedMethod().debugData(debugString);
486        return debugString.toString();
487    }
488
489    @Override
490    public String toString() {
491        final StringBuffer sb = new StringBuffer();
492        sb.append("Action [");
493        sb.append(super.toString());
494        sb.append(",type=");
495        sb.append(getType());
496        sb.append(",returns=");
497        sb.append(getReturnType());
498        sb.append(",parameters={");
499        for (int i = 0; i < getParameterCount(); i++) {
500            if (i > 0) {
501                sb.append(",");
502            }
503            sb.append(getParameters().get(i).getSpecification().getShortIdentifier());
504        }
505        sb.append("}]");
506        return sb.toString();
507    }
508
509
510}