/*
* 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.collections.collection;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import org.apache.isis.applib.annotation.Collection;
import org.apache.isis.applib.annotation.CollectionInteraction;
import org.apache.isis.applib.annotation.Disabled;
import org.apache.isis.applib.annotation.Hidden;
import org.apache.isis.applib.annotation.NotPersisted;
import org.apache.isis.applib.annotation.PostsCollectionAddedToEvent;
import org.apache.isis.applib.annotation.PostsCollectionRemovedFromEvent;
import org.apache.isis.applib.annotation.TypeOf;
import org.apache.isis.applib.services.eventbus.CollectionDomainEvent;
import org.apache.isis.core.commons.config.IsisConfiguration;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facetapi.FacetUtil;
import org.apache.isis.core.metamodel.facetapi.FeatureType;
import org.apache.isis.core.metamodel.facetapi.MetaModelValidatorRefiner;
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.actcoll.typeof.TypeOfFacet;
import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacetInferredFromArray;
import org.apache.isis.core.metamodel.facets.actcoll.typeof.TypeOfFacetInferredFromGenerics;
import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
import org.apache.isis.core.metamodel.facets.collections.collection.disabled.DisabledFacetForCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.disabled.DisabledFacetForDisabledAnnotationOnCollection;
import org.apache.isis.core.metamodel.facets.collections.collection.hidden.HiddenFacetForCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.hidden.HiddenFacetForHiddenAnnotationOnCollection;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionAddToFacetForDomainEventFromAbstract;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionAddToFacetForDomainEventFromCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionAddToFacetForDomainEventFromCollectionInteractionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionAddToFacetForDomainEventFromDefault;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionAddToFacetForPostsCollectionAddedToEventAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionDomainEventFacetAbstract;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionDomainEventFacetDefault;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionDomainEventFacetForCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionDomainEventFacetForCollectionInteractionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionRemoveFromFacetForDomainEventFromAbstract;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionRemoveFromFacetForDomainEventFromCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionRemoveFromFacetForDomainEventFromCollectionInteractionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionRemoveFromFacetForDomainEventFromDefault;
import org.apache.isis.core.metamodel.facets.collections.collection.modify.CollectionRemoveFromFacetForPostsCollectionRemovedFromEventAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.notpersisted.NotPersistedFacetForCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.notpersisted.NotPersistedFacetForNotPersistedAnnotationOnCollection;
import org.apache.isis.core.metamodel.facets.collections.collection.typeof.TypeOfFacetOnCollectionFromCollectionAnnotation;
import org.apache.isis.core.metamodel.facets.collections.collection.typeof.TypeOfFacetOnCollectionFromTypeOfAnnotation;
import org.apache.isis.core.metamodel.facets.collections.modify.CollectionAddToFacet;
import org.apache.isis.core.metamodel.facets.collections.modify.CollectionRemoveFromFacet;
import org.apache.isis.core.metamodel.facets.members.disabled.DisabledFacet;
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.services.ServicesInjector;
import org.apache.isis.core.metamodel.specloader.CollectionUtils;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorComposite;
import org.apache.isis.core.metamodel.specloader.validator.MetaModelValidatorForDeprecatedAnnotation;
import org.apache.isis.core.metamodel.util.EventUtil;
public class CollectionAnnotationFacetFactory extends FacetFactoryAbstract implements MetaModelValidatorRefiner {
private final MetaModelValidatorForDeprecatedAnnotation postsCollectionAddedToEventValidator = new MetaModelValidatorForDeprecatedAnnotation(PostsCollectionAddedToEvent.class);
private final MetaModelValidatorForDeprecatedAnnotation postsCollectionRemovedFromEventValidator = new MetaModelValidatorForDeprecatedAnnotation(PostsCollectionRemovedFromEvent.class);
private final MetaModelValidatorForDeprecatedAnnotation collectionInteractionValidator = new MetaModelValidatorForDeprecatedAnnotation(CollectionInteraction.class);
private final MetaModelValidatorForDeprecatedAnnotation hiddenValidator = new MetaModelValidatorForDeprecatedAnnotation(Hidden.class);
private final MetaModelValidatorForDeprecatedAnnotation disabledValidator = new MetaModelValidatorForDeprecatedAnnotation(Disabled.class);
private final MetaModelValidatorForDeprecatedAnnotation notPersistedValidator = new MetaModelValidatorForDeprecatedAnnotation(NotPersisted.class);
private final MetaModelValidatorForDeprecatedAnnotation typeOfValidator = new MetaModelValidatorForDeprecatedAnnotation(TypeOf.class);
public CollectionAnnotationFacetFactory() {
super(FeatureType.COLLECTIONS_AND_ACTIONS);
}
@Override
public void process(final ProcessMethodContext processMethodContext) {
processModify(processMethodContext);
processHidden(processMethodContext);
processEditing(processMethodContext);
processNotPersisted(processMethodContext);
processTypeOf(processMethodContext);
}
void processModify(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
final PropertyOrCollectionAccessorFacet getterFacet = holder.getFacet(PropertyOrCollectionAccessorFacet.class);
if(getterFacet == null) {
return;
}
//
// Set up CollectionDomainEventFacet, which will act as the hiding/disabling/validating advisor
//
final PostsCollectionAddedToEvent postsCollectionAddedToEvent = Annotations.getAnnotation(method, PostsCollectionAddedToEvent.class);
final PostsCollectionRemovedFromEvent postsCollectionRemovedFromEvent = Annotations.getAnnotation(method, PostsCollectionRemovedFromEvent.class);
final CollectionInteraction collectionInteraction = Annotations.getAnnotation(method, CollectionInteraction.class);
final Collection collection = Annotations.getAnnotation(method, Collection.class);
final Class<? extends CollectionDomainEvent<?, ?>> collectionDomainEventType;
final CollectionDomainEventFacetAbstract collectionDomainEventFacet;
// can't really do this, because would result in the event being fired for the
// hidden/disable/validate phases, most likely breaking existing code.
//
// in any case, which to use... addTo or removeFrom ?
// // search for @PostsCollectionAddedToEvent(value=...)
// if(postsCollectionAddedToEvent != null) {
// collectionDomainEventType = postsCollectionAddedToEvent.value();
// collectionDomainEventFacet = postsCollectionAddedToEventValidator.flagIfPresent(
// new CollectionDomainEventFacetForPostsCollectionAddedToEventAnnotation(
// collectionDomainEventType, servicesInjector, getSpecificationLoader(), holder));
// } else
// // search for @PostsCollectionRemovedFromEvent(value=...)
// if(postsCollectionRemovedFromEvent != null) {
// collectionDomainEventType = postsCollectionRemovedFromEvent.value();
// collectionDomainEventFacet = postsCollectionRemovedFromEventValidator.flagIfPresent(
// new CollectionDomainEventFacetForPostsCollectionRemovedFromEventAnnotation(
// collectionDomainEventType, servicesInjector, getSpecificationLoader(), holder));
// } else
// search for @CollectionInteraction(value=...)
if(collectionInteraction != null) {
collectionDomainEventType = collectionInteraction.value();
collectionDomainEventFacet = collectionInteractionValidator.flagIfPresent(
new CollectionDomainEventFacetForCollectionInteractionAnnotation(
collectionDomainEventType, servicesInjector, getSpecificationLoader(), holder), processMethodContext);
} else
// search for @Collection(domainEvent=...)
if(collection != null && collection.domainEvent() != null) {
collectionDomainEventType = collection.domainEvent();
collectionDomainEventFacet = new CollectionDomainEventFacetForCollectionAnnotation(
collectionDomainEventType, servicesInjector, getSpecificationLoader(), holder);
} else
// else use default event type
{
collectionDomainEventType = CollectionDomainEvent.Default.class;
collectionDomainEventFacet = new CollectionDomainEventFacetDefault(
collectionDomainEventType, servicesInjector, getSpecificationLoader(), holder);
}
if(!CollectionDomainEvent.Noop.class.isAssignableFrom(collectionDomainEventFacet.getEventType())) {
FacetUtil.addFacet(collectionDomainEventFacet);
}
if(EventUtil.eventTypeIsPostable(
collectionDomainEventFacet.getEventType(),
CollectionDomainEvent.Noop.class,
CollectionDomainEvent.Default.class,
"isis.reflector.facet.collectionAnnotation.domainEvent.postForDefault", getConfiguration())) {
FacetUtil.addFacet(collectionDomainEventFacet);
}
//
// if the collection is mutable, then replace the existing addTo and removeFrom facets with equivalents that
// also post to the event bus.
//
// here we support the deprecated annotations
//
final CollectionAddToFacet collectionAddToFacet = holder.getFacet(CollectionAddToFacet.class);
if (collectionAddToFacet != null) {
// the current collectionAddToFacet will end up as the underlying facet of
// one of these facets to be created.
final CollectionAddToFacetForDomainEventFromAbstract replacementFacet;
// deprecated
if (postsCollectionAddedToEvent != null) {
replacementFacet = new CollectionAddToFacetForPostsCollectionAddedToEventAnnotation(
postsCollectionAddedToEvent.value(), getterFacet, collectionAddToFacet, collectionDomainEventFacet, holder, servicesInjector);
} else
// deprecated (but more recently)
if(collectionInteraction != null) {
replacementFacet = new CollectionAddToFacetForDomainEventFromCollectionInteractionAnnotation(
collectionDomainEventType, getterFacet, collectionAddToFacet, collectionDomainEventFacet, holder, servicesInjector);
} else
// current
if(collection != null) {
replacementFacet = new CollectionAddToFacetForDomainEventFromCollectionAnnotation(
collectionDomainEventType, getterFacet, collectionAddToFacet, collectionDomainEventFacet, holder, servicesInjector);
} else
// default
{
replacementFacet = new CollectionAddToFacetForDomainEventFromDefault(
collectionDomainEventType, getterFacet, collectionAddToFacet, collectionDomainEventFacet, holder, servicesInjector);
}
FacetUtil.addFacet(replacementFacet);
}
final CollectionRemoveFromFacet collectionRemoveFromFacet = holder.getFacet(CollectionRemoveFromFacet.class);
if (collectionRemoveFromFacet != null) {
// the current collectionRemoveFromFacet will end up as the underlying facet of the PostsCollectionRemovedFromEventFacetAnnotation
final CollectionRemoveFromFacetForDomainEventFromAbstract replacementFacet;
// deprecated
if (postsCollectionRemovedFromEvent != null) {
replacementFacet = new CollectionRemoveFromFacetForPostsCollectionRemovedFromEventAnnotation(postsCollectionRemovedFromEvent.value(), getterFacet, collectionRemoveFromFacet, collectionDomainEventFacet, servicesInjector, holder);
} else
// deprecated (but more recently)
if(collectionInteraction != null) {
replacementFacet = new CollectionRemoveFromFacetForDomainEventFromCollectionInteractionAnnotation(collectionDomainEventType, getterFacet, collectionRemoveFromFacet, collectionDomainEventFacet, servicesInjector, holder);
} else
// current
if(collection != null) {
replacementFacet = new CollectionRemoveFromFacetForDomainEventFromCollectionAnnotation(collectionDomainEventType, getterFacet, collectionRemoveFromFacet, collectionDomainEventFacet, servicesInjector, holder);
} else
// default
{
replacementFacet = new CollectionRemoveFromFacetForDomainEventFromDefault(collectionDomainEventType, getterFacet, collectionRemoveFromFacet, collectionDomainEventFacet, servicesInjector, holder);
}
FacetUtil.addFacet(replacementFacet);
}
}
void processHidden(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @Hidden
final Hidden hiddenAnnotation = Annotations.getAnnotation(processMethodContext.getMethod(), Hidden.class);
HiddenFacet facet = hiddenValidator.flagIfPresent(HiddenFacetForHiddenAnnotationOnCollection.create(hiddenAnnotation, holder), processMethodContext);
// else check for @Collection(hidden=...)
final Collection collection = Annotations.getAnnotation(method, Collection.class);
if(facet == null) {
facet = HiddenFacetForCollectionAnnotation.create(collection, holder);
}
FacetUtil.addFacet(facet);
}
void processEditing(final ProcessMethodContext processMethodContext) {
final Method method = processMethodContext.getMethod();
final FacetHolder holder = processMethodContext.getFacetHolder();
// check for deprecated @Disabled
final Disabled annotation = Annotations.getAnnotation(method, Disabled.class);
DisabledFacet facet = disabledValidator.flagIfPresent(DisabledFacetForDisabledAnnotationOnCollection.create(annotation, holder), processMethodContext);
// else check for @Collection(editing=...)
final Collection collection = Annotations.getAnnotation(method, Collection.class);
if(facet == null) {
facet = DisabledFacetForCollectionAnnotation.create(collection, holder);
}
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);
final NotPersistedFacet facet1 = NotPersistedFacetForNotPersistedAnnotationOnCollection.create(annotation, holder);
FacetUtil.addFacet(notPersistedValidator.flagIfPresent(facet1, processMethodContext));
NotPersistedFacet facet = facet1;
// else search for @Collection(notPersisted=...)
final Collection collection = Annotations.getAnnotation(method, Collection.class);
if(facet == null) {
facet = NotPersistedFacetForCollectionAnnotation.create(collection, holder);
}
FacetUtil.addFacet(facet);
}
void processTypeOf(final ProcessMethodContext processMethodContext) {
final FacetedMethod facetHolder = processMethodContext.getFacetHolder();
final Method method = processMethodContext.getMethod();
final Class<?> methodReturnType = method.getReturnType();
if (!CollectionUtils.isCollectionType(methodReturnType) && !CollectionUtils.isArrayType(methodReturnType)) {
return;
}
TypeOfFacet facet;
// check for deprecated @TypeOf
final TypeOf annotation = Annotations.getAnnotation(method, TypeOf.class);
facet = typeOfValidator.flagIfPresent(
TypeOfFacetOnCollectionFromTypeOfAnnotation.create(annotation, facetHolder, getSpecificationLoader()), processMethodContext);
// else check for @Collection(typeOf=...)
final Collection collection = Annotations.getAnnotation(method, Collection.class);
if(facet == null) {
facet = TypeOfFacetOnCollectionFromCollectionAnnotation.create(collection, facetHolder, getSpecificationLoader());
}
// else infer from return type
if(facet == null) {
final Class<?> returnType = method.getReturnType();
if (returnType.isArray()) {
final Class<?> componentType = returnType.getComponentType();
facet = new TypeOfFacetInferredFromArray(componentType, facetHolder, getSpecificationLoader());
}
}
// else infer from generic return type
if(facet == null) {
facet = inferFromGenericReturnType(processMethodContext);
}
FacetUtil.addFacet(facet);
}
private TypeOfFacet inferFromGenericReturnType(final ProcessMethodContext processMethodContext) {
final FacetedMethod facetHolder = processMethodContext.getFacetHolder();
final Method method = processMethodContext.getMethod();
final Type type = method.getGenericReturnType();
if (!(type instanceof ParameterizedType)) {
return null;
}
final ParameterizedType parameterizedType = (ParameterizedType) type;
final Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length == 0) {
return null;
}
final Object actualTypeArgument = actualTypeArguments[0];
if (actualTypeArgument instanceof Class) {
final Class<?> actualType = (Class<?>) actualTypeArgument;
return new TypeOfFacetInferredFromGenerics(actualType, facetHolder, getSpecificationLoader());
}
if (actualTypeArgument instanceof TypeVariable) {
// TODO: what to do here?
return null;
}
return null;
}
// //////////////////////////////////////
@Override
public void refineMetaModelValidator(final MetaModelValidatorComposite metaModelValidator, final IsisConfiguration configuration) {
metaModelValidator.add(postsCollectionAddedToEventValidator);
metaModelValidator.add(postsCollectionRemovedFromEventValidator);
metaModelValidator.add(collectionInteractionValidator);
metaModelValidator.add(notPersistedValidator);
metaModelValidator.add(typeOfValidator);
metaModelValidator.add(hiddenValidator);
metaModelValidator.add(disabledValidator);
}
// //////////////////////////////////////
@Override
public void setServicesInjector(final ServicesInjector servicesInjector) {
super.setServicesInjector(servicesInjector);
final IsisConfiguration configuration = getConfiguration();
postsCollectionAddedToEventValidator.setConfiguration(configuration);
postsCollectionRemovedFromEventValidator.setConfiguration(configuration);
collectionInteractionValidator.setConfiguration(configuration);
typeOfValidator.setConfiguration(configuration);
notPersistedValidator.setConfiguration(configuration);
hiddenValidator.setConfiguration(configuration);
disabledValidator.setConfiguration(configuration);
}
}