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.annotation.Where;
027import org.apache.isis.applib.query.Query;
028import org.apache.isis.applib.query.QueryFindAllInstances;
029import org.apache.isis.core.commons.authentication.AuthenticationSession;
030import org.apache.isis.core.commons.debug.DebugString;
031import org.apache.isis.core.commons.exceptions.IsisException;
032import org.apache.isis.core.commons.util.ToString;
033import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
034import org.apache.isis.core.metamodel.consent.Consent;
035import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod;
036import org.apache.isis.core.metamodel.consent.InteractionResult;
037import org.apache.isis.core.metamodel.facetapi.FeatureType;
038import org.apache.isis.core.metamodel.facets.FacetedMethod;
039import org.apache.isis.core.metamodel.facets.accessor.PropertyOrCollectionAccessorFacet;
040import org.apache.isis.core.metamodel.facets.mandatory.MandatoryFacet;
041import org.apache.isis.core.metamodel.facets.object.bounded.ChoicesFacetUtils;
042import org.apache.isis.core.metamodel.facets.properties.autocomplete.PropertyAutoCompleteFacet;
043import org.apache.isis.core.metamodel.facets.properties.choices.PropertyChoicesFacet;
044import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet;
045import org.apache.isis.core.metamodel.facets.properties.modify.PropertyClearFacet;
046import org.apache.isis.core.metamodel.facets.properties.modify.PropertyInitializationFacet;
047import org.apache.isis.core.metamodel.facets.properties.modify.PropertySetterFacet;
048import org.apache.isis.core.metamodel.interactions.InteractionUtils;
049import org.apache.isis.core.metamodel.interactions.PropertyAccessContext;
050import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
051import org.apache.isis.core.metamodel.interactions.PropertyUsabilityContext;
052import org.apache.isis.core.metamodel.interactions.PropertyVisibilityContext;
053import org.apache.isis.core.metamodel.interactions.UsabilityContext;
054import org.apache.isis.core.metamodel.interactions.ValidityContext;
055import org.apache.isis.core.metamodel.interactions.VisibilityContext;
056import org.apache.isis.core.metamodel.spec.Instance;
057import org.apache.isis.core.metamodel.spec.ObjectSpecification;
058import org.apache.isis.core.metamodel.spec.feature.ObjectMemberContext;
059import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
060import org.apache.isis.core.progmodel.facets.param.autocomplete.MinLengthUtil;
061
062public class OneToOneAssociationImpl extends ObjectAssociationAbstract implements OneToOneAssociation {
063
064    public OneToOneAssociationImpl(final FacetedMethod facetedMethod, final ObjectMemberContext objectMemberContext) {
065        this(facetedMethod, getSpecification(objectMemberContext.getSpecificationLookup(), facetedMethod.getType()), objectMemberContext);
066    }
067    
068    protected OneToOneAssociationImpl(final FacetedMethod facetedMethod, final ObjectSpecification objectSpec, final ObjectMemberContext objectMemberContext) {
069        super(facetedMethod, FeatureType.PROPERTY, objectSpec, objectMemberContext);
070    }
071
072    // /////////////////////////////////////////////////////////////
073    // Hidden (or visible)
074    // /////////////////////////////////////////////////////////////
075
076    @Override
077    public VisibilityContext<?> createVisibleInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter ownerAdapter, Where where) {
078        return new PropertyVisibilityContext(getDeploymentCategory(), session, invocationMethod, ownerAdapter, getIdentifier(), where);
079    }
080
081    // /////////////////////////////////////////////////////////////
082    // Disabled (or enabled)
083    // /////////////////////////////////////////////////////////////
084
085    @Override
086    public UsabilityContext<?> createUsableInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod invocationMethod, final ObjectAdapter ownerAdapter, Where where) {
087        return new PropertyUsabilityContext(getDeploymentCategory(), session, invocationMethod, ownerAdapter, getIdentifier(), where);
088    }
089
090    // /////////////////////////////////////////////////////////////
091    // Validate
092    // /////////////////////////////////////////////////////////////
093
094    @Override
095    public ValidityContext<?> createValidateInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod interactionMethod, final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToReferenceAdapter) {
096        return new PropertyModifyContext(getDeploymentCategory(), session, interactionMethod, ownerAdapter, getIdentifier(), proposedToReferenceAdapter);
097    }
098
099    /**
100     * TODO: currently this method is hard-coded to assume all interactions are
101     * initiated {@link InteractionInvocationMethod#BY_USER by user}.
102     */
103    @Override
104    public Consent isAssociationValid(final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToReferenceAdapter) {
105        return isAssociationValidResult(ownerAdapter, proposedToReferenceAdapter).createConsent();
106    }
107
108    private InteractionResult isAssociationValidResult(final ObjectAdapter ownerAdapter, final ObjectAdapter proposedToReferenceAdapter) {
109        final ValidityContext<?> validityContext = createValidateInteractionContext(getAuthenticationSession(), InteractionInvocationMethod.BY_USER, ownerAdapter, proposedToReferenceAdapter);
110        return InteractionUtils.isValidResult(this, validityContext);
111    }
112
113    // /////////////////////////////////////////////////////////////
114    // init
115    // /////////////////////////////////////////////////////////////
116
117    @Override
118    public void initAssociation(final ObjectAdapter ownerAdapter, final ObjectAdapter referencedAdapter) {
119        final PropertyInitializationFacet initializerFacet = getFacet(PropertyInitializationFacet.class);
120        if (initializerFacet != null) {
121            initializerFacet.initProperty(ownerAdapter, referencedAdapter);
122        }
123    }
124
125    // /////////////////////////////////////////////////////////////
126    // Access (get, isEmpty)
127    // /////////////////////////////////////////////////////////////
128
129    @Override
130    public ObjectAdapter get(final ObjectAdapter ownerAdapter) {
131        final PropertyOrCollectionAccessorFacet facet = getFacet(PropertyOrCollectionAccessorFacet.class);
132        final Object referencedPojo = facet.getProperty(ownerAdapter);
133
134        if (referencedPojo == null) {
135            return null;
136        }
137
138        return getAdapterManager().adapterFor(referencedPojo, ownerAdapter);
139    }
140
141    /**
142     * TODO: currently this method is hard-coded to assume all interactions are
143     * initiated {@link InteractionInvocationMethod#BY_USER by user}.
144     */
145    @Override
146    public PropertyAccessContext createAccessInteractionContext(final AuthenticationSession session, final InteractionInvocationMethod interactionMethod, final ObjectAdapter ownerAdapter) {
147        return new PropertyAccessContext(getDeploymentCategory(), session, InteractionInvocationMethod.BY_USER, ownerAdapter, getIdentifier(), get(ownerAdapter));
148    }
149
150    @Override
151    public boolean isEmpty(final ObjectAdapter ownerAdapter) {
152        return get(ownerAdapter) == null;
153    }
154
155    // /////////////////////////////////////////////////////////////
156    // Set
157    // /////////////////////////////////////////////////////////////
158
159    @Override
160    public void set(final ObjectAdapter ownerAdapter, final ObjectAdapter newReferencedAdapter) {
161        if (newReferencedAdapter != null) {
162            setValue(ownerAdapter, newReferencedAdapter);
163        } else {
164            clearValue(ownerAdapter);
165        }
166    }
167
168    /**
169     * @see #set(ObjectAdapter, ObjectAdapter)
170     */
171    @Deprecated
172    @Override
173    public void setAssociation(final ObjectAdapter ownerAdapter, final ObjectAdapter newReferencedAdapter) {
174        setValue(ownerAdapter, newReferencedAdapter);
175    }
176
177    private void setValue(final ObjectAdapter ownerAdapter, final ObjectAdapter newReferencedAdapter) {
178        final PropertySetterFacet setterFacet = getFacet(PropertySetterFacet.class);
179        if (setterFacet == null) {
180            return;
181        }
182        if (ownerAdapter.representsPersistent() && newReferencedAdapter != null && newReferencedAdapter.isTransient() && !newReferencedAdapter.getSpecification().isParented()) {
183            // TODO: move to facet ?
184            throw new IsisException("can't set a reference to a transient object from a persistent one: " + newReferencedAdapter.titleString() + " (transient)");
185        }
186        setterFacet.setProperty(ownerAdapter, newReferencedAdapter);
187    }
188
189    /**
190     * @see #set(ObjectAdapter, ObjectAdapter)
191     */
192    @Deprecated
193    @Override
194    public void clearAssociation(final ObjectAdapter ownerAdapter) {
195        clearValue(ownerAdapter);
196    }
197
198    private void clearValue(final ObjectAdapter ownerAdapter) {
199        final PropertyClearFacet facet = getFacet(PropertyClearFacet.class);
200        facet.clearProperty(ownerAdapter);
201    }
202
203    // /////////////////////////////////////////////////////////////
204    // defaults
205    // /////////////////////////////////////////////////////////////
206
207    @Override
208    public ObjectAdapter getDefault(final ObjectAdapter ownerAdapter) {
209        PropertyDefaultFacet propertyDefaultFacet = getFacet(PropertyDefaultFacet.class);
210        // if no default on the association, attempt to find a default on the
211        // specification (eg an int should
212        // default to 0).
213        if (propertyDefaultFacet == null || propertyDefaultFacet.isNoop()) {
214            propertyDefaultFacet = this.getSpecification().getFacet(PropertyDefaultFacet.class);
215        }
216        if (propertyDefaultFacet == null) {
217            return null;
218        }
219        return propertyDefaultFacet.getDefault(ownerAdapter);
220    }
221
222    @Override
223    public void toDefault(final ObjectAdapter ownerAdapter) {
224        // don't default optional fields
225        final MandatoryFacet mandatoryFacet = getFacet(MandatoryFacet.class);
226        if (mandatoryFacet != null && mandatoryFacet.isInvertedSemantics()) {
227            return;
228        }
229
230        final ObjectAdapter defaultValue = getDefault(ownerAdapter);
231        if (defaultValue != null) {
232            initAssociation(ownerAdapter, defaultValue);
233        }
234    }
235
236    // /////////////////////////////////////////////////////////////
237    // choices and autoComplete
238    // /////////////////////////////////////////////////////////////
239
240    @Override
241    public boolean hasChoices() {
242        return getFacet(PropertyChoicesFacet.class) != null;
243    }
244
245    @Override
246    public ObjectAdapter[] getChoices(final ObjectAdapter ownerAdapter) {
247        final PropertyChoicesFacet propertyChoicesFacet = getFacet(PropertyChoicesFacet.class);
248        final Object[] pojoOptions = propertyChoicesFacet == null ? null : propertyChoicesFacet.getChoices(ownerAdapter, getSpecificationLookup());
249        if (pojoOptions != null) {
250            List<ObjectAdapter> adapters = Lists.transform(
251                    Lists.newArrayList(pojoOptions), ObjectAdapter.Functions.adapterForUsing(getAdapterManager()));
252            return adapters.toArray(new ObjectAdapter[]{});
253        } 
254        // // now incorporated into above choices processing (BoundedFacet is no more)
255        /* else if (BoundedFacetUtils.isBoundedSet(getSpecification())) {
256            return options();
257        } */
258        return null;
259    }
260
261    private <T> ObjectAdapter[] options() {
262        final Query<T> query = new QueryFindAllInstances<T>(getSpecification().getFullIdentifier());
263        final List<ObjectAdapter> allInstancesAdapter = getQuerySubmitter().allMatchingQuery(query);
264        final ObjectAdapter[] options = new ObjectAdapter[allInstancesAdapter.size()];
265        int j = 0;
266        for (final ObjectAdapter adapter : allInstancesAdapter) {
267            options[j++] = adapter;
268        }
269        return options;
270    }
271
272    
273    @Override
274    public boolean hasAutoComplete() {
275        final PropertyAutoCompleteFacet propertyAutoCompleteFacet = getFacet(PropertyAutoCompleteFacet.class);
276        return propertyAutoCompleteFacet != null;
277    }
278
279    @Override
280    public ObjectAdapter[] getAutoComplete(ObjectAdapter ownerAdapter, String searchArg) {
281        final PropertyAutoCompleteFacet propertyAutoCompleteFacet = getFacet(PropertyAutoCompleteFacet.class);
282        final Object[] pojoOptions = propertyAutoCompleteFacet.autoComplete(ownerAdapter, searchArg);
283        if (pojoOptions != null) {
284            final ObjectAdapter[] options = new ObjectAdapter[pojoOptions.length];
285            for (int i = 0; i < options.length; i++) {
286                options[i] = getAdapterManager().adapterFor(pojoOptions[i]);
287            }
288            return options;
289        }
290        return null;
291    }
292
293    @Override
294    public int getAutoCompleteMinLength() {
295        final PropertyAutoCompleteFacet propertyAutoCompleteFacet = getFacet(PropertyAutoCompleteFacet.class);
296        return propertyAutoCompleteFacet != null? propertyAutoCompleteFacet.getMinLength(): MinLengthUtil.MIN_LENGTH_DEFAULT;
297    }
298
299
300    // /////////////////////////////////////////////////////////////
301    // getInstance
302    // /////////////////////////////////////////////////////////////
303
304    @Override
305    public Instance getInstance(final ObjectAdapter ownerAdapter) {
306        final OneToOneAssociation specification = this;
307        return ownerAdapter.getInstance(specification);
308    }
309
310    // /////////////////////////////////////////////////////////////
311    // debug, toString
312    // /////////////////////////////////////////////////////////////
313
314    @Override
315    public String debugData() {
316        final DebugString debugString = new DebugString();
317        debugString.indent();
318        debugString.indent();
319        getFacetedMethod().debugData(debugString);
320        return debugString.toString();
321    }
322
323    @Override
324    public String toString() {
325        final ToString str = new ToString(this);
326        str.append(super.toString());
327        str.setAddComma();
328        str.append("persisted", !isNotPersisted());
329        str.append("type", getSpecification().getShortIdentifier());
330        return str.toString();
331    }
332
333
334}