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.collect.Lists; 025 026import org.apache.isis.applib.Identifier; 027import org.apache.isis.applib.filter.Filter; 028import org.apache.isis.applib.profiles.Localization; 029import org.apache.isis.applib.query.Query; 030import org.apache.isis.applib.query.QueryFindAllInstances; 031import org.apache.isis.core.commons.authentication.AuthenticationSession; 032import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider; 033import org.apache.isis.core.commons.lang.ClassExtensions; 034import org.apache.isis.core.commons.lang.ListExtensions; 035import org.apache.isis.core.commons.lang.StringExtensions; 036import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 037import org.apache.isis.core.metamodel.adapter.QuerySubmitter; 038import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager; 039import org.apache.isis.core.metamodel.consent.Allow; 040import org.apache.isis.core.metamodel.consent.Consent; 041import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod; 042import org.apache.isis.core.metamodel.consent.InteractionResultSet; 043import org.apache.isis.core.metamodel.deployment.DeploymentCategory; 044import org.apache.isis.core.metamodel.facetapi.Facet; 045import org.apache.isis.core.metamodel.facetapi.MultiTypedFacet; 046import org.apache.isis.core.metamodel.facets.TypedHolder; 047import org.apache.isis.core.metamodel.facets.describedas.DescribedAsFacet; 048import org.apache.isis.core.metamodel.facets.mandatory.MandatoryFacet; 049import org.apache.isis.core.metamodel.facets.named.NamedFacet; 050import org.apache.isis.core.metamodel.facets.param.autocomplete.ActionParameterAutoCompleteFacet; 051import org.apache.isis.core.metamodel.facets.param.choices.ActionParameterChoicesFacet; 052import org.apache.isis.core.metamodel.facets.param.defaults.ActionParameterDefaultsFacet; 053import org.apache.isis.core.metamodel.interactions.ActionArgumentContext; 054import org.apache.isis.core.metamodel.interactions.InteractionUtils; 055import org.apache.isis.core.metamodel.interactions.ValidityContext; 056import org.apache.isis.core.metamodel.spec.DomainModelException; 057import org.apache.isis.core.metamodel.spec.ObjectSpecification; 058import org.apache.isis.core.metamodel.spec.SpecificationLoader; 059import org.apache.isis.core.metamodel.spec.feature.ObjectAction; 060import org.apache.isis.core.metamodel.spec.feature.ObjectActionParameter; 061import org.apache.isis.core.progmodel.facets.object.bounded.ChoicesFacetFromBoundedAbstract; 062import org.apache.isis.core.progmodel.facets.param.autocomplete.MinLengthUtil; 063 064public abstract class ObjectActionParameterAbstract implements ObjectActionParameter { 065 066 private final int number; 067 private final ObjectActionImpl parentAction; 068 private final TypedHolder peer; 069 070 protected ObjectActionParameterAbstract(final int number, final ObjectActionImpl objectAction, final TypedHolder peer) { 071 this.number = number; 072 this.parentAction = objectAction; 073 this.peer = peer; 074 } 075 076 /** 077 * Subclasses should override either {@link #isObject()} or 078 * {@link #isCollection()}. 079 */ 080 @Override 081 public boolean isObject() { 082 return false; 083 } 084 085 /** 086 * Subclasses should override either {@link #isObject()} or 087 * {@link #isCollection()}. 088 */ 089 @Override 090 public boolean isCollection() { 091 return false; 092 } 093 094 /** 095 * Parameter number, 0-based. 096 */ 097 @Override 098 public int getNumber() { 099 return number; 100 } 101 102 @Override 103 public ObjectAction getAction() { 104 return parentAction; 105 } 106 107 /** 108 * NOT API, but exposed for the benefit of {@link ObjectActionParameterContributee}. 109 */ 110 public TypedHolder getPeer() { 111 return peer; 112 } 113 114 @Override 115 public ObjectSpecification getSpecification() { 116 return ObjectMemberAbstract.getSpecification(getSpecificationLookup(), peer.getType()); 117 } 118 119 @Override 120 public Identifier getIdentifier() { 121 return parentAction.getIdentifier(); 122 } 123 124 @Override 125 public String getId() { 126 final NamedFacet facet = getFacet(NamedFacet.class); 127 if (facet != null && facet.value() != null) { 128 return StringExtensions.asCamelLowerFirst(facet.value()); 129 } 130 final String name = getSpecification().getSingularName(); 131 final List<ObjectActionParameter> parameters = this.getAction().getParameters(new Filter<ObjectActionParameter>() { 132 133 @Override 134 public boolean accept(final ObjectActionParameter t) { 135 return equalsShortIdentifier(t.getSpecification(), getSpecification()); 136 } 137 138 protected boolean equalsShortIdentifier(final ObjectSpecification spec1, final ObjectSpecification spec2) { 139 return spec1.getShortIdentifier().toLowerCase().equals(spec2.getShortIdentifier().toLowerCase()); 140 } 141 }); 142 if (parameters.size() == 1) { 143 return StringExtensions.asCamelLowerFirst(name); 144 } 145 final int indexOf = parameters.indexOf(this); 146 return StringExtensions.asCamelLowerFirst(name + (indexOf + 1)); 147 } 148 149 @Override 150 public String getName() { 151 final NamedFacet facet = getFacet(NamedFacet.class); 152 if (facet != null && facet.value() != null) { 153 return facet.value(); 154 } 155 final String name = getSpecification().getSingularName(); 156 final List<ObjectActionParameter> parameters = getAction().getParameters(new Filter<ObjectActionParameter>() { 157 158 @Override 159 public boolean accept(final ObjectActionParameter t) { 160 return equalsShortIdentifier(t.getSpecification(), getSpecification()); 161 } 162 163 protected boolean equalsShortIdentifier(final ObjectSpecification spec1, final ObjectSpecification spec2) { 164 return spec1.getShortIdentifier().toLowerCase().equals(spec2.getShortIdentifier().toLowerCase()); 165 } 166 }); 167 if (parameters.size() == 1) { 168 return name; 169 } 170 final int indexOf = parameters.indexOf(this); 171 return name + " " + (indexOf + 1); 172 } 173 174 @Override 175 public String getDescription() { 176 final DescribedAsFacet facet = getFacet(DescribedAsFacet.class); 177 final String description = facet.value(); 178 return description == null ? "" : description; 179 } 180 181 @Override 182 public boolean isOptional() { 183 final MandatoryFacet facet = getFacet(MandatoryFacet.class); 184 return facet.isInvertedSemantics(); 185 } 186 187 public Consent isUsable() { 188 return Allow.DEFAULT; 189 } 190 191 // ////////////////////////////////////////////////////////// 192 // FacetHolder 193 // ////////////////////////////////////////////////////////// 194 195 @Override 196 public boolean containsFacet(final Class<? extends Facet> facetType) { 197 return peer != null ? peer.containsFacet(facetType) : false; 198 } 199 200 @Override 201 public boolean containsDoOpFacet(final Class<? extends Facet> facetType) { 202 return peer == null ? false : peer.containsDoOpFacet(facetType); 203 } 204 205 @Override 206 public <T extends Facet> T getFacet(final Class<T> cls) { 207 return peer != null ? peer.getFacet(cls) : null; 208 } 209 210 @SuppressWarnings("unchecked") 211 @Override 212 public Class<? extends Facet>[] getFacetTypes() { 213 return peer != null ? peer.getFacetTypes() : new Class[] {}; 214 } 215 216 @Override 217 public List<Facet> getFacets(final Filter<Facet> filter) { 218 return peer != null ? peer.getFacets(filter) : Lists.<Facet> newArrayList(); 219 } 220 221 @Override 222 public void addFacet(final Facet facet) { 223 if (peer != null) { 224 peer.addFacet(facet); 225 } 226 } 227 228 @Override 229 public void addFacet(final MultiTypedFacet facet) { 230 if (peer != null) { 231 peer.addFacet(facet); 232 } 233 } 234 235 @Override 236 public void removeFacet(final Facet facet) { 237 if (peer != null) { 238 peer.removeFacet(facet); 239 } 240 } 241 242 @Override 243 public void removeFacet(final Class<? extends Facet> facetType) { 244 if (peer != null) { 245 peer.removeFacet(facetType); 246 } 247 } 248 249 250 // ///////////////////////////////////////////////////////////// 251 // AutoComplete 252 // ///////////////////////////////////////////////////////////// 253 254 @Override 255 public boolean hasAutoComplete() { 256 final ActionParameterAutoCompleteFacet facet = getFacet(ActionParameterAutoCompleteFacet.class); 257 return facet != null; 258 } 259 260 @Override 261 public ObjectAdapter[] getAutoComplete(ObjectAdapter adapter, String searchArg) { 262 final List<ObjectAdapter> adapters = Lists.newArrayList(); 263 final ActionParameterAutoCompleteFacet facet = getFacet(ActionParameterAutoCompleteFacet.class); 264 265 if (facet != null) { 266 final Object[] choices = facet.autoComplete(adapter, searchArg); 267 checkChoicesOrAutoCompleteType(getSpecificationLookup(), choices, getSpecification()); 268 for (final Object choice : choices) { 269 adapters.add(getAdapterMap().adapterFor(choice)); 270 } 271 } 272 /* // now incorporated into above choices processing (BoundedFacet is no more) 273 if (adapters.size() == 0 && ChoicesFacetUtils.hasChoices(getSpecification())) { 274 addAllInstancesForType(adapters); 275 } 276 */ 277 return adapters.toArray(new ObjectAdapter[0]); 278 } 279 280 @Override 281 public int getAutoCompleteMinLength() { 282 final ActionParameterAutoCompleteFacet facet = getFacet(ActionParameterAutoCompleteFacet.class); 283 return facet != null? facet.getMinLength(): MinLengthUtil.MIN_LENGTH_DEFAULT; 284 } 285 286 287 // ///////////////////////////////////////////////////////////// 288 // Choices 289 // ///////////////////////////////////////////////////////////// 290 291 @Override 292 public boolean hasChoices() { 293 final ActionParameterChoicesFacet choicesFacet = getFacet(ActionParameterChoicesFacet.class); 294 return choicesFacet != null; 295 } 296 297 @Override 298 public ObjectAdapter[] getChoices(final ObjectAdapter adapter, final ObjectAdapter[] argumentsIfAvailable) { 299 final List<ObjectAdapter> argListIfAvailable = ListExtensions.mutableCopy(argumentsIfAvailable); 300 301 final ObjectAdapter target = targetForDefaultOrChoices(adapter, argListIfAvailable); 302 final List<ObjectAdapter> args = argsForDefaultOrChoices(adapter, argListIfAvailable); 303 304 return findChoices(target, args); 305 } 306 307 private ObjectAdapter[] findChoices(final ObjectAdapter target, final List<ObjectAdapter> args) { 308 final List<ObjectAdapter> adapters = Lists.newArrayList(); 309 final ActionParameterChoicesFacet facet = getFacet(ActionParameterChoicesFacet.class); 310 311 if (facet != null) { 312 final Object[] choices = facet.getChoices(target, args); 313 checkChoicesOrAutoCompleteType(getSpecificationLookup(), choices, getSpecification()); 314 for (final Object choice : choices) { 315 ObjectAdapter adapter = choice != null? getAdapterMap().adapterFor(choice) : null; 316 adapters.add(adapter); 317 } 318 } 319 // now incorporated into above choices processing (BoundedFacet is no more) 320 /* 321 if (adapters.size() == 0 && BoundedFacetUtils.isBoundedSet(getSpecification())) { 322 addAllInstancesForType(adapters); 323 } 324 */ 325 return adapters.toArray(new ObjectAdapter[0]); 326 } 327 328 // ///////////////////////////////////////////////////////////// 329 // Defaults 330 // ///////////////////////////////////////////////////////////// 331 332 @Override 333 public ObjectAdapter getDefault(final ObjectAdapter adapter) { 334 335 final ObjectAdapter target = targetForDefaultOrChoices(adapter, null); 336 final List<ObjectAdapter> args = argsForDefaultOrChoices(adapter, null); 337 338 return findDefault(target, args); 339 } 340 341 private ObjectAdapter findDefault( 342 final ObjectAdapter target, 343 final List<ObjectAdapter> args) { 344 final ActionParameterDefaultsFacet defaultsFacet = getFacet(ActionParameterDefaultsFacet.class); 345 if (defaultsFacet != null) { 346 final Object dflt = defaultsFacet.getDefault(target, args); 347 if (dflt == null) { 348 // it's possible that even though there is a default facet, when 349 // invoked it is unable to return a default. 350 return null; 351 } 352 return getAdapterMap().adapterFor(dflt); 353 } 354 return null; 355 } 356 357 /** 358 * Hook method; {@link ObjectActionParameterContributee contributed action parameter}s override. 359 */ 360 protected ObjectAdapter targetForDefaultOrChoices(ObjectAdapter adapter, final List<ObjectAdapter> argumentsIfAvailable) { 361 return adapter; 362 } 363 364 /** 365 * Hook method; {@link ObjectActionParameterContributee contributed action parameter}s override. 366 */ 367 protected List<ObjectAdapter> argsForDefaultOrChoices(final ObjectAdapter adapter, final List<ObjectAdapter> argumentsIfAvailable) { 368 return argumentsIfAvailable; 369 } 370 371 372 // helpers 373 374 static void checkChoicesOrAutoCompleteType(final SpecificationLoader specificationLookup, final Object[] objects, final ObjectSpecification paramSpec) { 375 for (final Object object : objects) { 376 377 if(object == null) { 378 continue; 379 } 380 381 // check type, but wrap first 382 // (eg we treat int.class and java.lang.Integer.class as compatible with each other) 383 final Class<? extends Object> choiceClass = object.getClass(); 384 final Class<?> paramClass = paramSpec.getCorrespondingClass(); 385 386 final Class<? extends Object> choiceWrappedClass = ClassExtensions.asWrappedIfNecessary(choiceClass); 387 final Class<? extends Object> paramWrappedClass = ClassExtensions.asWrappedIfNecessary(paramClass); 388 389 final ObjectSpecification choiceWrappedSpec = specificationLookup.loadSpecification(choiceWrappedClass); 390 final ObjectSpecification paramWrappedSpec = specificationLookup.loadSpecification(paramWrappedClass); 391 392 if (!choiceWrappedSpec.isOfType(paramWrappedSpec)) { 393 throw new DomainModelException("Type incompatible with parameter type; expected " + paramSpec.getFullIdentifier() + ", but was " + choiceClass.getName()); 394 } 395 } 396 } 397 398 /** 399 * unused - incorporated into the {@link ChoicesFacetFromBoundedAbstract} 400 */ 401 @SuppressWarnings("unused") 402 private <T> void addAllInstancesForType(final List<ObjectAdapter> adapters) { 403 final Query<T> query = new QueryFindAllInstances<T>(getSpecification().getFullIdentifier()); 404 final List<ObjectAdapter> allInstancesAdapter = getQuerySubmitter().allMatchingQuery(query); 405 for (final ObjectAdapter choiceAdapter : allInstancesAdapter) { 406 adapters.add(choiceAdapter); 407 } 408 } 409 410 411 // ///////////////////////////////////////////////////////////// 412 // Validation 413 // ///////////////////////////////////////////////////////////// 414 415 @Override 416 public ActionArgumentContext createProposedArgumentInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter targetObject, final ObjectAdapter[] proposedArguments, final int position) { 417 return new ActionArgumentContext(getDeploymentCategory(), getAuthenticationSession(), invocationMethod, targetObject, getIdentifier(), proposedArguments, position); 418 } 419 420 @Override 421 public String isValid(final ObjectAdapter adapter, final Object proposedValue, final Localization localization) { 422 423 ObjectAdapter proposedValueAdapter = null; 424 ObjectSpecification proposedValueSpec; 425 if(proposedValue != null) { 426 proposedValueAdapter = getAdapterMap().adapterFor(proposedValue); 427 proposedValueSpec = proposedValueAdapter.getSpecification(); 428 if(!proposedValueSpec.isOfType(proposedValueSpec)) { 429 proposedValueAdapter = doCoerceProposedValue(adapter, proposedValue, localization); 430 } 431 432 // check has been coerced into correct type; otherwise give up 433 if(proposedValueAdapter == null) { 434 return null; 435 } 436 proposedValueSpec = proposedValueAdapter.getSpecification(); 437 if(!proposedValueSpec.isOfType(proposedValueSpec)) { 438 return null; 439 } 440 } 441 442 443 final ValidityContext<?> ic = createProposedArgumentInteractionContext(getAuthenticationSession(), InteractionInvocationMethod.BY_USER, adapter, arguments(proposedValueAdapter), getNumber()); 444 445 final InteractionResultSet buf = new InteractionResultSet(); 446 InteractionUtils.isValidResultSet(this, ic, buf); 447 if (buf.isVetoed()) { 448 return buf.getInteractionResult().getReason(); 449 } 450 return null; 451 452 } 453 454 /** 455 * Optional hook for parsing. 456 */ 457 protected ObjectAdapter doCoerceProposedValue(ObjectAdapter adapter, Object proposedValue, final Localization localization) { 458 return null; 459 } 460 461 /** 462 * TODO: this is not ideal, because we can only populate the array for 463 * single argument, rather than the entire argument set. Instead, we ought 464 * to do this in two passes, one to build up the argument set as a single 465 * unit, and then validate each in turn. 466 */ 467 private ObjectAdapter[] arguments(final ObjectAdapter proposedValue) { 468 final int parameterCount = getAction().getParameterCount(); 469 final ObjectAdapter[] arguments = new ObjectAdapter[parameterCount]; 470 arguments[getNumber()] = proposedValue; 471 return arguments; 472 } 473 474 // ///////////////////////////////////////////////////////////// 475 // Dependencies (from parent) 476 // ///////////////////////////////////////////////////////////// 477 478 private DeploymentCategory getDeploymentCategory() { 479 return parentAction.getDeploymentCategory(); 480 } 481 482 protected SpecificationLoader getSpecificationLookup() { 483 return parentAction.getSpecificationLookup(); 484 } 485 486 protected AuthenticationSessionProvider getAuthenticationSessionProvider() { 487 return parentAction.getAuthenticationSessionProvider(); 488 } 489 490 protected AdapterManager getAdapterMap() { 491 return parentAction.getAdapterManager(); 492 } 493 494 protected QuerySubmitter getQuerySubmitter() { 495 return parentAction.getQuerySubmitter(); 496 } 497 498 protected AuthenticationSession getAuthenticationSession() { 499 return getAuthenticationSessionProvider().getAuthenticationSession(); 500 } 501 502}