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}