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}