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.facetprocessor;
021
022import static org.apache.isis.core.commons.ensure.Ensure.ensureThatState;
023import static org.hamcrest.CoreMatchers.is;
024import static org.hamcrest.CoreMatchers.notNullValue;
025
026import java.lang.reflect.Method;
027import java.util.Collections;
028import java.util.Iterator;
029import java.util.List;
030import java.util.Map;
031import java.util.Properties;
032import java.util.Set;
033
034import com.google.common.collect.Lists;
035import com.google.common.collect.Maps;
036
037import org.apache.isis.core.commons.config.IsisConfiguration;
038import org.apache.isis.core.commons.lang.ListExtensions;
039import org.apache.isis.core.metamodel.facetapi.FacetHolder;
040import org.apache.isis.core.metamodel.facetapi.FeatureType;
041import org.apache.isis.core.metamodel.facetapi.MethodRemover;
042import org.apache.isis.core.metamodel.facets.FacetFactory;
043import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
044import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessMethodContext;
045import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessParameterContext;
046import org.apache.isis.core.metamodel.facets.FacetedMethod;
047import org.apache.isis.core.metamodel.facets.FacetedMethodParameter;
048import org.apache.isis.core.metamodel.facets.ContributeeMemberFacetFactory;
049import org.apache.isis.core.metamodel.facets.MethodFilteringFacetFactory;
050import org.apache.isis.core.metamodel.facets.MethodPrefixBasedFacetFactory;
051import org.apache.isis.core.metamodel.facets.MethodRemoverConstants;
052import org.apache.isis.core.metamodel.facets.PropertyOrCollectionIdentifyingFacetFactory;
053import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
054import org.apache.isis.core.metamodel.runtimecontext.RuntimeContext;
055import org.apache.isis.core.metamodel.runtimecontext.RuntimeContextAware;
056import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
057import org.apache.isis.core.metamodel.specloader.collectiontyperegistry.CollectionTypeRegistry;
058import org.apache.isis.core.metamodel.specloader.specimpl.ObjectActionContributee;
059
060public class FacetProcessor implements RuntimeContextAware {
061
062    private final IsisConfiguration configuration;
063    private final CollectionTypeRegistry collectionTypeRegistry;
064    private final ProgrammingModel programmingModel;
065
066    private RuntimeContext runtimeContext;
067
068    /**
069     * Class<FacetFactory> => FacetFactory
070     */
071    private final Map<Class<? extends FacetFactory>, FacetFactory> factoryByFactoryType = Maps.newHashMap();
072
073    /**
074     * {@link FacetFactory Facet factories}, in order they were
075     * {@link #registerFactory(FacetFactory) registered}.
076     */
077    private final List<FacetFactory> factories = Lists.newArrayList();
078
079    /**
080     * All method prefixes to check in {@link #recognizes(Method)}.
081     * 
082     * <p>
083     * Derived from factories that implement
084     * {@link MethodPrefixBasedFacetFactory}.
085     * 
086     * <p>
087     * If <tt>null</tt>, indicates that the cache hasn't been built.
088     */
089    private List<String> cachedMethodPrefixes;
090
091    /**
092     * All registered {@link FacetFactory factories} that implement
093     * {@link MethodFilteringFacetFactory}.
094     * 
095     * <p>
096     * Used within {@link #recognizes(Method)}.
097     * 
098     * <p>
099     * If <tt>null</tt>, indicates that the cache hasn't been built.
100     */
101    private List<MethodFilteringFacetFactory> cachedMethodFilteringFactories;
102    
103    /**
104     * All registered {@link FacetFactory factories} that implement
105     * {@link ContributeeMemberFacetFactory}.
106     * 
107     * <p>
108     * If <tt>null</tt>, indicates that the cache hasn't been built.
109     */
110    private List<ContributeeMemberFacetFactory> cachedMemberOrderingFactories;
111
112    /**
113     * All registered {@link FacetFactory factories} that implement
114     * {@link PropertyOrCollectionIdentifyingFacetFactory}.
115     * 
116     * <p>
117     * Used within {@link #recognizes(Method)}.
118     * 
119     * <p>
120     * If <tt>null</tt>, indicates that the cache hasn't been built.
121     */
122    private List<PropertyOrCollectionIdentifyingFacetFactory> cachedPropertyOrCollectionIdentifyingFactories;
123
124    /**
125     * ObjectFeatureType => List<FacetFactory>
126     * 
127     * <p>
128     * Lazily initialized, then cached. The lists remain in the same order that
129     * the factories were {@link #registerFactory(FacetFactory) registered}.
130     */
131    private Map<FeatureType, List<FacetFactory>> factoryListByFeatureType = null;
132
133    public FacetProcessor(final IsisConfiguration configuration, final CollectionTypeRegistry collectionTypeRegistry, final ProgrammingModel programmingModel) {
134        ensureThatState(configuration, is(notNullValue()));
135        ensureThatState(collectionTypeRegistry, is(notNullValue()));
136        ensureThatState(programmingModel, is(notNullValue()));
137
138        this.configuration = configuration;
139        this.programmingModel = programmingModel;
140        this.collectionTypeRegistry = collectionTypeRegistry;
141    }
142
143    // //////////////////////////////////////////////////
144    // init, shutdown (application scoped)
145    // //////////////////////////////////////////////////
146
147    public void init() {
148        ensureThatState(runtimeContext, is(notNullValue()));
149        final List<FacetFactory> facetFactoryList = programmingModel.getList();
150        for (final FacetFactory facetFactory : facetFactoryList) {
151            registerFactory(facetFactory);
152        }
153    }
154
155    public void shutdown() {
156    }
157
158    public void registerFactory(final FacetFactory factory) {
159        clearCaches();
160        factoryByFactoryType.put(factory.getClass(), factory);
161        factories.add(factory);
162
163        injectDependenciesInto(factory);
164    }
165
166    /**
167     * This is <tt>public</tt> so that can be used for <tt>@Facets</tt>
168     * processing.
169     */
170    public void injectDependenciesInto(final FacetFactory factory) {
171        getCollectionTypeRepository().injectInto(factory);
172        getIsisConfiguration().injectInto(factory);
173
174        // cascades all the subcomponents also
175        getRuntimeContext().injectInto(factory);
176    }
177
178    public FacetFactory getFactoryByFactoryType(final Class<? extends FacetFactory> factoryType) {
179        return factoryByFactoryType.get(factoryType);
180    }
181
182    /**
183     * Appends to the supplied {@link Set} all of the {@link Method}s that may
184     * represent a property or collection.
185     * 
186     * <p>
187     * Delegates to all known
188     * {@link PropertyOrCollectionIdentifyingFacetFactory}s.
189     */
190    public Set<Method> findAssociationCandidateAccessors(final List<Method> methods, final Set<Method> candidates) {
191        cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired();
192        for (final Method method : methods) {
193            if (method == null) {
194                continue;
195            }
196            for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) {
197                if (facetFactory.isPropertyOrCollectionAccessorCandidate(method)) {
198                    candidates.add(method);
199                }
200            }
201        }
202        return candidates;
203    }
204
205    /**
206     * Use the provided {@link MethodRemover} to have all known
207     * {@link PropertyOrCollectionIdentifyingFacetFactory}s to remove all
208     * property accessors, and append them to the supplied methodList.
209     * 
210     * <p>
211     * Intended to be called after
212     * {@link #findAndRemoveValuePropertyAccessors(MethodRemover, List)} once
213     * only reference properties remain.
214     * 
215     * @see PropertyOrCollectionIdentifyingFacetFactory#findAndRemoveValuePropertyAccessors(MethodRemover,
216     *      List)
217     */
218    public void findAndRemovePropertyAccessors(final MethodRemover methodRemover, final List<Method> methodListToAppendTo) {
219        cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired();
220        for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) {
221            facetFactory.findAndRemovePropertyAccessors(methodRemover, methodListToAppendTo);
222        }
223    }
224
225    /**
226     * Use the provided {@link MethodRemover} to have all known
227     * {@link PropertyOrCollectionIdentifyingFacetFactory}s to remove all
228     * property accessors, and append them to the supplied methodList.
229     * 
230     * @see PropertyOrCollectionIdentifyingFacetFactory#findAndRemoveCollectionAccessors(MethodRemover,
231     *      List)
232     */
233    public void findAndRemoveCollectionAccessors(final MethodRemover methodRemover, final List<Method> methodListToAppendTo) {
234        cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired();
235        for (final PropertyOrCollectionIdentifyingFacetFactory facetFactory : cachedPropertyOrCollectionIdentifyingFactories) {
236            facetFactory.findAndRemoveCollectionAccessors(methodRemover, methodListToAppendTo);
237        }
238    }
239
240    /**
241     * Whether this {@link Method method} is recognized by any of the
242     * {@link FacetFactory}s.
243     * 
244     * <p>
245     * Typically this is when method has a specific prefix, such as
246     * <tt>validate</tt> or <tt>hide</tt>. Specifically, it checks:
247     * <ul>
248     * <li>the method's prefix against the prefixes supplied by any
249     * {@link MethodPrefixBasedFacetFactory}</li>
250     * <li>the method against any {@link MethodFilteringFacetFactory}</li>
251     * </ul>
252     * 
253     * <p>
254     * The design of {@link MethodPrefixBasedFacetFactory} (whereby this facet
255     * factory set does the work) is a slight performance optimization for when
256     * there are multiple facet factories that search for the same prefix.
257     */
258    public boolean recognizes(final Method method) {
259        cacheMethodPrefixesIfRequired();
260        final String methodName = method.getName();
261        for (final String prefix : cachedMethodPrefixes) {
262            if (methodName.startsWith(prefix)) {
263                return true;
264            }
265        }
266
267        cacheMethodFilteringFacetFactoriesIfRequired();
268        for (final MethodFilteringFacetFactory factory : cachedMethodFilteringFactories) {
269            if (factory.recognizes(method)) {
270                return true;
271            }
272        }
273
274        return false;
275    }
276
277    /**
278     * Attaches all facets applicable to the provided {@link FeatureType#OBJECT
279     * object}) to the supplied {@link FacetHolder}.
280     * 
281     * <p>
282     * Delegates to {@link FacetFactory#process(Class, FacetHolder)} for each
283     * appropriate factory.
284     * 
285     * @see FacetFactory#process(ProcessClassContext)
286     * 
287     * @param cls
288     *            - class to process
289     * @param facetHolder
290     *            - holder to attach facets to.
291     */
292    public void process(
293            final Class<?> cls, 
294            final Properties metadataProperties, 
295            final MethodRemover methodRemover, 
296            final FacetHolder facetHolder) {
297        final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.OBJECT);
298        for (final FacetFactory facetFactory : factoryList) {
299            facetFactory.process(new ProcessClassContext(cls, metadataProperties, removerElseNullRemover(methodRemover), facetHolder));
300        }
301    }
302
303    public void processPost(
304            final Class<?> cls, 
305            final Properties metadataProperties, 
306            final MethodRemover methodRemover, 
307            final FacetHolder facetHolder) {
308        final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.OBJECT_POST_PROCESSING);
309        for (final FacetFactory facetFactory : factoryList) {
310            facetFactory.process(new ProcessClassContext(cls, metadataProperties, removerElseNullRemover(methodRemover), facetHolder));
311        }
312    }
313
314    /**
315     * Attaches all facets applicable to the provided {@link FeatureType type of
316     * feature} to the supplied {@link FacetHolder}.
317     * 
318     * <p>
319     * Delegates to {@link FacetFactory#process(Method, FacetHolder)} for each
320     * appropriate factory.
321     * 
322     * @see FacetFactory#process(Method, FacetHolder)
323     * 
324     * @param cls
325     *            - class in which introspect; allowing the helper methods to be
326     *            found is subclasses of that which the method was originally
327     *            found.
328     * @param method
329     *            - method to process
330     * @param facetedMethod
331     *            - holder to attach facets to.
332     * @param featureType
333     *            - what type of feature the method represents (property,
334     *            action, collection etc)
335     * @param metadataProperties 
336     *            - additional properties to parse and use 
337     */
338    public void process(
339            final Class<?> cls, 
340            final Method method, 
341            final MethodRemover methodRemover, 
342            final FacetedMethod facetedMethod, 
343            final FeatureType featureType, 
344            final Properties metadataProperties) {
345        final List<FacetFactory> factoryList = getFactoryListByFeatureType(featureType);
346        for (final FacetFactory facetFactory : factoryList) {
347            facetFactory.process(new ProcessMethodContext(cls, featureType, metadataProperties, method, removerElseNullRemover(methodRemover), facetedMethod));
348        }
349    }
350
351    
352    public void processMemberOrder(
353            final Properties metadataProperties, 
354            final ObjectMember facetHolder) {
355        cacheMemberOrderingFacetFactoriesIfRequired();
356        for (final ContributeeMemberFacetFactory facetFactory : cachedMemberOrderingFactories) {
357            facetFactory.process(new ContributeeMemberFacetFactory.ProcessContributeeMemberContext(metadataProperties, facetHolder));
358        }
359    }
360
361    /**
362     * Attaches all facets applicable to the provided
363     * {@link FeatureType#ACTION_PARAMETER parameter}), to the supplied
364     * {@link FacetHolder}.
365     * 
366     * <p>
367     * Delegates to {@link FacetFactory#processParams(ProcessParameterContext)}
368     * for each appropriate factory.
369     * 
370     * @see FacetFactory#processParams(ProcessParameterContext)
371     * 
372     * @param method
373     *            - action method to process
374     * @param paramNum
375     *            - 0-based
376     * @param facetedMethodParameter
377     *            - holder to attach facets to.
378     */
379    public void processParams(
380            final Method method, 
381            final int paramNum, 
382            final FacetedMethodParameter facetedMethodParameter) {
383        final List<FacetFactory> factoryList = getFactoryListByFeatureType(FeatureType.ACTION_PARAMETER);
384        for (final FacetFactory facetFactory : factoryList) {
385            facetFactory.processParams(new ProcessParameterContext(method, paramNum, facetedMethodParameter));
386        }
387    }
388
389
390    
391    
392    private List<FacetFactory> getFactoryListByFeatureType(final FeatureType featureType) {
393        cacheByFeatureTypeIfRequired();
394        List<FacetFactory> list = factoryListByFeatureType.get(featureType);
395        return list != null? list: Collections.<FacetFactory>emptyList();
396    }
397
398    private void clearCaches() {
399        factoryListByFeatureType = null;
400        cachedMethodPrefixes = null;
401        cachedMethodFilteringFactories = null;
402        cachedPropertyOrCollectionIdentifyingFactories = null;
403    }
404
405    private synchronized void cacheByFeatureTypeIfRequired() {
406        if (factoryListByFeatureType != null) {
407            return;
408        }
409        factoryListByFeatureType = Maps.newHashMap();
410        for (final FacetFactory factory : factories) {
411            final List<FeatureType> featureTypes = factory.getFeatureTypes();
412            for (final FeatureType featureType : featureTypes) {
413                final List<FacetFactory> factoryList = getList(factoryListByFeatureType, featureType);
414                factoryList.add(factory);
415            }
416        }
417    }
418
419    private synchronized void cacheMethodPrefixesIfRequired() {
420        if (cachedMethodPrefixes != null) {
421            return;
422        }
423        cachedMethodPrefixes = Lists.newArrayList();
424        for (final FacetFactory facetFactory : factories) {
425            if (facetFactory instanceof MethodPrefixBasedFacetFactory) {
426                final MethodPrefixBasedFacetFactory methodPrefixBasedFacetFactory = (MethodPrefixBasedFacetFactory) facetFactory;
427                ListExtensions.mergeWith(cachedMethodPrefixes, methodPrefixBasedFacetFactory.getPrefixes());
428            }
429        }
430    }
431
432    private synchronized void cacheMethodFilteringFacetFactoriesIfRequired() {
433        if (cachedMethodFilteringFactories != null) {
434            return;
435        }
436        cachedMethodFilteringFactories = Lists.newArrayList();
437        for (final FacetFactory factory : factories) {
438            if (factory instanceof MethodFilteringFacetFactory) {
439                final MethodFilteringFacetFactory methodFilteringFacetFactory = (MethodFilteringFacetFactory) factory;
440                cachedMethodFilteringFactories.add(methodFilteringFacetFactory);
441            }
442        }
443    }
444
445    private synchronized void cacheMemberOrderingFacetFactoriesIfRequired() {
446        if (cachedMemberOrderingFactories != null) {
447            return;
448        }
449        cachedMemberOrderingFactories = Lists.newArrayList();
450        for (final FacetFactory factory : factories) {
451            if (factory instanceof ContributeeMemberFacetFactory) {
452                final ContributeeMemberFacetFactory memberOrderingFacetFactory = (ContributeeMemberFacetFactory) factory;
453                cachedMemberOrderingFactories.add(memberOrderingFacetFactory);
454            }
455        }
456    }
457    
458    private synchronized void cachePropertyOrCollectionIdentifyingFacetFactoriesIfRequired() {
459        if (cachedPropertyOrCollectionIdentifyingFactories != null) {
460            return;
461        }
462        cachedPropertyOrCollectionIdentifyingFactories = Lists.newArrayList();
463        final Iterator<FacetFactory> iter = factories.iterator();
464        while (iter.hasNext()) {
465            final FacetFactory factory = iter.next();
466            if (factory instanceof PropertyOrCollectionIdentifyingFacetFactory) {
467                final PropertyOrCollectionIdentifyingFacetFactory identifyingFacetFactory = (PropertyOrCollectionIdentifyingFacetFactory) factory;
468                cachedPropertyOrCollectionIdentifyingFactories.add(identifyingFacetFactory);
469            }
470        }
471    }
472
473    private static <K, T> List<T> getList(final Map<K, List<T>> map, final K key) {
474        List<T> list = map.get(key);
475        if (list == null) {
476            list = Lists.newArrayList();
477            map.put(key, list);
478        }
479        return list;
480    }
481
482    private MethodRemover removerElseNullRemover(final MethodRemover methodRemover) {
483        return methodRemover != null ? methodRemover : MethodRemoverConstants.NULL;
484    }
485
486    // ////////////////////////////////////////////////////////////////////
487    // Dependencies (injected in constructor)
488    // ////////////////////////////////////////////////////////////////////
489
490    private IsisConfiguration getIsisConfiguration() {
491        return configuration;
492    }
493
494    private CollectionTypeRegistry getCollectionTypeRepository() {
495        return collectionTypeRegistry;
496    }
497
498    // ////////////////////////////////////////////////////////////////////
499    // Dependencies (injected via setter due to *Aware)
500    // ////////////////////////////////////////////////////////////////////
501
502    private RuntimeContext getRuntimeContext() {
503        return runtimeContext;
504    }
505
506    /**
507     * Injected so can propogate to any {@link #registerFactory(FacetFactory)
508     * registered} {@link FacetFactory} s that are also
509     * {@link RuntimeContextAware}.
510     */
511    @Override
512    public void setRuntimeContext(final RuntimeContext runtimeContext) {
513        this.runtimeContext = runtimeContext;
514    }
515
516
517}