/* * 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.facetprocessor; import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.isis.core.commons.lang.ListExtensions; import org.apache.isis.core.metamodel.facetapi.FacetHolder; import org.apache.isis.core.metamodel.facetapi.FeatureType; import org.apache.isis.core.metamodel.facetapi.MethodRemover; import org.apache.isis.core.metamodel.facets.ContributeeMemberFacetFactory; import org.apache.isis.core.metamodel.facets.FacetFactory; import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext; import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext; import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessParameterContext; import org.apache.isis.core.metamodel.facets.FacetedMethod; import org.apache.isis.core.metamodel.facets.FacetedMethodParameter; import org.apache.isis.core.metamodel.facets.MethodFilteringFacetFactory; import org.apache.isis.core.metamodel.facets.MethodPrefixBasedFacetFactory; import org.apache.isis.core.metamodel.facets.MethodRemoverConstants; import org.apache.isis.core.metamodel.facets.PropertyOrCollectionIdentifyingFacetFactory; import org.apache.isis.core.metamodel.progmodel.ProgrammingModel; import org.apache.isis.core.metamodel.services.ServicesInjector; import org.apache.isis.core.metamodel.services.ServicesInjectorAware; import org.apache.isis.core.metamodel.spec.feature.ObjectMember; public class FacetProcessor implements ServicesInjectorAware { private final ProgrammingModel programmingModel; /** * Class<FacetFactory> => FacetFactory */ private final Map<Class<? extends FacetFactory>, FacetFactory> factoryByFactoryType = Maps.newHashMap(); /** * {@link FacetFactory Facet factories}, in order they were * {@link #registerFactory(FacetFactory) registered}. */ private final List<FacetFactory> factories = Lists.newArrayList(); /** * All method prefixes to check in {@link #recognizes(Method)}. * * <p> * Derived from factories that implement * {@link MethodPrefixBasedFacetFactory}. * * <p> * If <tt>null</tt>, indicates that the cache hasn't been built. */ private List<String> cachedMethodPrefixes; /** * All registered {@link FacetFactory factories} that implement * {@link MethodFilteringFacetFactory}. * * <p> * Used within {@link #recognizes(Method)}. * * <p> * If <tt>null</tt>, indicates that the cache hasn't been built. */ private List<MethodFilteringFacetFactory> cachedMethodFilteringFactories; /** * All registered {@link FacetFactory factories} that implement * {@link ContributeeMemberFacetFactory}. * * <p> * If <tt>null</tt>, indicates that the cache hasn't been built. */ private List<ContributeeMemberFacetFactory> cachedContributeeMemberFacetFactories; /** * All registered {@link FacetFactory factories} that implement * {@link PropertyOrCollectionIdentifyingFacetFactory}. * * <p> * Used within {@link #recognizes(Method)}. * * <p> * If <tt>null</tt>, indicates that the cache hasn't been built. */ private List<PropertyOrCollectionIdentifyingFacetFactory> cachedPropertyOrCollectionIdentifyingFactories; /** * ObjectFeatureType => List<FacetFactory> * * <p> * Lazily initialized, then cached. The lists remain in the same order that * the factories were {@link #registerFactory(FacetFactory) registered}. */ private Map<FeatureType, List<FacetFactory>> factoryListByFeatureType = null; public FacetProcessor(final ProgrammingModel programmingModel) { this.programmingModel = programmingModel; } // ////////////////////////////////////////////////// // init, shutdown (application scoped) // ////////////////////////////////////////////////// public void init() { final List<FacetFactory> facetFactoryList = programmingModel.getList(); for (final FacetFactory facetFactory : facetFactoryList) { registerFactory(facetFactory); } } public void shutdown() { } public void registerFactory(final FacetFactory factory) { clearCaches(); factoryByFactoryType.put(factory.getClass(), factory); factories.add(factory); injectDependenciesInto(factory); } /** * This is <tt>public</tt> so that can be used for <tt>@Facets</tt> * processing. */ public void injectDependenciesInto(final FacetFactory factory) { servicesInjector.injectInto(factory); } /** * Appends to the supplied {@link Set} all of the {@link Method}s that may * represent a property or collection. * * <p> * Delegates to all known * {@link PropertyOrCollectionIdentifyingFacetFactory}s. */ public Set<Method> findAssociationCandidateAccessors(final List<Method> methods, final Set<Method> candidates) { cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired(); for (final Method method : methods) { if (method == null) { continue; } for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) { if (facetFactory.isPropertyOrCollectionAccessorCandidate(method)) { candidates.add(method); } } } return candidates; } /** * Use the provided {@link MethodRemover} to have all known * {@link PropertyOrCollectionIdentifyingFacetFactory}s to remove all * property accessors, and append them to the supplied methodList. * * <p> * Intended to be called after {@link #findAndRemovePropertyAccessors(org.apache.isis.core.metamodel.facetapi.MethodRemover, java.util.List)} once only reference properties remain. */ public void findAndRemovePropertyAccessors(final MethodRemover methodRemover, final List<Method> methodListToAppendTo) { cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired(); for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) { facetFactory.findAndRemovePropertyAccessors(methodRemover, methodListToAppendTo); } } /** * Use the provided {@link MethodRemover} to have all known * {@link PropertyOrCollectionIdentifyingFacetFactory}s to remove all * property accessors, and append them to the supplied methodList. * * @see PropertyOrCollectionIdentifyingFacetFactory#findAndRemoveCollectionAccessors(MethodRemover, * List) */ public void findAndRemoveCollectionAccessors(final MethodRemover methodRemover, final List<Method> methodListToAppendTo) { cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired(); for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) { facetFactory.findAndRemoveCollectionAccessors(methodRemover, methodListToAppendTo); } } /** * Whether this {@link Method method} is recognized by any of the * {@link FacetFactory}s. * * <p> * Typically this is when method has a specific prefix, such as * <tt>validate</tt> or <tt>hide</tt>. Specifically, it checks: * <ul> * <li>the method's prefix against the prefixes supplied by any * {@link MethodPrefixBasedFacetFactory}</li> * <li>the method against any {@link MethodFilteringFacetFactory}</li> * </ul> * * <p> * The design of {@link MethodPrefixBasedFacetFactory} (whereby this facet * factory set does the work) is a slight performance optimization for when * there are multiple facet factories that search for the same prefix. */ public boolean recognizes(final Method method) { cacheMethodPrefixesIfRequired(); final String methodName = method.getName(); for (final String prefix : cachedMethodPrefixes) { if (methodName.startsWith(prefix)) { return true; } } cacheMethodFilteringFacetFactoriesIfRequired(); for (final MethodFilteringFacetFactory factory : cachedMethodFilteringFactories) { if (factory.recognizes(method)) { return true; } } return false; } /** * Attaches all facets applicable to the provided {@link FeatureType#OBJECT * object}) to the supplied {@link FacetHolder}. * * <p> * Delegates to {@link FacetFactory#process(org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext)} for each * appropriate factory. * * @see FacetFactory#process(ProcessClassContext) * * @param cls * - class to process * @param facetHolder * - holder to attach facets to. */ public void process( final Class<?> cls, final Properties metadataProperties, final MethodRemover methodRemover, final FacetHolder facetHolder) { final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.OBJECT); for (final FacetFactory facetFactory : factoryList) { facetFactory.process(new ProcessClassContext(cls, metadataProperties, removerElseNullRemover(methodRemover), facetHolder)); } } public void processPost( final Class<?> cls, final Properties metadataProperties, final MethodRemover methodRemover, final FacetHolder facetHolder) { final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.OBJECT_POST_PROCESSING); for (final FacetFactory facetFactory : factoryList) { facetFactory.process(new ProcessClassContext(cls, metadataProperties, removerElseNullRemover(methodRemover), facetHolder)); } } /** * Attaches all facets applicable to the provided {@link FeatureType type of * feature} to the supplied {@link FacetHolder}. * * <p> * Delegates to {@link FacetFactory#process(org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext)} for each * appropriate factory. * * @param cls * - class in which introspect; allowing the helper methods to be * found is subclasses of that which the method was originally * found. * @param method * - method to process * @param facetedMethod * - holder to attach facets to. * @param featureType * - what type of feature the method represents (property, * action, collection etc) * @param metadataProperties * - additional properties to parse and use */ public void process( final Class<?> cls, final Method method, final MethodRemover methodRemover, final FacetedMethod facetedMethod, final FeatureType featureType, final Properties metadataProperties) { final List<FacetFactory> factoryList = getFactoryListByFeatureType(featureType); final ProcessMethodContext processMethodContext = new ProcessMethodContext(cls, featureType, metadataProperties, method, removerElseNullRemover(methodRemover), facetedMethod); for (final FacetFactory facetFactory : factoryList) { facetFactory.process(processMethodContext); } } public void processMemberOrder( final Properties metadataProperties, final ObjectMember facetHolder) { cacheContributeeMemberFacetFactoriesIfRequired(); final ContributeeMemberFacetFactory.ProcessContributeeMemberContext processMemberContext = new ContributeeMemberFacetFactory.ProcessContributeeMemberContext(metadataProperties, facetHolder); for (final ContributeeMemberFacetFactory facetFactory : cachedContributeeMemberFacetFactories) { facetFactory.process(processMemberContext); } } /** * Attaches all facets applicable to the provided parameter to the supplied * {@link FacetHolder}. * * <p> * Delegates to {@link FacetFactory#processParams(ProcessParameterContext)} * for each appropriate factory. * * @see FacetFactory#processParams(ProcessParameterContext) * * @param introspectedClass * @param method * - action method to process * @param paramNum * - 0-based * @param methodRemover * @param facetedMethodParameter */ public void processParams( final Class<?> introspectedClass, final Method method, final int paramNum, final MethodRemover methodRemover, final FacetedMethodParameter facetedMethodParameter) { for (FeatureType featureType : FeatureType.PARAMETERS_ONLY) { processParams(introspectedClass, method, paramNum, methodRemover, facetedMethodParameter, featureType); } } public void processParams( final Class<?> introspectedClass, final Method method, final int paramNum, final MethodRemover methodRemover, final FacetedMethodParameter facetedMethodParameter, final FeatureType featureType) { final List<FacetFactory> factoryList = getFactoryListByFeatureType(featureType); final ProcessParameterContext processParameterContext = new ProcessParameterContext(introspectedClass, method, paramNum, methodRemover, facetedMethodParameter); for (final FacetFactory facetFactory : factoryList) { facetFactory.processParams(processParameterContext); } } private List<FacetFactory> getFactoryListByFeatureType(final FeatureType featureType) { cacheByFeatureTypeIfRequired(); List<FacetFactory> list = factoryListByFeatureType.get(featureType); return list != null? list: Collections.<FacetFactory>emptyList(); } private void clearCaches() { factoryListByFeatureType = null; cachedMethodPrefixes = null; cachedMethodFilteringFactories = null; cachedPropertyOrCollectionIdentifyingFactories = null; } private synchronized void cacheByFeatureTypeIfRequired() { if (factoryListByFeatureType != null) { return; } factoryListByFeatureType = Maps.newHashMap(); for (final FacetFactory factory : factories) { final List<FeatureType> featureTypes = factory.getFeatureTypes(); for (final FeatureType featureType : featureTypes) { final List<FacetFactory> factoryList = getList(factoryListByFeatureType, featureType); factoryList.add(factory); } } } private synchronized void cacheMethodPrefixesIfRequired() { if (cachedMethodPrefixes != null) { return; } cachedMethodPrefixes = Lists.newArrayList(); for (final FacetFactory facetFactory : factories) { if (facetFactory instanceof MethodPrefixBasedFacetFactory) { final MethodPrefixBasedFacetFactory methodPrefixBasedFacetFactory = (MethodPrefixBasedFacetFactory) facetFactory; ListExtensions.mergeWith(cachedMethodPrefixes, methodPrefixBasedFacetFactory.getPrefixes()); } } } private synchronized void cacheMethodFilteringFacetFactoriesIfRequired() { if (cachedMethodFilteringFactories != null) { return; } cachedMethodFilteringFactories = Lists.newArrayList(); for (final FacetFactory factory : factories) { if (factory instanceof MethodFilteringFacetFactory) { final MethodFilteringFacetFactory methodFilteringFacetFactory = (MethodFilteringFacetFactory) factory; cachedMethodFilteringFactories.add(methodFilteringFacetFactory); } } } private synchronized void cacheContributeeMemberFacetFactoriesIfRequired() { if (cachedContributeeMemberFacetFactories != null) { return; } cachedContributeeMemberFacetFactories = Lists.newArrayList(); for (final FacetFactory factory : factories) { if (factory instanceof ContributeeMemberFacetFactory) { final ContributeeMemberFacetFactory memberOrderingFacetFactory = (ContributeeMemberFacetFactory) factory; cachedContributeeMemberFacetFactories.add(memberOrderingFacetFactory); } } } private synchronized void cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired() { if (cachedPropertyOrCollectionIdentifyingFactories != null) { return; } cachedPropertyOrCollectionIdentifyingFactories = Lists.newArrayList(); for (FacetFactory factory : factories) { if (factory instanceof PropertyOrCollectionIdentifyingFacetFactory) { final PropertyOrCollectionIdentifyingFacetFactory identifyingFacetFactory = (PropertyOrCollectionIdentifyingFacetFactory) factory; cachedPropertyOrCollectionIdentifyingFactories.add(identifyingFacetFactory); } } } private static <K, T> List<T> getList(final Map<K, List<T>> map, final K key) { List<T> list = map.get(key); if (list == null) { list = Lists.newArrayList(); map.put(key, list); } return list; } private MethodRemover removerElseNullRemover(final MethodRemover methodRemover) { return methodRemover != null ? methodRemover : MethodRemoverConstants.NULL; } //region > dependencies private ServicesInjector servicesInjector; @Override public void setServicesInjector(final ServicesInjector servicesInjector) { this.servicesInjector = servicesInjector; } //endregion }