/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.isis.core.metamodel.specloader.specimpl;
import java.util.Collections;
import java.util.List;
import com.google.common.collect.Lists;
import org.apache.isis.applib.annotation.Where;
import org.apache.isis.applib.services.command.Command;
import org.apache.isis.core.commons.exceptions.IsisException;
import org.apache.isis.core.commons.util.ToString;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.consent.Consent;
import org.apache.isis.core.metamodel.consent.InteractionInitiatedBy;
import org.apache.isis.core.metamodel.consent.InteractionResult;
import org.apache.isis.core.metamodel.facetapi.FeatureType;
import org.apache.isis.core.metamodel.facets.FacetedMethod;
import org.apache.isis.core.metamodel.facets.actions.action.invocation.CommandUtil;
import org.apache.isis.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet;
import org.apache.isis.core.metamodel.facets.param.autocomplete.MinLengthUtil;
import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
import org.apache.isis.core.metamodel.facets.properties.autocomplete.PropertyAutoCompleteFacet;
import org.apache.isis.core.metamodel.facets.properties.choices.PropertyChoicesFacet;
import org.apache.isis.core.metamodel.facets.properties.defaults.PropertyDefaultFacet;
import org.apache.isis.core.metamodel.facets.properties.update.clear.PropertyClearFacet;
import org.apache.isis.core.metamodel.facets.properties.update.init.PropertyInitializationFacet;
import org.apache.isis.core.metamodel.facets.properties.update.modify.PropertySetterFacet;
import org.apache.isis.core.metamodel.interactions.InteractionUtils;
import org.apache.isis.core.metamodel.interactions.PropertyAccessContext;
import org.apache.isis.core.metamodel.interactions.PropertyModifyContext;
import org.apache.isis.core.metamodel.interactions.PropertyUsabilityContext;
import org.apache.isis.core.metamodel.interactions.PropertyVisibilityContext;
import org.apache.isis.core.metamodel.interactions.UsabilityContext;
import org.apache.isis.core.metamodel.interactions.ValidityContext;
import org.apache.isis.core.metamodel.interactions.VisibilityContext;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.services.command.CommandDtoServiceInternal;
import org.apache.isis.core.metamodel.spec.ObjectSpecification;
import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
import org.apache.isis.schema.cmd.v1.CommandDto;
public class OneToOneAssociationDefault extends ObjectAssociationAbstract implements OneToOneAssociation {
public OneToOneAssociationDefault(
final FacetedMethod facetedMethod,
final ServicesInjector servicesInjector) {
this(facetedMethod,
getSpecification(servicesInjector.getSpecificationLoader(), facetedMethod.getType()),
servicesInjector);
}
protected OneToOneAssociationDefault(
final FacetedMethod facetedMethod,
final ObjectSpecification objectSpec,
final ServicesInjector servicesInjector) {
super(facetedMethod, FeatureType.PROPERTY, objectSpec, servicesInjector);
}
//region > visible, usable
@Override
public VisibilityContext<?> createVisibleInteractionContext(
final ObjectAdapter ownerAdapter, final InteractionInitiatedBy interactionInitiatedBy,
Where where) {
return new PropertyVisibilityContext(ownerAdapter, getIdentifier(), interactionInitiatedBy, where);
}
@Override
public UsabilityContext<?> createUsableInteractionContext(
final ObjectAdapter ownerAdapter, final InteractionInitiatedBy interactionInitiatedBy,
Where where) {
return new PropertyUsabilityContext(ownerAdapter, getIdentifier(), interactionInitiatedBy, where);
}
//endregion
//region > Validity
private ValidityContext<?> createValidateInteractionContext(
final ObjectAdapter ownerAdapter,
final ObjectAdapter proposedToReferenceAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
return new PropertyModifyContext(ownerAdapter, getIdentifier(), proposedToReferenceAdapter,
interactionInitiatedBy);
}
@Override
public Consent isAssociationValid(
final ObjectAdapter ownerAdapter,
final ObjectAdapter proposedAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
return isAssociationValidResult(ownerAdapter, proposedAdapter, interactionInitiatedBy).createConsent();
}
private InteractionResult isAssociationValidResult(
final ObjectAdapter ownerAdapter,
final ObjectAdapter proposedToReferenceAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
final ValidityContext<?> validityContext =
createValidateInteractionContext(
ownerAdapter, proposedToReferenceAdapter, interactionInitiatedBy
);
return InteractionUtils.isValidResult(this, validityContext);
}
//endregion
//region > init
@Override
public void initAssociation(final ObjectAdapter ownerAdapter, final ObjectAdapter referencedAdapter) {
final PropertyInitializationFacet initializerFacet = getFacet(PropertyInitializationFacet.class);
if (initializerFacet != null) {
initializerFacet.initProperty(ownerAdapter, referencedAdapter);
}
}
//endregion
//region > Access (get, isEmpty)
@Override
public ObjectAdapter get(
final ObjectAdapter ownerAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
final PropertyOrCollectionAccessorFacet facet = getFacet(PropertyOrCollectionAccessorFacet.class);
final Object referencedPojo =
facet.getProperty(ownerAdapter, interactionInitiatedBy);
if (referencedPojo == null) {
return null;
}
return getPersistenceSessionService().adapterFor(referencedPojo);
}
// REVIEW: UNUSED
private PropertyAccessContext createAccessInteractionContext(
final ObjectAdapter ownerAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
final ObjectAdapter referencedAdapter = get(ownerAdapter, interactionInitiatedBy);
return new PropertyAccessContext(
ownerAdapter, getIdentifier(), referencedAdapter, interactionInitiatedBy
);
}
@Override
public boolean isEmpty(final ObjectAdapter ownerAdapter, final InteractionInitiatedBy interactionInitiatedBy) {
return get(ownerAdapter, interactionInitiatedBy) == null;
}
//endregion
//region > Set
@Override
public void set(
final ObjectAdapter ownerAdapter,
final ObjectAdapter newReferencedAdapter) {
set(ownerAdapter, newReferencedAdapter, InteractionInitiatedBy.USER);
}
/**
* Sets up the {@link Command}, then delegates to the appropriate facet
* ({@link PropertySetterFacet} or {@link PropertyClearFacet}).
*/
@Override
public void set(
final ObjectAdapter ownerAdapter,
final ObjectAdapter newReferencedAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
setupCommand(ownerAdapter, newReferencedAdapter);
setInternal(ownerAdapter, newReferencedAdapter, interactionInitiatedBy);
}
private void setInternal(
final ObjectAdapter ownerAdapter,
final ObjectAdapter newReferencedAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
if (newReferencedAdapter != null) {
setValue(ownerAdapter, newReferencedAdapter, interactionInitiatedBy);
} else {
clearValue(ownerAdapter, interactionInitiatedBy);
}
}
private void setValue(
final ObjectAdapter ownerAdapter,
final ObjectAdapter newReferencedAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
final PropertySetterFacet setterFacet = getFacet(PropertySetterFacet.class);
if (setterFacet == null) {
return;
}
if ( ownerAdapter.representsPersistent() &&
newReferencedAdapter != null &&
newReferencedAdapter.isTransient() &&
!newReferencedAdapter.getSpecification().isParented()) {
// TODO: I've never seen this exception, and in any case DataNucleus supports persistence-by-reachability; so probably not required
throw new IsisException("can't set a reference to a transient object from a persistent one: " + newReferencedAdapter.titleString(null) + " (transient)");
}
setterFacet.setProperty(this, ownerAdapter, newReferencedAdapter, interactionInitiatedBy);
}
private void clearValue(
final ObjectAdapter ownerAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
final PropertyClearFacet facet = getFacet(PropertyClearFacet.class);
facet.clearProperty(this, ownerAdapter, interactionInitiatedBy);
}
//endregion
//region > defaults
@Override
public ObjectAdapter getDefault(final ObjectAdapter ownerAdapter) {
PropertyDefaultFacet propertyDefaultFacet = getFacet(PropertyDefaultFacet.class);
// if no default on the association, attempt to find a default on the
// specification (eg an int should
// default to 0).
if (propertyDefaultFacet == null || propertyDefaultFacet.isNoop()) {
propertyDefaultFacet = this.getSpecification().getFacet(PropertyDefaultFacet.class);
}
if (propertyDefaultFacet == null) {
return null;
}
return propertyDefaultFacet.getDefault(ownerAdapter);
}
@Override
public void toDefault(final ObjectAdapter ownerAdapter) {
// don't default optional fields
final MandatoryFacet mandatoryFacet = getFacet(MandatoryFacet.class);
if (mandatoryFacet != null && mandatoryFacet.isInvertedSemantics()) {
return;
}
final ObjectAdapter defaultValue = getDefault(ownerAdapter);
if (defaultValue != null) {
initAssociation(ownerAdapter, defaultValue);
}
}
//endregion
//region > choices and autoComplete
@Override
public boolean hasChoices() {
return getFacet(PropertyChoicesFacet.class) != null;
}
@Override
public ObjectAdapter[] getChoices(
final ObjectAdapter ownerAdapter,
final InteractionInitiatedBy interactionInitiatedBy) {
final PropertyChoicesFacet propertyChoicesFacet = getFacet(PropertyChoicesFacet.class);
if (propertyChoicesFacet == null) {
return null;
}
final Object[] pojoOptions = propertyChoicesFacet.getChoices(
ownerAdapter,
getSpecificationLoader(),
interactionInitiatedBy);
List<ObjectAdapter> adapters = Lists.transform(
Lists.newArrayList(pojoOptions), ObjectAdapter.Functions.adapterForUsing(getPersistenceSessionService()));
return adapters.toArray(new ObjectAdapter[]{});
}
@Override
public boolean hasAutoComplete() {
final PropertyAutoCompleteFacet propertyAutoCompleteFacet = getFacet(PropertyAutoCompleteFacet.class);
return propertyAutoCompleteFacet != null;
}
@Override
public ObjectAdapter[] getAutoComplete(
final ObjectAdapter ownerAdapter,
final String searchArg,
final InteractionInitiatedBy interactionInitiatedBy) {
final PropertyAutoCompleteFacet propertyAutoCompleteFacet = getFacet(PropertyAutoCompleteFacet.class);
final Object[] pojoOptions = propertyAutoCompleteFacet.autoComplete(ownerAdapter, searchArg,
interactionInitiatedBy);
if (pojoOptions != null) {
final ObjectAdapter[] options = new ObjectAdapter[pojoOptions.length];
for (int i = 0; i < options.length; i++) {
options[i] = getPersistenceSessionService().adapterFor(pojoOptions[i]);
}
return options;
}
return null;
}
@Override
public int getAutoCompleteMinLength() {
final PropertyAutoCompleteFacet propertyAutoCompleteFacet = getFacet(PropertyAutoCompleteFacet.class);
return propertyAutoCompleteFacet != null? propertyAutoCompleteFacet.getMinLength(): MinLengthUtil.MIN_LENGTH_DEFAULT;
}
//endregion
/**
* Internal API
*/
public void setupCommand(
final ObjectAdapter targetAdapter,
final ObjectAdapter valueAdapterOrNull) {
setupCommandTarget(targetAdapter, valueAdapterOrNull);
setupCommandMemberIdentifier();
setupCommandMementoAndExecutionContext(targetAdapter, valueAdapterOrNull);
}
private void setupCommandTarget(
final ObjectAdapter targetAdapter,
final ObjectAdapter valueAdapter) {
final String arguments = CommandUtil.argDescriptionFor(valueAdapter);
setupCommandTarget(targetAdapter, arguments);
}
private void setupCommandMementoAndExecutionContext(
final ObjectAdapter targetAdapter,
final ObjectAdapter valueAdapterOrNull) {
final CommandDtoServiceInternal commandDtoServiceInternal = getCommandDtoService();
final CommandDto dto = commandDtoServiceInternal.asCommandDto(
Collections.singletonList(targetAdapter), this, valueAdapterOrNull);
setupCommandDtoAndExecutionContext(dto);
}
//region > toString
@Override
public String toString() {
final ToString str = new ToString(this);
str.append(super.toString());
str.setAddComma();
str.append("persisted", !isNotPersisted());
str.append("type", getSpecification().getShortIdentifier());
return str.toString();
}
//endregion
}