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 org.apache.isis.applib.annotation.Where; 023import org.apache.isis.core.commons.authentication.AuthenticationSession; 024import org.apache.isis.core.commons.debug.DebugString; 025import org.apache.isis.core.commons.exceptions.IsisException; 026import org.apache.isis.core.commons.util.ToString; 027import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 028import org.apache.isis.core.metamodel.consent.Consent; 029import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod; 030import org.apache.isis.core.metamodel.consent.InteractionResult; 031import org.apache.isis.core.metamodel.facetapi.FeatureType; 032import org.apache.isis.core.metamodel.facets.FacetedMethod; 033import org.apache.isis.core.metamodel.facets.accessor.PropertyOrCollectionAccessorFacet; 034import org.apache.isis.core.metamodel.facets.collections.modify.CollectionAddToFacet; 035import org.apache.isis.core.metamodel.facets.collections.modify.CollectionClearFacet; 036import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet; 037import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacetUtils; 038import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemoveFromFacet; 039import org.apache.isis.core.metamodel.interactions.CollectionAddToContext; 040import org.apache.isis.core.metamodel.interactions.CollectionRemoveFromContext; 041import org.apache.isis.core.metamodel.interactions.CollectionUsabilityContext; 042import org.apache.isis.core.metamodel.interactions.CollectionVisibilityContext; 043import org.apache.isis.core.metamodel.interactions.InteractionUtils; 044import org.apache.isis.core.metamodel.interactions.UsabilityContext; 045import org.apache.isis.core.metamodel.interactions.ValidityContext; 046import org.apache.isis.core.metamodel.interactions.VisibilityContext; 047import org.apache.isis.core.metamodel.spec.Instance; 048import org.apache.isis.core.metamodel.spec.ObjectSpecification; 049import org.apache.isis.core.metamodel.spec.feature.ObjectMemberContext; 050import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation; 051 052public class OneToManyAssociationImpl extends ObjectAssociationAbstract implements OneToManyAssociation { 053 054 public OneToManyAssociationImpl( 055 final FacetedMethod facetedMethod, 056 final ObjectMemberContext objectMemberContext) { 057 this(facetedMethod, getSpecification(objectMemberContext.getSpecificationLookup(), facetedMethod.getType()), objectMemberContext); 058 } 059 060 protected OneToManyAssociationImpl( 061 final FacetedMethod facetedMethod, 062 final ObjectSpecification objectSpec, 063 final ObjectMemberContext objectMemberContext) { 064 super(facetedMethod, FeatureType.COLLECTION, objectSpec, objectMemberContext); 065 } 066 067 @Override 068 public CollectionSemantics getCollectionSemantics() { 069 final Class<?> underlyingClass = getSpecification().getCorrespondingClass(); 070 return getCollectionTypeRegistry().semanticsOf(underlyingClass); 071 } 072 073 // ///////////////////////////////////////////////////////////// 074 // Hidden (or visible) 075 // ///////////////////////////////////////////////////////////// 076 077 @Override 078 public VisibilityContext<?> createVisibleInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter ownerAdapter, Where where) { 079 return new CollectionVisibilityContext(getDeploymentCategory(), session, invocationMethod, ownerAdapter, getIdentifier(), where); 080 } 081 082 // ///////////////////////////////////////////////////////////// 083 // Disabled (or enabled) 084 // ///////////////////////////////////////////////////////////// 085 086 @Override 087 public UsabilityContext<?> createUsableInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter ownerAdapter, Where where) { 088 return new CollectionUsabilityContext(getDeploymentCategory(), session, invocationMethod, ownerAdapter, getIdentifier(), where); 089 } 090 091 // ///////////////////////////////////////////////////////////// 092 // Validate Add 093 // ///////////////////////////////////////////////////////////// 094 095 @Override 096 public ValidityContext<?> createValidateAddInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToAddAdapter) { 097 return new CollectionAddToContext(getDeploymentCategory(), session, invocationMethod, ownerAdapter, getIdentifier(), proposedToAddAdapter); 098 } 099 100 /** 101 * TODO: currently this method is hard-coded to assume all interactions are 102 * initiated {@link InteractionInvocationMethod#BY_USER by user}. 103 */ 104 @Override 105 public Consent isValidToAdd(final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToAddAdapter) { 106 return isValidToAddResult(ownerAdapter, proposedToAddAdapter).createConsent(); 107 } 108 109 private InteractionResult isValidToAddResult(final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToAddAdapter) { 110 final ValidityContext<?> validityContext = createValidateAddInteractionContext(getAuthenticationSession(), InteractionInvocationMethod.BY_USER, ownerAdapter, proposedToAddAdapter); 111 return InteractionUtils.isValidResult(this, validityContext); 112 } 113 114 // ///////////////////////////////////////////////////////////// 115 // Validate Remove 116 // ///////////////////////////////////////////////////////////// 117 118 @Override 119 public ValidityContext<?> createValidateRemoveInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToRemoveAdapter) { 120 return new CollectionRemoveFromContext(getDeploymentCategory(), session, invocationMethod, ownerAdapter, getIdentifier(), proposedToRemoveAdapter); 121 } 122 123 /** 124 * TODO: currently this method is hard-coded to assume all interactions are 125 * initiated {@link InteractionInvocationMethod#BY_USER by user}. 126 */ 127 @Override 128 public Consent isValidToRemove(final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToRemoveAdapter) { 129 return isValidToRemoveResult(ownerAdapter, proposedToRemoveAdapter).createConsent(); 130 } 131 132 private InteractionResult isValidToRemoveResult(final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToRemoveAdapter) { 133 final ValidityContext<?> validityContext = createValidateRemoveInteractionContext(getAuthenticationSession(), InteractionInvocationMethod.BY_USER, ownerAdapter, proposedToRemoveAdapter); 134 return InteractionUtils.isValidResult(this, validityContext); 135 } 136 137 private boolean readWrite() { 138 return !isNotPersisted(); 139 } 140 141 // ///////////////////////////////////////////////////////////// 142 // get, isEmpty, add, clear 143 // ///////////////////////////////////////////////////////////// 144 145 @Override 146 public ObjectAdapter get(final ObjectAdapter ownerAdapter) { 147 148 final PropertyOrCollectionAccessorFacet accessor = getFacet(PropertyOrCollectionAccessorFacet.class); 149 final Object collection = accessor.getProperty(ownerAdapter); 150 if (collection == null) { 151 return null; 152 } 153 return getAdapterManager().adapterFor(collection, ownerAdapter, this); 154 } 155 156 @Override 157 public boolean isEmpty(final ObjectAdapter parentAdapter) { 158 // REVIEW should we be able to determine if a collection is empty 159 // without loading it? 160 final ObjectAdapter collection = get(parentAdapter); 161 final CollectionFacet facet = CollectionFacetUtils.getCollectionFacetFromSpec(collection); 162 return facet.size(collection) == 0; 163 } 164 165 // ///////////////////////////////////////////////////////////// 166 // add, clear 167 // ///////////////////////////////////////////////////////////// 168 169 @Override 170 public void addElement(final ObjectAdapter ownerAdapter, final ObjectAdapter referencedAdapter) { 171 if (referencedAdapter == null) { 172 throw new IllegalArgumentException("Can't use null to add an item to a collection"); 173 } 174 if (readWrite()) { 175 if (ownerAdapter.representsPersistent() && referencedAdapter.isTransient()) { 176 throw new IsisException("can't set a reference to a transient object from a persistent one: " + ownerAdapter.titleString() + " (persistent) -> " + referencedAdapter.titleString() + " (transient)"); 177 } 178 final CollectionAddToFacet facet = getFacet(CollectionAddToFacet.class); 179 facet.add(ownerAdapter, referencedAdapter); 180 } 181 } 182 183 @Override 184 public void removeElement(final ObjectAdapter ownerAdapter, final ObjectAdapter referencedAdapter) { 185 if (referencedAdapter == null) { 186 throw new IllegalArgumentException("element should not be null"); 187 } 188 if (readWrite()) { 189 final CollectionRemoveFromFacet facet = getFacet(CollectionRemoveFromFacet.class); 190 facet.remove(ownerAdapter, referencedAdapter); 191 } 192 } 193 194 public void removeAllAssociations(final ObjectAdapter ownerAdapter) { 195 final CollectionClearFacet facet = getFacet(CollectionClearFacet.class); 196 facet.clear(ownerAdapter); 197 } 198 199 @Override 200 public void clearCollection(final ObjectAdapter ownerAdapter) { 201 if (readWrite()) { 202 final CollectionClearFacet facet = getFacet(CollectionClearFacet.class); 203 facet.clear(ownerAdapter); 204 } 205 } 206 207 // ///////////////////////////////////////////////////////////// 208 // defaults 209 // ///////////////////////////////////////////////////////////// 210 211 @Override 212 public ObjectAdapter getDefault(final ObjectAdapter ownerAdapter) { 213 return null; 214 } 215 216 @Override 217 public void toDefault(final ObjectAdapter ownerAdapter) { 218 } 219 220 // ///////////////////////////////////////////////////////////// 221 // choices & autoComplete 222 // ///////////////////////////////////////////////////////////// 223 224 @Override 225 public ObjectAdapter[] getChoices(final ObjectAdapter ownerAdapter) { 226 return new ObjectAdapter[0]; 227 } 228 229 @Override 230 public boolean hasChoices() { 231 return false; 232 } 233 234 235 @Override 236 public boolean hasAutoComplete() { 237 return false; 238 } 239 240 @Override 241 public ObjectAdapter[] getAutoComplete(ObjectAdapter object, String searchArg) { 242 return new ObjectAdapter[0]; 243 } 244 245 @Override 246 public int getAutoCompleteMinLength() { 247 return 0; // n/a 248 } 249 250 // ///////////////////////////////////////////////////////////// 251 // getInstance 252 // ///////////////////////////////////////////////////////////// 253 254 @Override 255 public Instance getInstance(final ObjectAdapter adapter) { 256 final OneToManyAssociation specification = this; 257 return adapter.getInstance(specification); 258 } 259 260 // ///////////////////////////////////////////////////////////// 261 // debug, toString 262 // ///////////////////////////////////////////////////////////// 263 264 @Override 265 public String debugData() { 266 final DebugString debugString = new DebugString(); 267 debugString.indent(); 268 debugString.indent(); 269 getFacetedMethod().debugData(debugString); 270 return debugString.toString(); 271 } 272 273 @Override 274 public String toString() { 275 final ToString str = new ToString(this); 276 str.append(super.toString()); 277 str.append(","); 278 str.append("persisted", !isNotPersisted()); 279 str.append("type", getSpecification() == null ? "unknown" : getSpecification().getShortIdentifier()); 280 return str.toString(); 281 } 282 283 284}