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.*; 023import com.google.common.base.Function; 024import com.google.common.collect.Collections2; 025import com.google.common.collect.Iterables; 026import com.google.common.collect.Lists; 027import com.google.common.collect.Maps; 028import org.slf4j.Logger; 029import org.slf4j.LoggerFactory; 030import org.apache.isis.applib.Identifier; 031import org.apache.isis.applib.annotation.NotPersistable; 032import org.apache.isis.applib.annotation.When; 033import org.apache.isis.applib.annotation.Where; 034import org.apache.isis.applib.filter.Filter; 035import org.apache.isis.applib.filter.Filters; 036import org.apache.isis.applib.profiles.Localization; 037import org.apache.isis.core.commons.authentication.AuthenticationSession; 038import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider; 039import org.apache.isis.core.commons.exceptions.UnknownTypeException; 040import org.apache.isis.core.commons.lang.ClassExtensions; 041import org.apache.isis.core.commons.util.ToString; 042import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 043import org.apache.isis.core.metamodel.adapter.ServicesProvider; 044import org.apache.isis.core.metamodel.consent.Consent; 045import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod; 046import org.apache.isis.core.metamodel.consent.InteractionResult; 047import org.apache.isis.core.metamodel.deployment.DeploymentCategory; 048import org.apache.isis.core.metamodel.facetapi.Facet; 049import org.apache.isis.core.metamodel.facetapi.FacetHolder; 050import org.apache.isis.core.metamodel.facetapi.FacetHolderImpl; 051import org.apache.isis.core.metamodel.facetapi.FeatureType; 052import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet; 053import org.apache.isis.core.metamodel.facets.describedas.DescribedAsFacet; 054import org.apache.isis.core.metamodel.facets.help.HelpFacet; 055import org.apache.isis.core.metamodel.facets.hide.HiddenFacet; 056import org.apache.isis.core.metamodel.facets.members.cssclass.CssClassFacet; 057import org.apache.isis.core.metamodel.facets.named.NamedFacet; 058import org.apache.isis.core.metamodel.facets.object.aggregated.ParentedFacet; 059import org.apache.isis.core.metamodel.facets.object.dirty.ClearDirtyObjectFacet; 060import org.apache.isis.core.metamodel.facets.object.dirty.IsDirtyObjectFacet; 061import org.apache.isis.core.metamodel.facets.object.dirty.MarkDirtyObjectFacet; 062import org.apache.isis.core.metamodel.facets.object.encodeable.EncodableFacet; 063import org.apache.isis.core.metamodel.facets.object.icon.IconFacet; 064import org.apache.isis.core.metamodel.facets.object.immutable.ImmutableFacet; 065import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet; 066import org.apache.isis.core.metamodel.facets.object.notpersistable.NotPersistableFacet; 067import org.apache.isis.core.metamodel.facets.object.objecttype.ObjectSpecIdFacet; 068import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet; 069import org.apache.isis.core.metamodel.facets.object.plural.PluralFacet; 070import org.apache.isis.core.metamodel.facets.object.title.TitleFacet; 071import org.apache.isis.core.metamodel.facets.object.value.ValueFacet; 072import org.apache.isis.core.metamodel.facets.typeof.TypeOfFacet; 073import org.apache.isis.core.metamodel.interactions.InteractionContext; 074import org.apache.isis.core.metamodel.interactions.InteractionUtils; 075import org.apache.isis.core.metamodel.interactions.ObjectTitleContext; 076import org.apache.isis.core.metamodel.interactions.ObjectValidityContext; 077import org.apache.isis.core.metamodel.layout.DeweyOrderSet; 078import org.apache.isis.core.metamodel.spec.*; 079import org.apache.isis.core.metamodel.spec.feature.*; 080import org.apache.isis.core.metamodel.specloader.facetprocessor.FacetProcessor; 081import org.apache.isis.core.metamodel.specloader.specimpl.objectlist.ObjectSpecificationForFreeStandingList; 082import org.apache.isis.core.progmodel.facets.actions.notcontributed.NotContributedFacet; 083 084public abstract class ObjectSpecificationAbstract extends FacetHolderImpl implements ObjectSpecification { 085 086 private final static Logger LOG = LoggerFactory.getLogger(ObjectSpecificationAbstract.class); 087 088 private static class SubclassList { 089 private final List<ObjectSpecification> classes = Lists.newArrayList(); 090 091 public void addSubclass(final ObjectSpecification subclass) { 092 if(classes.contains(subclass)) { 093 return; 094 } 095 classes.add(subclass); 096 } 097 098 public boolean hasSubclasses() { 099 return !classes.isEmpty(); 100 } 101 102 public List<ObjectSpecification> toList() { 103 return Collections.unmodifiableList(classes); 104 } 105 } 106 107 private final DeploymentCategory deploymentCategory; 108 private final AuthenticationSessionProvider authenticationSessionProvider; 109 private final ServicesProvider servicesProvider; 110 private final ObjectInstantiator objectInstantiator; 111 private final SpecificationLoader specificationLookup; 112 113 private final FacetProcessor facetProcessor; 114 115 /** 116 * Only populated once {@link #introspectTypeHierarchyAndMembers()} is called. 117 */ 118 protected Properties metadataProperties; 119 120 protected final ObjectMemberContext objectMemberContext; 121 122 123 private final List<ObjectAssociation> associations = Lists.newArrayList(); 124 private final List<ObjectAction> objectActions = Lists.newArrayList(); 125 // partitions and caches objectActions by type; updated in sortCacheAndUpdateActions() 126 private final Map<ActionType, List<ObjectAction>> objectActionsByType = createObjectActionsByType(); 127 128 private static Map<ActionType, List<ObjectAction>> createObjectActionsByType() { 129 final Map<ActionType, List<ObjectAction>> map = Maps.newHashMap(); 130 for (final ActionType type : ActionType.values()) { 131 map.put(type, Lists.<ObjectAction>newArrayList()); 132 } 133 return map; 134 } 135 136 private boolean contributeeAssociationsAdded; 137 private boolean contributeeActionsAdded; 138 139 140 private final List<ObjectSpecification> interfaces = Lists.newArrayList(); 141 private final SubclassList subclasses = new SubclassList(); 142 143 private final Class<?> correspondingClass; 144 private final String fullName; 145 private final String shortName; 146 private final Identifier identifier; 147 private final boolean isAbstract; 148 // derived lazily, cached since immutable 149 private ObjectSpecId specId; 150 151 private ObjectSpecification superclassSpec; 152 153 private Persistability persistability = Persistability.USER_PERSISTABLE; 154 155 private MarkDirtyObjectFacet markDirtyObjectFacet; 156 private ClearDirtyObjectFacet clearDirtyObjectFacet; 157 private IsDirtyObjectFacet isDirtyObjectFacet; 158 159 private TitleFacet titleFacet; 160 private IconFacet iconFacet; 161 162 private IntrospectionState introspected = IntrospectionState.NOT_INTROSPECTED; 163 164 // ////////////////////////////////////////////////////////////////////// 165 // Constructor 166 // ////////////////////////////////////////////////////////////////////// 167 168 public ObjectSpecificationAbstract( 169 final Class<?> introspectedClass, 170 final String shortName, 171 final SpecificationContext specificationContext, 172 final ObjectMemberContext objectMemberContext) { 173 174 this.correspondingClass = introspectedClass; 175 this.fullName = introspectedClass.getName(); 176 this.shortName = shortName; 177 178 this.isAbstract = ClassExtensions.isAbstract(introspectedClass); 179 this.identifier = Identifier.classIdentifier(introspectedClass); 180 181 this.deploymentCategory = specificationContext.getDeploymentCategory(); 182 this.authenticationSessionProvider = specificationContext.getAuthenticationSessionProvider(); 183 this.servicesProvider = specificationContext.getServicesProvider(); 184 this.objectInstantiator = specificationContext.getObjectInstantiator(); 185 this.specificationLookup = specificationContext.getSpecificationLookup(); 186 this.facetProcessor = specificationContext.getFacetProcessor(); 187 188 this.objectMemberContext = objectMemberContext; 189 } 190 191 192 protected DeploymentCategory getDeploymentCategory() { 193 return deploymentCategory; 194 } 195 196 // ////////////////////////////////////////////////////////////////////// 197 // Stuff immediately derivable from class 198 // ////////////////////////////////////////////////////////////////////// 199 200 @Override 201 public FeatureType getFeatureType() { 202 return FeatureType.OBJECT; 203 } 204 205 @Override 206 public ObjectSpecId getSpecId() { 207 if(specId == null) { 208 final ObjectSpecIdFacet facet = getFacet(ObjectSpecIdFacet.class); 209 if(facet == null) { 210 throw new IllegalStateException("could not find an ObjectSpecIdFacet for " + this.getFullIdentifier()); 211 } 212 if(facet != null) { 213 specId = facet.value(); 214 } 215 } 216 return specId; 217 } 218 219 /** 220 * As provided explicitly within the 221 * {@link #ObjectSpecificationAbstract(Class, String, org.apache.isis.core.metamodel.spec.SpecificationContext, org.apache.isis.core.metamodel.spec.feature.ObjectMemberContext)} 222 * constructor}. 223 * 224 * <p> 225 * Not API, but <tt>public</tt> so that {@link FacetedMethodsBuilder} can 226 * call it. 227 */ 228 @Override 229 public Class<?> getCorrespondingClass() { 230 return correspondingClass; 231 } 232 233 @Override 234 public String getShortIdentifier() { 235 return shortName; 236 } 237 238 /** 239 * The {@link Class#getName() (full) name} of the 240 * {@link #getCorrespondingClass() class}. 241 */ 242 @Override 243 public String getFullIdentifier() { 244 return fullName; 245 } 246 247 248 public enum IntrospectionState { 249 NOT_INTROSPECTED, 250 BEING_INTROSPECTED, 251 INTROSPECTED, 252 } 253 254 /** 255 * Only if {@link #setIntrospectionState(org.apache.isis.core.metamodel.specloader.specimpl.ObjectSpecificationAbstract.IntrospectionState)} 256 * has been called (should be called within {@link #updateFromFacetValues()}. 257 */ 258 public IntrospectionState getIntrospectionState() { 259 return introspected; 260 } 261 262 public void setIntrospectionState(IntrospectionState introspectationState) { 263 this.introspected = introspectationState; 264 } 265 266 protected boolean isNotIntrospected() { 267 return !(getIntrospectionState() == IntrospectionState.INTROSPECTED); 268 } 269 270 271 // ////////////////////////////////////////////////////////////////////// 272 // Introspection (part 1) 273 // ////////////////////////////////////////////////////////////////////// 274 275 public abstract void introspectTypeHierarchyAndMembers(); 276 277 /** 278 * Intended to be called within {@link #introspectTypeHierarchyAndMembers()} 279 * . 280 */ 281 protected void updateSuperclass(final Class<?> superclass) { 282 if (superclass == null) { 283 return; 284 } 285 superclassSpec = getSpecificationLookup().loadSpecification(superclass); 286 if (superclassSpec != null) { 287 if (LOG.isDebugEnabled()) { 288 LOG.debug(" Superclass " + superclass.getName()); 289 } 290 updateAsSubclassTo(superclassSpec); 291 } 292 } 293 294 /** 295 * Intended to be called within {@link #introspectTypeHierarchyAndMembers()} 296 * . 297 */ 298 protected void updateInterfaces(final List<ObjectSpecification> interfaces) { 299 this.interfaces.clear(); 300 this.interfaces.addAll(interfaces); 301 } 302 303 /** 304 * Intended to be called within {@link #introspectTypeHierarchyAndMembers()} 305 * . 306 */ 307 protected void updateAsSubclassTo(final ObjectSpecification supertypeSpec) { 308 if (!(supertypeSpec instanceof ObjectSpecificationAbstract)) { 309 return; 310 } 311 // downcast required because addSubclass is (deliberately) not public 312 // API 313 final ObjectSpecificationAbstract introspectableSpec = (ObjectSpecificationAbstract) supertypeSpec; 314 introspectableSpec.updateSubclasses(this); 315 } 316 317 /** 318 * Intended to be called within {@link #introspectTypeHierarchyAndMembers()} 319 * . 320 */ 321 protected void updateAsSubclassTo(final List<ObjectSpecification> supertypeSpecs) { 322 for (final ObjectSpecification supertypeSpec : supertypeSpecs) { 323 updateAsSubclassTo(supertypeSpec); 324 } 325 } 326 327 private void updateSubclasses(final ObjectSpecification subclass) { 328 this.subclasses.addSubclass(subclass); 329 } 330 331 protected void sortAndUpdateAssociations(final List<ObjectAssociation> associations) { 332 final List<ObjectAssociation> orderedAssociations = sortAssociations(associations); 333 synchronized (this.associations) { 334 this.associations.clear(); 335 this.associations.addAll(orderedAssociations); 336 } 337 } 338 339 protected void sortCacheAndUpdateActions(final List<ObjectAction> objectActions) { 340 final List<ObjectAction> orderedActions = sortActions(objectActions); 341 synchronized (this.objectActions){ 342 this.objectActions.clear(); 343 this.objectActions.addAll(orderedActions); 344 345 for (final ActionType type : ActionType.values()) { 346 final List<ObjectAction> objectActionForType = objectActionsByType.get(type); 347 objectActionForType.clear(); 348 objectActionForType.addAll(Collections2.filter(objectActions, ObjectAction.Predicates.ofType(type))); 349 } 350 } 351 } 352 353 // ////////////////////////////////////////////////////////////////////// 354 // Introspection (part 2) 355 // ////////////////////////////////////////////////////////////////////// 356 357 public void updateFromFacetValues() { 358 clearDirtyObjectFacet = getFacet(ClearDirtyObjectFacet.class); 359 markDirtyObjectFacet = getFacet(MarkDirtyObjectFacet.class); 360 isDirtyObjectFacet = getFacet(IsDirtyObjectFacet.class); 361 362 titleFacet = getFacet(TitleFacet.class); 363 iconFacet = getFacet(IconFacet.class); 364 365 this.persistability = determinePersistability(); 366 } 367 368 private Persistability determinePersistability() { 369 final NotPersistableFacet notPersistableFacet = getFacet(NotPersistableFacet.class); 370 if (notPersistableFacet == null) { 371 return Persistability.USER_PERSISTABLE; 372 } 373 final NotPersistable.By initiatedBy = notPersistableFacet.value(); 374 if (initiatedBy == NotPersistable.By.USER_OR_PROGRAM) { 375 return Persistability.TRANSIENT; 376 } else if (initiatedBy == NotPersistable.By.USER) { 377 return Persistability.PROGRAM_PERSISTABLE; 378 } else { 379 return Persistability.USER_PERSISTABLE; 380 } 381 } 382 383 /** 384 * Intended to be called (if at all) within {@link #updateFromFacetValues()} 385 * . 386 */ 387 protected void setClearDirtyObjectFacet(final ClearDirtyObjectFacet clearDirtyObjectFacet) { 388 this.clearDirtyObjectFacet = clearDirtyObjectFacet; 389 } 390 391 392 // ////////////////////////////////////////////////////////////////////// 393 // Title, Icon 394 // ////////////////////////////////////////////////////////////////////// 395 396 @Override 397 public String getTitle(final ObjectAdapter targetAdapter, final Localization localization) { 398 return getTitle(null, targetAdapter, localization); 399 } 400 401 @Override 402 public String getTitle(ObjectAdapter contextAdapterIfAny, ObjectAdapter targetAdapter, Localization localization) { 403 if (titleFacet != null) { 404 final String titleString = titleFacet.title(contextAdapterIfAny, targetAdapter, localization); 405 if (titleString != null && !titleString.equals("")) { 406 return titleString; 407 } 408 } 409 return (this.isService() ? "" : "Untitled ") + getSingularName(); 410 } 411 412 413 @Override 414 public String getIconName(final ObjectAdapter reference) { 415 return iconFacet == null ? null : iconFacet.iconName(reference); 416 } 417 418 // ////////////////////////////////////////////////////////////////////// 419 // Specification 420 // ////////////////////////////////////////////////////////////////////// 421 422 @Override 423 public Instance getInstance(final ObjectAdapter adapter) { 424 return adapter; 425 } 426 427 // ////////////////////////////////////////////////////////////////////// 428 // Hierarchical 429 // ////////////////////////////////////////////////////////////////////// 430 431 /** 432 * Determines if this class represents the same class, or a subclass, of the 433 * specified class. 434 * 435 * <p> 436 * cf {@link Class#isAssignableFrom(Class)}, though target and parameter are 437 * the opposite way around, ie: 438 * 439 * <pre> 440 * cls1.isAssignableFrom(cls2); 441 * </pre> 442 * <p> 443 * is equivalent to: 444 * 445 * <pre> 446 * spec2.isOfType(spec1); 447 * </pre> 448 * 449 * <p> 450 * Callable after {@link #introspectTypeHierarchyAndMembers()} has been 451 * called. 452 */ 453 @Override 454 public boolean isOfType(final ObjectSpecification specification) { 455 // do the comparison using value types because of a possible aliasing/race condition 456 // in matchesParameterOf when building up contributed associations 457 if (specification.getSpecId().equals(this.getSpecId())) { 458 return true; 459 } 460 for (final ObjectSpecification interfaceSpec : interfaces()) { 461 if (interfaceSpec.isOfType(specification)) { 462 return true; 463 } 464 } 465 final ObjectSpecification superclassSpec = superclass(); 466 return superclassSpec != null ? superclassSpec.isOfType(specification) : false; 467 } 468 469 // ////////////////////////////////////////////////////////////////////// 470 // Name, Description, Persistability 471 // ////////////////////////////////////////////////////////////////////// 472 473 /** 474 * The name according to any available {@link org.apache.isis.core.metamodel.facets.named.NamedFacet}, 475 * but falling back to {@link #getFullIdentifier()} otherwise. 476 */ 477 @Override 478 public String getSingularName() { 479 final NamedFacet namedFacet = getFacet(NamedFacet.class); 480 return namedFacet != null? namedFacet.value() : this.getFullIdentifier(); 481 } 482 483 /** 484 * The pluralized name according to any available {@link org.apache.isis.core.metamodel.facets.object.plural.PluralFacet}, 485 * else <tt>null</tt>. 486 */ 487 @Override 488 public String getPluralName() { 489 final PluralFacet pluralFacet = getFacet(PluralFacet.class); 490 return pluralFacet.value(); 491 } 492 493 /** 494 * The description according to any available {@link org.apache.isis.core.metamodel.facets.object.plural.PluralFacet}, 495 * else empty string (<tt>""</tt>). 496 */ 497 @Override 498 public String getDescription() { 499 final DescribedAsFacet describedAsFacet = getFacet(DescribedAsFacet.class); 500 final String describedAs = describedAsFacet.value(); 501 return describedAs == null ? "" : describedAs; 502 } 503 504 /* 505 * help is typically a reference (eg a URL) and so should not default to a 506 * textual value if not set up 507 */ 508 @Override 509 public String getHelp() { 510 final HelpFacet helpFacet = getFacet(HelpFacet.class); 511 return helpFacet == null ? null : helpFacet.value(); 512 } 513 514 @Override 515 public String getCssClass() { 516 final CssClassFacet cssClassFacet = getFacet(CssClassFacet.class); 517 return cssClassFacet == null ? null : cssClassFacet.value(); 518 } 519 520 @Override 521 public Persistability persistability() { 522 return persistability; 523 } 524 525 // ////////////////////////////////////////////////////////////////////// 526 // Dirty object support 527 // ////////////////////////////////////////////////////////////////////// 528 529 @Override 530 public boolean isDirty(final ObjectAdapter object) { 531 return isDirtyObjectFacet == null ? false : isDirtyObjectFacet.invoke(object); 532 } 533 534 @Override 535 public void clearDirty(final ObjectAdapter object) { 536 if (clearDirtyObjectFacet != null) { 537 clearDirtyObjectFacet.invoke(object); 538 } 539 } 540 541 @Override 542 public void markDirty(final ObjectAdapter object) { 543 if (markDirtyObjectFacet != null) { 544 markDirtyObjectFacet.invoke(object); 545 } 546 } 547 548 // ////////////////////////////////////////////////////////////////////// 549 // Facet Handling 550 // ////////////////////////////////////////////////////////////////////// 551 552 @Override 553 public <Q extends Facet> Q getFacet(final Class<Q> facetType) { 554 final Q facet = super.getFacet(facetType); 555 Q noopFacet = null; 556 if (isNotANoopFacet(facet)) { 557 return facet; 558 } else { 559 noopFacet = facet; 560 } 561 if (interfaces() != null) { 562 final List<ObjectSpecification> interfaces = interfaces(); 563 for (int i = 0; i < interfaces.size(); i++) { 564 final ObjectSpecification interfaceSpec = interfaces.get(i); 565 if (interfaceSpec == null) { 566 // HACK: shouldn't happen, but occurring on occasion when 567 // running 568 // XATs under JUnit4. Some sort of race condition? 569 continue; 570 } 571 final Q interfaceFacet = interfaceSpec.getFacet(facetType); 572 if (isNotANoopFacet(interfaceFacet)) { 573 return interfaceFacet; 574 } else { 575 if (noopFacet == null) { 576 noopFacet = interfaceFacet; 577 } 578 } 579 } 580 } 581 // search up the inheritance hierarchy 582 final ObjectSpecification superSpec = superclass(); 583 if (superSpec != null) { 584 final Q superClassFacet = superSpec.getFacet(facetType); 585 if (isNotANoopFacet(superClassFacet)) { 586 return superClassFacet; 587 } 588 } 589 return noopFacet; 590 } 591 592 private boolean isNotANoopFacet(final Facet facet) { 593 return facet != null && !facet.isNoop(); 594 } 595 596 // ////////////////////////////////////////////////////////////////////// 597 // DefaultValue 598 // ////////////////////////////////////////////////////////////////////// 599 600 @Override 601 public Object getDefaultValue() { 602 return null; 603 } 604 605 // ////////////////////////////////////////////////////////////////////// 606 // Identifier 607 // ////////////////////////////////////////////////////////////////////// 608 609 @Override 610 public Identifier getIdentifier() { 611 return identifier; 612 } 613 614 // ////////////////////////////////////////////////////////////////// 615 // create InteractionContext 616 // ////////////////////////////////////////////////////////////////// 617 618 @Override 619 public ObjectTitleContext createTitleInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod interactionMethod, final ObjectAdapter targetObjectAdapter) { 620 return new ObjectTitleContext(getDeploymentCategory(), session, interactionMethod, targetObjectAdapter, getIdentifier(), targetObjectAdapter.titleString()); 621 } 622 623 // ////////////////////////////////////////////////////////////////////// 624 // Superclass, Interfaces, Subclasses, isAbstract 625 // ////////////////////////////////////////////////////////////////////// 626 627 @Override 628 public ObjectSpecification superclass() { 629 return superclassSpec; 630 } 631 632 @Override 633 public List<ObjectSpecification> interfaces() { 634 return Collections.unmodifiableList(interfaces); 635 } 636 637 @Override 638 public List<ObjectSpecification> subclasses() { 639 return subclasses.toList(); 640 } 641 642 @Override 643 public boolean hasSubclasses() { 644 return subclasses.hasSubclasses(); 645 } 646 647 @Override 648 public final boolean isAbstract() { 649 return isAbstract; 650 } 651 652 // ////////////////////////////////////////////////////////////////////// 653 // Associations 654 // ////////////////////////////////////////////////////////////////////// 655 656 @Override 657 public List<ObjectAssociation> getAssociations(final Contributed contributed) { 658 // the "contributed.isIncluded()" guard is required because we cannot do this too early; 659 // there must be a session available 660 if(contributed.isIncluded() && !contributeeAssociationsAdded) { 661 synchronized (this.associations) { 662 List<ObjectAssociation> associations = Lists.newArrayList(this.associations); 663 associations.addAll(createContributeeAssociations()); 664 sortAndUpdateAssociations(associations); 665 contributeeAssociationsAdded = true; 666 } 667 } 668 final List<ObjectAssociation> associations = Lists.newArrayList(this.associations); 669 return Lists.newArrayList(Iterables.filter( 670 associations, ContributeeMember.Predicates.regularElse(contributed))); 671 } 672 673 674 private static ThreadLocal<Boolean> invalidatingCache = new ThreadLocal<Boolean>() { 675 protected Boolean initialValue() { 676 return Boolean.FALSE; 677 }; 678 }; 679 680 /** 681 * The association with the given {@link ObjectAssociation#getId() id}. 682 * 683 * <p> 684 * This is overridable because {@link ObjectSpecificationForFreeStandingList} 685 * simply returns <tt>null</tt>. 686 * 687 * <p> 688 * TODO put fields into hash. 689 * 690 * <p> 691 * TODO: could this be made final? (ie does the framework ever call this 692 * method for an {@link ObjectSpecificationForFreeStandingList}) 693 */ 694 @Override 695 public ObjectAssociation getAssociation(final String id) { 696 ObjectAssociation oa = getAssociationWithId(id); 697 if(oa != null) { 698 return oa; 699 } 700 if(!getDeploymentCategory().isProduction()) { 701 // automatically refresh if not in production 702 // (better support for jrebel) 703 704 LOG.warn("Could not find association with id '" + id + "'; invalidating cache automatically"); 705 if(!invalidatingCache.get()) { 706 // make sure don't go into an infinite loop, though. 707 try { 708 invalidatingCache.set(true); 709 getSpecificationLookup().invalidateCache(getCorrespondingClass()); 710 } finally { 711 invalidatingCache.set(false); 712 } 713 } else { 714 LOG.warn("... already invalidating cache earlier in stacktrace, so skipped this time"); 715 } 716 oa = getAssociationWithId(id); 717 if(oa != null) { 718 return oa; 719 } 720 } 721 throw new ObjectSpecificationException("No association called '" + id + "' in '" + getSingularName() + "'"); 722 } 723 724 private ObjectAssociation getAssociationWithId(final String id) { 725 for (final ObjectAssociation objectAssociation : getAssociations(Contributed.INCLUDED)) { 726 if (objectAssociation.getId().equals(id)) { 727 return objectAssociation; 728 } 729 } 730 return null; 731 } 732 733 @Deprecated 734 @Override 735 public List<ObjectAssociation> getAssociations(Filter<ObjectAssociation> filter) { 736 return getAssociations(Contributed.INCLUDED, filter); 737 } 738 739 @Override 740 public List<ObjectAssociation> getAssociations(Contributed contributed, final Filter<ObjectAssociation> filter) { 741 final List<ObjectAssociation> allAssociations = getAssociations(contributed); 742 return Lists.newArrayList( 743 Iterables.filter(allAssociations, Filters.asPredicate(filter))); 744 } 745 746 @SuppressWarnings({ "rawtypes", "unchecked" }) 747 @Override 748 public List<OneToOneAssociation> getProperties(Contributed contributed) { 749 final List list = getAssociations(contributed, ObjectAssociation.Filters.PROPERTIES); 750 return list; 751 } 752 753 @Override 754 @SuppressWarnings({ "unchecked", "rawtypes" }) 755 public List<OneToManyAssociation> getCollections(Contributed contributed) { 756 final List list = getAssociations(contributed, ObjectAssociation.Filters.COLLECTIONS); 757 return list; 758 } 759 760 // ////////////////////////////////////////////////////////////////////// 761 // getObjectActions 762 // ////////////////////////////////////////////////////////////////////// 763 764 @Override 765 public List<ObjectAction> getObjectActions( 766 final List<ActionType> types, 767 final Contributed contributed, 768 final Filter<ObjectAction> filter) { 769 770 // update our list of actions if requesting for contributed actions 771 // and they have not yet been added 772 // the "contributed.isIncluded()" guard is required because we cannot do this too early; 773 // there must be a session available 774 if(contributed.isIncluded() && !contributeeActionsAdded) { 775 synchronized (this.objectActions) { 776 final List<ObjectAction> actions = Lists.newArrayList(this.objectActions); 777 actions.addAll(createContributeeActions()); 778 sortCacheAndUpdateActions(actions); 779 contributeeActionsAdded = true; 780 } 781 } 782 783 final List<ObjectAction> actions = Lists.newArrayList(); 784 for (final ActionType type : types) { 785 final Collection<ObjectAction> filterActions = 786 Collections2.filter(objectActionsByType.get(type), Filters.asPredicate(filter)); 787 actions.addAll(filterActions); 788 } 789 return Lists.newArrayList( 790 Iterables.filter( 791 actions, 792 ContributeeMember.Predicates.regularElse(contributed))); 793 } 794 795 @Override 796 public List<ObjectAction> getObjectActions( 797 final Contributed contributed) { 798 return getObjectActions(ActionType.ALL, contributed, Filters.<ObjectAction>any()); 799 } 800 801 @Override 802 public List<ObjectAction> getObjectActions( 803 final ActionType type, 804 final Contributed contributed, 805 final Filter<ObjectAction> filter) { 806 return getObjectActions(Collections.singletonList(type), contributed, filter); 807 } 808 809 // ////////////////////////////////////////////////////////////////////// 810 // sorting 811 // ////////////////////////////////////////////////////////////////////// 812 813 protected List<ObjectAssociation> sortAssociations(final List<ObjectAssociation> associations) { 814 final DeweyOrderSet orderSet = DeweyOrderSet.createOrderSet(associations); 815 final MemberGroupLayoutFacet memberGroupLayoutFacet = this.getFacet(MemberGroupLayoutFacet.class); 816 817 if(memberGroupLayoutFacet != null) { 818 final List<String> groupOrder = Lists.newArrayList(); 819 groupOrder.addAll(memberGroupLayoutFacet.getLeft()); 820 groupOrder.addAll(memberGroupLayoutFacet.getMiddle()); 821 groupOrder.addAll(memberGroupLayoutFacet.getRight()); 822 823 orderSet.reorderChildren(groupOrder); 824 } 825 final List<ObjectAssociation> orderedAssociations = Lists.newArrayList(); 826 sortAssociations(orderSet, orderedAssociations); 827 return orderedAssociations; 828 } 829 830 private static void sortAssociations(final DeweyOrderSet orderSet, final List<ObjectAssociation> associationsToAppendTo) { 831 for (final Object element : orderSet) { 832 if (element instanceof OneToManyAssociation) { 833 associationsToAppendTo.add((ObjectAssociation) element); 834 } else if (element instanceof OneToOneAssociation) { 835 associationsToAppendTo.add((ObjectAssociation) element); 836 } else if (element instanceof DeweyOrderSet) { 837 // just flatten. 838 DeweyOrderSet childOrderSet = (DeweyOrderSet) element; 839 sortAssociations(childOrderSet, associationsToAppendTo); 840 } else { 841 throw new UnknownTypeException(element); 842 } 843 } 844 } 845 846 protected static List<ObjectAction> sortActions(final List<ObjectAction> actions) { 847 final DeweyOrderSet orderSet = DeweyOrderSet.createOrderSet(actions); 848 final List<ObjectAction> orderedActions = Lists.newArrayList(); 849 sortActions(orderSet, orderedActions); 850 return orderedActions; 851 } 852 853 private static void sortActions(final DeweyOrderSet orderSet, final List<ObjectAction> actionsToAppendTo) { 854 for (final Object element : orderSet) { 855 if(element instanceof ObjectAction) { 856 final ObjectAction objectAction = (ObjectAction) element; 857 actionsToAppendTo.add(objectAction); 858 } 859 else if (element instanceof DeweyOrderSet) { 860 final DeweyOrderSet set = ((DeweyOrderSet) element); 861 final List<ObjectAction> actions = Lists.newArrayList(); 862 sortActions(set, actions); 863 actionsToAppendTo.addAll(actions); 864 } else { 865 throw new UnknownTypeException(element); 866 } 867 } 868 } 869 870 // ////////////////////////////////////////////////////////////////////// 871 // getServiceActionsReturning 872 // ////////////////////////////////////////////////////////////////////// 873 874 @Override 875 public List<ObjectAction> getServiceActionsReturning(final List<ActionType> types) { 876 final List<ObjectAction> serviceActions = Lists.newArrayList(); 877 final List<ObjectAdapter> services = getServicesProvider().getServices(); 878 for (final ObjectAdapter serviceAdapter : services) { 879 appendServiceActionsReturning(serviceAdapter, types, serviceActions); 880 } 881 return serviceActions; 882 } 883 884 private void appendServiceActionsReturning(final ObjectAdapter serviceAdapter, final List<ActionType> types, final List<ObjectAction> relatedActionsToAppendTo) { 885 final List<ObjectAction> matchingActionsToAppendTo = Lists.newArrayList(); 886 for (final ActionType type : types) { 887 final List<ObjectAction> serviceActions = serviceAdapter.getSpecification().getObjectActions(type, Contributed.INCLUDED, Filters.<ObjectAction>any()); 888 for (final ObjectAction serviceAction : serviceActions) { 889 addIfReturnsSubtype(serviceAction, matchingActionsToAppendTo); 890 } 891 } 892 relatedActionsToAppendTo.addAll(matchingActionsToAppendTo); 893 } 894 895 private void addIfReturnsSubtype(final ObjectAction serviceAction, final List<ObjectAction> matchingActionsToAppendTo) { 896 final ObjectSpecification returnType = serviceAction.getReturnType(); 897 if (returnType == null) { 898 return; 899 } 900 if (returnType.isParentedOrFreeCollection()) { 901 final TypeOfFacet facet = serviceAction.getFacet(TypeOfFacet.class); 902 if (facet != null) { 903 final ObjectSpecification elementType = facet.valueSpec(); 904 addIfReturnsSubtype(serviceAction, elementType, matchingActionsToAppendTo); 905 } 906 } else { 907 addIfReturnsSubtype(serviceAction, returnType, matchingActionsToAppendTo); 908 } 909 } 910 911 private void addIfReturnsSubtype(final ObjectAction serviceAction, final ObjectSpecification actionType, final List<ObjectAction> matchingActionsToAppendTo) { 912 if (actionType.isOfType(this)) { 913 matchingActionsToAppendTo.add(serviceAction); 914 } 915 } 916 917 918 // ////////////////////////////////////////////////////////////////////// 919 // contributee associations (properties and collections) 920 // ////////////////////////////////////////////////////////////////////// 921 922 private List<ObjectAssociation> createContributeeAssociations() { 923 if (isService()) { 924 return Collections.emptyList(); 925 } 926 927 final List<ObjectAssociation> contributeeAssociations = Lists.newArrayList(); 928 final List<ObjectAdapter> services = getServicesProvider().getServices(); 929 for (final ObjectAdapter serviceAdapter : services) { 930 addContributeeAssociationsIfAny(serviceAdapter, contributeeAssociations); 931 } 932 return contributeeAssociations; 933 } 934 935 private void addContributeeAssociationsIfAny( 936 final ObjectAdapter serviceAdapter, 937 final List<ObjectAssociation> contributeeAssociationsToAppendTo) { 938 final ObjectSpecification specification = serviceAdapter.getSpecification(); 939 if (specification == this) { 940 return; 941 } 942 final List<ObjectAssociation> contributeeAssociations = createContributeeAssociations(serviceAdapter); 943 contributeeAssociationsToAppendTo.addAll(contributeeAssociations); 944 } 945 946 /** 947 * Synthesises {@link ObjectAssociation}s from matching {@link ObjectAction}s of any of the services 948 * that accept one parameter 949 */ 950 private List<ObjectAssociation> createContributeeAssociations(final ObjectAdapter serviceAdapter) { 951 952 final ObjectSpecification specification = serviceAdapter.getSpecification(); 953 final List<ObjectAction> serviceActions = specification.getObjectActions(ActionType.USER, Contributed.INCLUDED, Filters.<ObjectAction>any()); 954 955 final List<ObjectActionImpl> contributedActions = Lists.newArrayList(); 956 for (final ObjectAction serviceAction : serviceActions) { 957 if (isAlwaysHidden(serviceAction)) { 958 continue; 959 } 960 final NotContributedFacet notContributed = serviceAction.getFacet(NotContributedFacet.class); 961 if(notContributed != null && notContributed.toAssociations()) { 962 continue; 963 } 964 if(!serviceAction.hasReturn()) { 965 continue; 966 } 967 if (serviceAction.getParameterCount() != 1 || contributeeParameterMatchOf(serviceAction) == -1) { 968 continue; 969 } 970 if(!(serviceAction instanceof ObjectActionImpl)) { 971 continue; 972 } 973 contributedActions.add((ObjectActionImpl) serviceAction); 974 } 975 976 return Lists.newArrayList(Iterables.transform(contributedActions, createContributeeAssociationFunctor(serviceAdapter, this))); 977 } 978 979 980 private Function<ObjectActionImpl, ObjectAssociation> createContributeeAssociationFunctor( 981 final ObjectAdapter serviceAdapter, final ObjectSpecification contributeeType) { 982 return new Function<ObjectActionImpl, ObjectAssociation>(){ 983 @Override 984 public ObjectAssociation apply(ObjectActionImpl input) { 985 final ObjectSpecification returnType = input.getReturnType(); 986 final ObjectAssociationAbstract association = returnType.isNotCollection() 987 ? new OneToOneAssociationContributee(serviceAdapter, input, contributeeType, objectMemberContext) 988 : new OneToManyAssociationContributee(serviceAdapter, input, contributeeType, objectMemberContext); 989 facetProcessor.processMemberOrder(metadataProperties, association); 990 return association; 991 } 992 }; 993 } 994 995 996 // ////////////////////////////////////////////////////////////////////// 997 // contributee actions 998 // ////////////////////////////////////////////////////////////////////// 999 1000 /** 1001 * All contributee actions (each wrapping a service's contributed action) for this spec. 1002 * 1003 * <p> 1004 * If this specification {@link #isService() is actually for} a service, 1005 * then returns an empty list. 1006 */ 1007 protected List<ObjectAction> createContributeeActions() { 1008 if (isService()) { 1009 return Collections.emptyList(); 1010 } 1011 final List<ObjectAction> contributeeActions = Lists.newArrayList(); 1012 1013 final List<ObjectAdapter> services = getServicesProvider().getServices(); 1014 for (final ObjectAdapter serviceAdapter : services) { 1015 addContributeeActionsIfAny(serviceAdapter, contributeeActions); 1016 } 1017 return contributeeActions; 1018 } 1019 1020 private void addContributeeActionsIfAny( 1021 final ObjectAdapter serviceAdapter, 1022 final List<ObjectAction> contributeeActionsToAppendTo) { 1023 final ObjectSpecification specification = serviceAdapter.getSpecification(); 1024 if (specification == this) { 1025 return; 1026 } 1027 final List<ObjectAction> contributeeActions = Lists.newArrayList(); 1028 final List<ObjectAction> serviceActions = specification.getObjectActions(ActionType.ALL, Contributed.INCLUDED, Filters.<ObjectAction>any()); 1029 for (final ObjectAction serviceAction : serviceActions) { 1030 if (isAlwaysHidden(serviceAction)) { 1031 continue; 1032 } 1033 final NotContributedFacet notContributed = serviceAction.getFacet(NotContributedFacet.class); 1034 if(notContributed != null && notContributed.toActions()) { 1035 continue; 1036 } 1037 if(!(serviceAction instanceof ObjectActionImpl)) { 1038 continue; 1039 } 1040 final ObjectActionImpl contributedAction = (ObjectActionImpl) serviceAction; 1041 1042 // see if qualifies by inspecting all parameters 1043 final int contributeeParam = contributeeParameterMatchOf(contributedAction); 1044 if (contributeeParam != -1) { 1045 ObjectActionContributee contributeeAction = 1046 new ObjectActionContributee(serviceAdapter, contributedAction, contributeeParam, this, objectMemberContext); 1047 facetProcessor.processMemberOrder(metadataProperties, contributeeAction); 1048 contributeeActions.add(contributeeAction); 1049 } 1050 } 1051 contributeeActionsToAppendTo.addAll(contributeeActions); 1052 } 1053 1054 private boolean isAlwaysHidden(final FacetHolder holder) { 1055 final HiddenFacet hiddenFacet = holder.getFacet(HiddenFacet.class); 1056 return hiddenFacet != null && hiddenFacet.when() == When.ALWAYS && hiddenFacet.where() == Where.ANYWHERE; 1057 } 1058 1059 1060 1061 /** 1062 * @param serviceAction - number of the parameter that matches, or -1 if none. 1063 */ 1064 private int contributeeParameterMatchOf(final ObjectAction serviceAction) { 1065 final List<ObjectActionParameter> params = serviceAction.getParameters(); 1066 for (final ObjectActionParameter param : params) { 1067 if (isOfType(param.getSpecification())) { 1068 return param.getNumber(); 1069 } 1070 } 1071 return -1; 1072 } 1073 1074 // ////////////////////////////////////////////////////////////////////// 1075 // validity 1076 // ////////////////////////////////////////////////////////////////////// 1077 1078 @Override 1079 public Consent isValid(final ObjectAdapter inObject) { 1080 return isValidResult(inObject).createConsent(); 1081 } 1082 1083 /** 1084 * TODO: currently this method is hard-coded to assume all interactions are 1085 * initiated {@link InteractionInvocationMethod#BY_USER by user}. 1086 */ 1087 @Override 1088 public InteractionResult isValidResult(final ObjectAdapter targetObjectAdapter) { 1089 final ObjectValidityContext validityContext = createValidityInteractionContext(deploymentCategory, getAuthenticationSession(), InteractionInvocationMethod.BY_USER, targetObjectAdapter); 1090 return InteractionUtils.isValidResult(this, validityContext); 1091 } 1092 1093 /** 1094 * Create an {@link InteractionContext} representing an attempt to save the 1095 * object. 1096 */ 1097 @Override 1098 public ObjectValidityContext createValidityInteractionContext(DeploymentCategory deploymentCategory, final AuthenticationSession session, final InteractionInvocationMethod interactionMethod, final ObjectAdapter targetObjectAdapter) { 1099 return new ObjectValidityContext(deploymentCategory, session, interactionMethod, targetObjectAdapter, getIdentifier()); 1100 } 1101 1102 // ////////////////////////////////////////////////////////////////////// 1103 // convenience isXxx (looked up from facets) 1104 // ////////////////////////////////////////////////////////////////////// 1105 1106 @Override 1107 public boolean isImmutable() { 1108 return containsFacet(ImmutableFacet.class); 1109 } 1110 1111 @Override 1112 public boolean isHidden() { 1113 return containsFacet(HiddenFacet.class); 1114 } 1115 1116 @Override 1117 public boolean isParseable() { 1118 return containsFacet(ParseableFacet.class); 1119 } 1120 1121 @Override 1122 public boolean isEncodeable() { 1123 return containsFacet(EncodableFacet.class); 1124 } 1125 1126 @Override 1127 public boolean isValue() { 1128 return containsFacet(ValueFacet.class); 1129 } 1130 1131 @Override 1132 public boolean isParented() { 1133 return containsFacet(ParentedFacet.class); 1134 } 1135 1136 @Override 1137 public boolean isParentedOrFreeCollection() { 1138 return containsFacet(CollectionFacet.class); 1139 } 1140 1141 @Override 1142 public boolean isNotCollection() { 1143 return !isParentedOrFreeCollection(); 1144 } 1145 1146 @Override 1147 public boolean isValueOrIsParented() { 1148 return isValue() || isParented(); 1149 } 1150 1151 // ////////////////////////////////////////////////////////////////////// 1152 // misc 1153 // ////////////////////////////////////////////////////////////////////// 1154 1155 @Override 1156 public Object createObject() { 1157 throw new UnsupportedOperationException(getFullIdentifier()); 1158 } 1159 1160 @Override 1161 public ObjectAdapter initialize(ObjectAdapter objectAdapter) { 1162 return objectAdapter; 1163 } 1164 1165 // ////////////////////////////////////////////////////////////////////// 1166 // toString 1167 // ////////////////////////////////////////////////////////////////////// 1168 1169 @Override 1170 public String toString() { 1171 final ToString str = new ToString(this); 1172 str.append("class", getFullIdentifier()); 1173 return str.toString(); 1174 } 1175 1176 // ////////////////////////////////////////////////////////////////////// 1177 // Convenience 1178 // ////////////////////////////////////////////////////////////////////// 1179 1180 /** 1181 * convenience method to return the current {@link AuthenticationSession} 1182 * from the {@link #getAuthenticationSessionProvider() injected} 1183 * {@link AuthenticationSessionProvider}. 1184 */ 1185 protected final AuthenticationSession getAuthenticationSession() { 1186 return getAuthenticationSessionProvider().getAuthenticationSession(); 1187 } 1188 1189 // ////////////////////////////////////////////////////////////////////// 1190 // Dependencies (injected in constructor) 1191 // ////////////////////////////////////////////////////////////////////// 1192 1193 protected AuthenticationSessionProvider getAuthenticationSessionProvider() { 1194 return authenticationSessionProvider; 1195 } 1196 1197 public ServicesProvider getServicesProvider() { 1198 return servicesProvider; 1199 } 1200 1201 public ObjectInstantiator getObjectInstantiator() { 1202 return objectInstantiator; 1203 } 1204 1205 public SpecificationLoader getSpecificationLookup() { 1206 return specificationLookup; 1207 } 1208 1209 1210}