/*
* 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.facets.properties.property;
import org.apache.isis.applib.annotation.*;
import org.apache.isis.applib.services.HasTransactionId;
import org.apache.isis.applib.services.eventbus.PropertyChangedEvent;
import org.apache.isis.applib.services.eventbus.PropertyDomainEvent;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.metamodel.facetapi.*;
import org.apache.isis.core.metamodel.facets.Annotations;
import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
import org.apache.isis.core.metamodel.facets.FacetedMethod;
import org.apache.isis.core.metamodel.facets.actions.command.CommandFacet;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
import org.apache.isis.core.metamodel.facets.objectvalue.fileaccept.FileAcceptFacet;
import org.apache.isis.core.metamodel.facets.objectvalue.mandatory.MandatoryFacet;
import org.apache.isis.core.metamodel.facets.objectvalue.maxlen.MaxLengthFacet;
import org.apache.isis.core.metamodel.facets.objectvalue.regex.RegExFacet;
import org.apache.isis.core.metamodel.facets.objectvalue.regex.TitleFacetFormattedByRegex;
import org.apache.isis.core.metamodel.facets.propcoll.accessor.PropertyOrCollectionAccessorFacet;
import org.apache.isis.core.metamodel.facets.propcoll.notpersisted.NotPersistedFacet;
import org.apache.isis.core.metamodel.facets.properties.property.command.CommandFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.disabled.DisabledFacetForDisabledAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.disabled.DisabledFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.fileaccept.FileAcceptFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.hidden.HiddenFacetForHiddenAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.hidden.HiddenFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.mandatory.MandatoryFacetForMandatoryAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.mandatory.MandatoryFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.mandatory.MandatoryFacetInvertedByNullableAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.mandatory.MandatoryFacetInvertedByOptionalAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.maxlength.MaxLengthFacetForMaxLengthAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.maxlength.MaxLengthFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.modify.*;
import org.apache.isis.core.metamodel.facets.properties.property.mustsatisfy.MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.mustsatisfy.MustSatisfySpecificationFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.notpersisted.NotPersistedFacetForNotPersistedAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.property.notpersisted.NotPersistedFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.publishing.PublishedPropertyFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.regex.RegExFacetForPropertyAnnotation;
import org.apache.isis.core.metamodel.facets.properties.property.regex.RegExFacetForRegExAnnotationOnProperty;
import org.apache.isis.core.metamodel.facets.properties.publish.PublishedPropertyFacet;
import org.apache.isis.core.metamodel.facets.properties.update.clear.PropertyClearFacet;
import org.apache.isis.core.metamodel.facets.properties.update.modify.PropertySetterFacet;
import org.apache.isis.core.metamodel.services.ServicesInjector;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForConflictingOptionality;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForDeprecatedAnnotation;
import org.apache.isis.core.metamodel.util.EventUtil;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
public class PropertyAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
private final MetaModelValidatorForDeprecatedAnnotation postsPropertyChangedEventValidator = new MetaModelValidatorForDeprecatedAnnotation(PostsPropertyChangedEvent.class);
private final MetaModelValidatorForDeprecatedAnnotation propertyInteractionValidator = new MetaModelValidatorForDeprecatedAnnotation(PropertyInteraction.class);
private final MetaModelValidatorForDeprecatedAnnotation regexValidator = new MetaModelValidatorForDeprecatedAnnotation(RegEx.class);
private final MetaModelValidatorForDeprecatedAnnotation optionalValidator = new MetaModelValidatorForDeprecatedAnnotation(Optional.class);
private final MetaModelValidatorForDeprecatedAnnotation mandatoryValidator = new MetaModelValidatorForDeprecatedAnnotation(Mandatory.class);
private final MetaModelValidatorForDeprecatedAnnotation hiddenValidator = new MetaModelValidatorForDeprecatedAnnotation(Hidden.class);
private final MetaModelValidatorForDeprecatedAnnotation disabledValidator = new MetaModelValidatorForDeprecatedAnnotation(Disabled.class);
private final MetaModelValidatorForDeprecatedAnnotation maxLengthValidator = new MetaModelValidatorForDeprecatedAnnotation(MaxLength.class);
private final MetaModelValidatorForDeprecatedAnnotation mustSatisfyValidator = new MetaModelValidatorForDeprecatedAnnotation(MustSatisfy.class);
private final MetaModelValidatorForDeprecatedAnnotation notPersistedValidator = new MetaModelValidatorForDeprecatedAnnotation(NotPersisted.class);
private final MetaModelValidatorForConflictingOptionality conflictingOptionalityValidator = new MetaModelValidatorForConflictingOptionality();
public PropertyAnnotationFacetFactory() {
super(FeatureType.PROPERTIES_AND_ACTIONS);
}
@Override
public void process(final ProcessMethodContext processMethodContext) {
processModify(processMethodContext);
processHidden(processMethodContext);
processEditing(processMethodContext);
processCommand(processMethodContext);
processPublishing(processMethodContext);
processMaxLength(processMethodContext);
processMustSatisfy(processMethodContext);
processNotPersisted(processMethodContext);
processOptional(processMethodContext);
processRegEx(processMethodContext);
processFileAccept(processMethodContext);
}
void processModify(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetedMethod holder = processMethodContext.getFacetHolder();
final PropertyOrCollectionAccessorFacet getterFacet = holder.getFacet(PropertyOrCollectionAccessorFacet.class);
if(getterFacet == null) {
return;
}
//
// Set up PropertyDomainEventFacet, which will act as the hiding/disabling/validating advisor
//
final PostsPropertyChangedEvent postsPropertyChangedEvent = Annotations.getAnnotation(method, PostsPropertyChangedEvent.class);
final PropertyInteraction propertyInteraction = Annotations.getAnnotation(method, PropertyInteraction.class);
final Property property = Annotations.getAnnotation(method, Property.class);
final Class<? extends PropertyDomainEvent<?, ?>> propertyDomainEventType;
final PropertyDomainEventFacetAbstract propertyDomainEventFacet;
// can't really do this, because would result in the event being fired for the
// hidden/disable/validate phases, most likely breaking existing code.
// if(postsPropertyChangedEvent != null) {
// propertyDomainEventType = postsPropertyChangedEvent.value();
// propertyDomainEventFacet = postsPropertyChangedEventValidator.flagIfPresent(
// new PropertyDomainEventFacetForPostsPropertyChangedEventAnnotation(
// propertyDomainEventType, getterFacet, servicesInjector, getSpecificationLoader(), holder));
// } else
// search for @PropertyInteraction(value=...)
if(propertyInteraction != null) {
propertyDomainEventType = propertyInteraction.value();
propertyDomainEventFacet = propertyInteractionValidator.flagIfPresent(
new PropertyDomainEventFacetForPropertyInteractionAnnotation(
propertyDomainEventType, getterFacet, servicesInjector, getSpecificationLoader(), holder), processMethodContext);
} else
// search for @Property(domainEvent=...)
if(property != null && property.domainEvent() != null) {
propertyDomainEventType = property.domainEvent();
propertyDomainEventFacet = new PropertyDomainEventFacetForPropertyAnnotation(
propertyDomainEventType, getterFacet, servicesInjector, getSpecificationLoader(), holder);
} else
// else use default event type
{
propertyDomainEventType = PropertyDomainEvent.Default.class;
propertyDomainEventFacet = new PropertyDomainEventFacetDefault(
propertyDomainEventType, getterFacet, servicesInjector, getSpecificationLoader(), holder);
}
if(EventUtil.eventTypeIsPostable(
propertyDomainEventFacet.getEventType(),
PropertyDomainEvent.Noop.class,
PropertyDomainEvent.Default.class,
"isis.reflector.facet.propertyAnnotation.domainEvent.postForDefault",
getConfiguration())) {
FacetUtil.addFacet(propertyDomainEventFacet);
}
//
// if the property is mutable, then replace the current setter and clear facets with equivalents that
// emit the appropriate domain event and then delegate onto the underlying
//
final PropertySetterFacet setterFacet = holder.getFacet(PropertySetterFacet.class);
if(setterFacet != null) {
// the current setter facet will end up as the underlying facet
final PropertySetterFacet replacementFacet;
// deprecated
if(postsPropertyChangedEvent != null) {
final Class<? extends PropertyChangedEvent<?, ?>> propertySetEventType = postsPropertyChangedEvent.value();
replacementFacet = new PropertySetterFacetForPostsPropertyChangedEventAnnotation(
propertySetEventType, getterFacet, setterFacet, propertyDomainEventFacet, holder, servicesInjector);
} else
// deprecated (but more recently)
if(propertyInteraction != null) {
replacementFacet = new PropertySetterFacetForDomainEventFromPropertyInteractionAnnotation(
propertyDomainEventType, getterFacet, setterFacet, propertyDomainEventFacet, holder, servicesInjector);
} else
// current
if(property != null) {
replacementFacet = new PropertySetterFacetForDomainEventFromPropertyAnnotation(
propertyDomainEventType, getterFacet, setterFacet, propertyDomainEventFacet, holder, servicesInjector);
} else
// default
{
replacementFacet = new PropertySetterFacetForDomainEventFromDefault(
propertyDomainEventType, getterFacet, setterFacet, propertyDomainEventFacet, holder, servicesInjector);
}
FacetUtil.addFacet(replacementFacet);
}
final PropertyClearFacet clearFacet = holder.getFacet(PropertyClearFacet.class);
if(clearFacet != null) {
// the current clear facet will end up as the underlying facet
final PropertyClearFacet replacementFacet;
// deprecated
if(postsPropertyChangedEvent != null) {
final Class<? extends PropertyChangedEvent<?, ?>> propertyClearEventType = postsPropertyChangedEvent.value();
replacementFacet = new PropertyClearFacetForPostsPropertyChangedEventAnnotation(
propertyClearEventType, getterFacet, clearFacet, propertyDomainEventFacet, holder, servicesInjector);
} else
// deprecated (but more recently)
if(propertyInteraction != null) {
replacementFacet = new PropertyClearFacetForDomainEventFromPropertyInteractionAnnotation(
propertyDomainEventType, getterFacet, clearFacet, propertyDomainEventFacet, holder, servicesInjector);
} else
// current
if(property != null) {
replacementFacet = new PropertyClearFacetForDomainEventFromPropertyAnnotation(
propertyDomainEventType, getterFacet, clearFacet, propertyDomainEventFacet, holder, servicesInjector);
} else
// default
{
replacementFacet = new PropertyClearFacetForDomainEventFromDefault(
propertyDomainEventType, getterFacet, clearFacet, propertyDomainEventFacet, holder, servicesInjector);
}
FacetUtil.addFacet(replacementFacet);
}
}
void processHidden(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @Hidden first
final Hidden hiddenAnnotation = Annotations.getAnnotation(processMethodContext.getMethod(), Hidden.class);
HiddenFacet facet = hiddenValidator.flagIfPresent(HiddenFacetForHiddenAnnotationOnProperty.create(hiddenAnnotation, holder), processMethodContext);
// else search for @Property(hidden=...)
final Property property = Annotations.getAnnotation(method, Property.class);
if(facet == null) {
facet = HiddenFacetForPropertyAnnotation.create(property, holder);
}
FacetUtil.addFacet(facet);
}
void processEditing(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @Disabled first
final Disabled annotation = Annotations.getAnnotation(method, Disabled.class);
final DisabledFacet disabledFacet = DisabledFacetForDisabledAnnotationOnProperty.create(annotation, holder);
DisabledFacet facet = disabledValidator.flagIfPresent(disabledFacet, processMethodContext);
// else search for @Property(editing=...)
final Property property = Annotations.getAnnotation(method, Property.class);
if(facet == null) {
facet = DisabledFacetForPropertyAnnotation.create(property, holder);
}
FacetUtil.addFacet(facet);
}
void processCommand(final ProcessMethodContext processMethodContext) {
final Class<?> cls = processMethodContext.getCls();
final Method method = processMethodContext.getMethod();
final Property property = Annotations.getAnnotation(method, Property.class);
final FacetedMethod facetHolder = processMethodContext.getFacetHolder();
final FacetHolder holder = facetHolder;
//
// this rule inspired by a similar rule for auditing and publishing, see DomainObjectAnnotationFacetFactory
//
if(HasTransactionId.class.isAssignableFrom(processMethodContext.getCls())) {
// do not install on any implementation of HasTransactionId
// (ie commands, audit entries, published events).
return;
}
// check for @Property(command=...)
final CommandFacet commandFacet = CommandFacetForPropertyAnnotation.create(property, getConfiguration(), holder);
FacetUtil.addFacet(commandFacet);
}
void processPublishing(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final Property property = Annotations.getAnnotation(method, Property.class);
final FacetHolder holder = processMethodContext.getFacetHolder();
//
// this rule inspired by a similar rule for auditing and publishing, see DomainObjectAnnotationFacetFactory
// and for commands, see above
//
if(HasTransactionId.class.isAssignableFrom(processMethodContext.getCls())) {
// do not install on any implementation of HasTransactionId
// (ie commands, audit entries, published events).
return;
}
// check for @Property(publishing=...)
final PublishedPropertyFacet facet = PublishedPropertyFacetForPropertyAnnotation
.create(property, getConfiguration(), holder);
FacetUtil.addFacet(facet);
}
void processMaxLength(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @MaxLength first
final MaxLength annotation = Annotations.getAnnotation(method, MaxLength.class);
MaxLengthFacet facet = maxLengthValidator.flagIfPresent(MaxLengthFacetForMaxLengthAnnotationOnProperty.create(annotation, holder), processMethodContext);
// else search for @Property(maxLength=...)
final Property property = Annotations.getAnnotation(method, Property.class);
if(facet == null) {
facet = MaxLengthFacetForPropertyAnnotation.create(property, holder);
}
FacetUtil.addFacet(facet);
}
void processMustSatisfy(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @MustSatisfy first
final MustSatisfy annotation = Annotations.getAnnotation(method, MustSatisfy.class);
Facet facet = mustSatisfyValidator.flagIfPresent(MustSatisfySpecificationFacetForMustSatisfyAnnotationOnProperty.create(annotation, holder, servicesInjector), processMethodContext);
// else search for @Property(mustSatisfy=...)
final Property property = Annotations.getAnnotation(method, Property.class);
if(facet == null) {
facet = MustSatisfySpecificationFacetForPropertyAnnotation.create(property, holder, servicesInjector);
}
FacetUtil.addFacet(facet);
}
void processNotPersisted(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @NotPersisted first
final NotPersisted annotation = Annotations.getAnnotation(method, NotPersisted.class);
NotPersistedFacet facet = notPersistedValidator.flagIfPresent(NotPersistedFacetForNotPersistedAnnotationOnProperty.create(annotation, holder), processMethodContext);
// else search for @Property(notPersisted=...)
final Property property = Annotations.getAnnotation(method, Property.class);
if(facet == null) {
facet = NotPersistedFacetForPropertyAnnotation.create(property, holder);
}
FacetUtil.addFacet(facet);
}
void processOptional(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @Optional
final Optional optionalAnnotation = Annotations.getAnnotation(method, Optional.class);
FacetUtil.addFacet(
optionalValidator.flagIfPresent(
MandatoryFacetInvertedByOptionalAnnotationOnProperty.create(optionalAnnotation, method, holder),
processMethodContext));
// check for deprecated @Mandatory
final Mandatory mandatoryAnnotation = Annotations.getAnnotation(method, Mandatory.class);
final MandatoryFacet facet =
mandatoryValidator.flagIfPresent(
MandatoryFacetForMandatoryAnnotationOnProperty.create(mandatoryAnnotation, holder),
processMethodContext);
FacetUtil.addFacet(facet);
conflictingOptionalityValidator.flagIfConflict(
facet, "Conflicting @Mandatory with other optionality annotation");
// else check for @Nullable
final Nullable nullableAnnotation = Annotations.getAnnotation(method, Nullable.class);
final MandatoryFacet facet2 =
MandatoryFacetInvertedByNullableAnnotationOnProperty.create(nullableAnnotation, method, holder);
FacetUtil.addFacet(facet2);
conflictingOptionalityValidator.flagIfConflict(
facet2, "Conflicting @Nullable with other optionality annotation");
// else search for @Property(optional=...)
final Property property = Annotations.getAnnotation(method, Property.class);
final MandatoryFacet facet3 = MandatoryFacetForPropertyAnnotation.create(property, method, holder);
FacetUtil.addFacet(facet3);
conflictingOptionalityValidator.flagIfConflict(
facet3, "Conflicting Property#optionality with other optionality annotation");
}
void processRegEx(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
final Class<?> returnType = processMethodContext.getMethod().getReturnType();
// check for deprecated @RegEx first
final RegEx annotation = Annotations.getAnnotation(processMethodContext.getMethod(), RegEx.class);
RegExFacet facet = regexValidator.flagIfPresent(RegExFacetForRegExAnnotationOnProperty.create(annotation, returnType, holder), processMethodContext);
if (facet != null) {
// @RegEx also supports corresponding title facet
FacetUtil.addFacet(new TitleFacetFormattedByRegex(facet));
}
// else search for @Property(pattern=...)
final Property property = Annotations.getAnnotation(method, Property.class);
if (facet == null) {
facet = RegExFacetForPropertyAnnotation.create(property, returnType, holder);
}
FacetUtil.addFacet(facet);
}
void processFileAccept(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// else search for @Property(maxLength=...)
final Property property = Annotations.getAnnotation(method, Property.class);
FileAcceptFacet facet = FileAcceptFacetForPropertyAnnotation.create(property, holder);
FacetUtil.addFacet(facet);
}
// //////////////////////////////////////
@Override
public void refineMetaModelValidator(final MetaModelValidatorComposite metaModelValidator, final IsisConfiguration configuration) {
metaModelValidator.add(postsPropertyChangedEventValidator);
metaModelValidator.add(propertyInteractionValidator);
metaModelValidator.add(regexValidator);
metaModelValidator.add(optionalValidator);
metaModelValidator.add(mandatoryValidator);
metaModelValidator.add(hiddenValidator);
metaModelValidator.add(disabledValidator);
metaModelValidator.add(maxLengthValidator);
metaModelValidator.add(mustSatisfyValidator);
metaModelValidator.add(notPersistedValidator);
metaModelValidator.add(conflictingOptionalityValidator);
}
// //////////////////////////////////////
@Override
public void setServicesInjector(final ServicesInjector servicesInjector) {
super.setServicesInjector(servicesInjector);
IsisConfiguration configuration = servicesInjector.getConfigurationServiceInternal();
postsPropertyChangedEventValidator.setConfiguration(configuration);
propertyInteractionValidator.setConfiguration(configuration);
regexValidator.setConfiguration(configuration);
optionalValidator.setConfiguration(configuration);
mandatoryValidator.setConfiguration(configuration);
hiddenValidator.setConfiguration(configuration);
disabledValidator.setConfiguration(configuration);
maxLengthValidator.setConfiguration(configuration);
mustSatisfyValidator.setConfiguration(configuration);
notPersistedValidator.setConfiguration(configuration);
}
}