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}