Content of file SystemRoleFactorySupport.java.html
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" lang="en"><head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/><link rel="stylesheet" href="../../jacoco-resources/report.css" type="text/css"/><link rel="shortcut icon" href="../../jacoco-resources/report.gif" type="image/gif"/><title>SystemRoleFactorySupport.java</title><link rel="stylesheet" href="../../jacoco-resources/prettify.css" type="text/css"/><script type="text/javascript" src="../../jacoco-resources/prettify.js"></script></head><body onload="window['PR_TAB_WIDTH']=4;prettyPrint()"><div class="breadcrumb" id="breadcrumb"><span class="info"><a href="../../jacoco-sessions.html" class="el_session">Sessions</a></span><a href="../../index.html" class="el_report">TheseFoolishThings :: Examples :: Finder :: In-memory Finder</a> > <a href="../index.html" class="el_bundle">it-tidalwave-util</a> > <a href="index.source.html" class="el_package">it.tidalwave.role.spi</a> > <span class="el_source">SystemRoleFactorySupport.java</span></div><h1>SystemRoleFactorySupport.java</h1><pre class="source lang-java linenums">/*
* *********************************************************************************************************************
*
* TheseFoolishThings: Miscellaneous utilities
* http://tidalwave.it/projects/thesefoolishthings
*
* Copyright (C) 2009 - 2023 by Tidalwave s.a.s. (http://tidalwave.it)
*
* *********************************************************************************************************************
*
* Licensed 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.
*
* *********************************************************************************************************************
*
* git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
* git clone https://github.com/tidalwave-it/thesefoolishthings-src
*
* *********************************************************************************************************************
*/
package it.tidalwave.role.spi;
import java.lang.reflect.InvocationTargetException;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import it.tidalwave.util.ContextManager;
import it.tidalwave.util.annotation.VisibleForTesting;
import it.tidalwave.role.impl.OwnerAndRole;
import it.tidalwave.role.impl.MultiMap;
import it.tidalwave.dci.annotation.DciRole;
import lombok.extern.slf4j.Slf4j;
import static java.util.Comparator.*;
import static it.tidalwave.util.ShortNames.*;
/***********************************************************************************************************************
*
* A basic implementation of a {@link SystemRoleFactory}. This class must be specialized to:
*
* <ol>
* <li>discover roles (see {@link #scan(java.util.Collection)}</li>
* <li>associate roles to a datum (see {@link #findDatumTypesForRole(java.lang.Class)}</li>
* <li>associate roles to contexts (see {@link #findContextTypeForRole(java.lang.Class)}</li>
* <li>eventually retrieve beans to inject in created roles (see {@link #getBean(java.lang.Class)}</li>
* </ol>
*
* Specializations might use annotations or configuration files to accomplish these tasks.
*
* @author Fabrizio Giudici
*
**********************************************************************************************************************/
<span class="fc" id="L66">@Slf4j</span>
<span class="fc" id="L67">public abstract class SystemRoleFactorySupport implements SystemRoleFactory</span>
{
<span class="fc" id="L69"> @VisibleForTesting final MultiMap<OwnerAndRole, Class<?>> roleMapByOwnerAndRole = new MultiMap<>();</span>
// FIXME: use ConcurrentHashMap
<span class="fc" id="L72"> @VisibleForTesting final Set<OwnerAndRole> alreadyScanned = new HashSet<>();</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override @Nonnull
public synchronized <T> List<T> findRoles (@Nonnull final Object datum, @Nonnull final Class<? extends T> roleType)
{
<span class="fc" id="L82"> log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));</span>
<span class="fc" id="L83"> final Class<?> datumType = findTypeOf(datum);</span>
<span class="fc" id="L84"> final List<T> roles = new ArrayList<>();</span>
<span class="fc" id="L85"> final var roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);</span>
<span class="fc bfc" id="L87" title="All 2 branches covered."> outer: for (final var roleImplementationType : roleImplementationTypes)</span>
{
<span class="pc bpc" id="L89" title="1 of 2 branches missed."> for (final var constructor : roleImplementationType.getDeclaredConstructors())</span>
{
<span class="fc" id="L91"> log.trace(">>>> trying constructor {}", constructor);</span>
<span class="fc" id="L92"> final var parameterTypes = constructor.getParameterTypes();</span>
<span class="fc" id="L93"> Optional<?> context = Optional.empty();</span>
<span class="fc" id="L94"> final var contextType = findContextTypeForRole(roleImplementationType);</span>
<span class="fc bfc" id="L96" title="All 2 branches covered."> if (contextType.isPresent())</span>
{
// With DI frameworks such as Spring it's better to avoid eager initializations of references
<span class="fc" id="L99"> final var contextManager = ContextManager.getInstance();</span>
<span class="fc" id="L100"> log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));</span>
<span class="fc" id="L101"> context = contextManager.findContextOfType(contextType.get());</span>
<span class="fc bfc" id="L103" title="All 2 branches covered."> if (context.isEmpty())</span>
{
<span class="fc" id="L105"> log.trace(">>>> role {} discarded, can't find context: {}",</span>
<span class="fc" id="L106"> shortName(roleImplementationType), shortName(contextType.get()));</span>
<span class="fc" id="L107"> continue outer;</span>
}
}
try
{
<span class="fc" id="L113"> final var params = getParameterValues(parameterTypes, datumType, datum, contextType, context);</span>
<span class="fc" id="L114"> roles.add(roleType.cast(constructor.newInstance(params)));</span>
<span class="fc" id="L115"> break;</span>
}
<span class="nc" id="L117"> catch (InstantiationException | IllegalAccessException</span>
| IllegalArgumentException | InvocationTargetException e)
{
<span class="nc" id="L120"> log.error("Could not instantiate role of type " + roleImplementationType, e);</span>
}
}
<span class="fc" id="L123"> }</span>
<span class="pc bpc" id="L125" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="nc" id="L127"> log.trace(">>>> findRoles() returning: {}", shortIds(roles));</span>
}
<span class="fc" id="L130"> return roles;</span>
}
/*******************************************************************************************************************
*
* Prepare the constructor parameters out of the given expected types. Parameters will be eventually made of the
* given datum, context, and other objects returned by {@link #getBean(java.lang.Class)}.
*
* @param parameterTypes the expected types
* @param datumClass the type of the datum
* @param datum the datum
* @param contextClass the type of the context
* @param context the context
*
******************************************************************************************************************/
@Nonnull
private Object[] getParameterValues (@Nonnull final Class<?>[] parameterTypes,
@Nonnull final Class<?> datumClass,
@Nonnull final Object datum,
@Nonnull final Optional<Class<?>> contextClass,
@Nonnull final Optional<?> context)
{
<span class="fc" id="L152"> final var values = new ArrayList<>();</span>
<span class="fc bfc" id="L154" title="All 2 branches covered."> for (final var parameterType : parameterTypes)</span>
{
<span class="fc bfc" id="L156" title="All 2 branches covered."> if (parameterType.isAssignableFrom(datumClass))</span>
{
<span class="fc" id="L158"> values.add(datum);</span>
}
<span class="fc bfc" id="L160" title="All 4 branches covered."> else if (contextClass.isPresent() && parameterType.isAssignableFrom(contextClass.get()))</span>
{
<span class="fc" id="L162"> values.add(context.orElse(null));</span>
}
else // generic injection
{
// FIXME: it's injecting null, but perhaps should it throw exception?
it's injecting null, but perhaps should it throw exception?
<span class="fc" id="L167"> values.add(getBean(parameterType).orElse(null));</span>
}
}
<span class="fc" id="L171"> log.trace(">>>> constructor parameters: {}", values);</span>
<span class="fc" id="L172"> return values.toArray();</span>
}
/*******************************************************************************************************************
*
* Finds the role implementations for the given owner type and role type. This method might discover new
* implementations that weren't found during the initial scan, since the initial scan can't go down in a
* hierarchy; that is, given a Base class or interface with some associated roles, it can't associate those roles
* to subclasses (or implementations) of Base. Now we can navigate up the hierarchy and complete the picture.
* Each new discovered role is added into the map, so the next time scanning will be faster.
*
* @param datumType the type of the datum
* @param roleType the type of the role to find
* @return the types of role implementations
*
******************************************************************************************************************/
@Nonnull
@VisibleForTesting synchronized <T> Set<Class<? extends T>> findRoleImplementationsFor (
@Nonnull final Class<?> datumType,
@Nonnull final Class<T> roleType)
{
<span class="fc" id="L193"> final var datumAndRole = new OwnerAndRole(datumType, roleType);</span>
<span class="pc bpc" id="L195" title="1 of 2 branches missed."> if (!alreadyScanned.contains(datumAndRole))</span>
{
<span class="fc" id="L197"> alreadyScanned.add(datumAndRole);</span>
<span class="fc" id="L198"> final var before = new HashSet<>(roleMapByOwnerAndRole.getValues(datumAndRole));</span>
<span class="fc bfc" id="L200" title="All 2 branches covered."> for (final var superDatumAndRole : datumAndRole.getSuper())</span>
{
<span class="fc" id="L202"> roleMapByOwnerAndRole.addAll(datumAndRole, roleMapByOwnerAndRole.getValues(superDatumAndRole));</span>
<span class="fc" id="L203"> }</span>
<span class="fc" id="L205"> final var after = new HashSet<>(roleMapByOwnerAndRole.getValues(datumAndRole));</span>
<span class="fc" id="L206"> logChanges(datumAndRole, before, after);</span>
}
<span class="fc" id="L209"> return (Set<Class<? extends T>>)(Set)roleMapByOwnerAndRole.getValues(datumAndRole);</span>
}
/*******************************************************************************************************************
*
* Scans all the given role implementation classes and build a map of roles by owner class.
*
* @param roleImplementationTypes the types of role implementations to scan
*
******************************************************************************************************************/
protected synchronized void scan (@Nonnull final Collection<Class<?>> roleImplementationTypes)
{
<span class="fc" id="L221"> log.debug("scan({})", shortNames(roleImplementationTypes));</span>
<span class="fc bfc" id="L223" title="All 2 branches covered."> for (final var roleImplementationType : roleImplementationTypes)</span>
{
<span class="fc bfc" id="L225" title="All 2 branches covered."> for (final var datumType : findDatumTypesForRole(roleImplementationType))</span>
{
<span class="fc bfc" id="L227" title="All 2 branches covered."> for (final var roleType : findAllImplementedInterfacesOf(roleImplementationType))</span>
{
<span class="pc bpc" id="L229" title="1 of 2 branches missed."> if (!"org.springframework.beans.factory.aspectj.ConfigurableObject".equals(roleType.getName()))</span>
{
<span class="fc" id="L231"> roleMapByOwnerAndRole.add(new OwnerAndRole(datumType, roleType), roleImplementationType);</span>
}
<span class="fc" id="L233"> }</span>
}
<span class="fc" id="L235"> }</span>
<span class="fc" id="L237"> logRoles();</span>
<span class="fc" id="L238"> }</span>
/*******************************************************************************************************************
*
* Finds all the interfaces implemented by a given class, including those eventually implemented by superclasses
* and interfaces that are indirectly implemented (e.g. C implements I1, I1 extends I2).
*
* @param clazz the class to inspect
* @return the implemented interfaces
*
******************************************************************************************************************/
@Nonnull
@VisibleForTesting static SortedSet<Class<?>> findAllImplementedInterfacesOf (@Nonnull final Class<?> clazz)
{
<span class="fc" id="L252"> final SortedSet<Class<?>> interfaces = new TreeSet<>(comparing(Class::getName));</span>
<span class="fc" id="L253"> interfaces.addAll(List.of(clazz.getInterfaces()));</span>
<span class="fc bfc" id="L255" title="All 2 branches covered."> for (final var interface_ : interfaces)</span>
{
<span class="fc" id="L257"> interfaces.addAll(findAllImplementedInterfacesOf(interface_));</span>
<span class="fc" id="L258"> }</span>
<span class="fc bfc" id="L260" title="All 2 branches covered."> if (clazz.getSuperclass() != null)</span>
{
<span class="fc" id="L262"> interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));</span>
}
<span class="fc" id="L265"> return interfaces;</span>
}
/*******************************************************************************************************************
*
* Retrieves an extra bean.
*
* @param <T> the static type of the bean
* @param beanType the dynamic type of the bean
* @return the bean
*
******************************************************************************************************************/
@Nonnull
protected <T> Optional<T> getBean (@Nonnull final Class<T> beanType)
{
<span class="nc" id="L280"> return Optional.empty();</span>
}
/*******************************************************************************************************************
*
* Returns the type of the context associated to the given role implementation type.
*
* @param roleImplementationType the role type
* @return the context type
*
******************************************************************************************************************/
@Nonnull
protected Optional<Class<?>> findContextTypeForRole (@Nonnull final Class<?> roleImplementationType)
{
<span class="nc" id="L294"> final var contextClass = roleImplementationType.getAnnotation(DciRole.class).context();</span>
<span class="nc bnc" id="L295" title="All 2 branches missed."> return (contextClass == DciRole.NoContext.class) ? Optional.empty() : Optional.of(contextClass);</span>
}
/*******************************************************************************************************************
*
* Returns the valid datum types for the given role implementation type.
*
* @param roleImplementationType the role type
* @return the datum types
*
******************************************************************************************************************/
@Nonnull
protected Class<?>[] findDatumTypesForRole (@Nonnull final Class<?> roleImplementationType)
{
<span class="nc" id="L309"> return roleImplementationType.getAnnotation(DciRole.class).datumType();</span>
}
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
private void logChanges (@Nonnull final OwnerAndRole ownerAndRole,
@Nonnull final Set<Class<?>> before,
@Nonnull final Set<Class<?>> after)
{
<span class="fc" id="L320"> after.removeAll(before);</span>
<span class="fc bfc" id="L322" title="All 2 branches covered."> if (!after.isEmpty())</span>
{
<span class="fc" id="L324"> log.debug(">>>>>>> added implementations: {} -> {}", ownerAndRole, shortNames(after));</span>
<span class="pc bpc" id="L326" title="1 of 2 branches missed."> if (log.isTraceEnabled()) // yes, trace</span>
{
<span class="nc" id="L328"> logRoles();</span>
}
}
<span class="fc" id="L331"> }</span>
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void logRoles()
{
<span class="fc" id="L339"> log.debug("Configured roles:");</span>
<span class="fc" id="L341"> final var entries = new ArrayList<>(roleMapByOwnerAndRole.entrySet());</span>
<span class="fc" id="L342"> entries.sort(comparing((Map.Entry<OwnerAndRole, Set<Class<?>>> e) -> e.getKey().getOwnerClass().getName())</span>
<span class="fc" id="L343"> .thenComparing(e -> e.getKey().getRoleClass().getName()));</span>
<span class="fc bfc" id="L345" title="All 2 branches covered."> for (final var entry : entries)</span>
{
<span class="fc" id="L347"> log.debug(">>>> {}: {} -> {}",</span>
<span class="fc" id="L348"> shortName(entry.getKey().getOwnerClass()),</span>
<span class="fc" id="L349"> shortName(entry.getKey().getRoleClass()),</span>
<span class="fc" id="L350"> shortNames(entry.getValue()));</span>
<span class="fc" id="L351"> }</span>
<span class="fc" id="L352"> }</span>
/*******************************************************************************************************************
*
* Returns the type of an object, taking care of mocks created by Mockito, for which the implemented interface is
* returned.
*
* @param object the object
* @return the object type
*
******************************************************************************************************************/
@Nonnull
@VisibleForTesting static <T> Class<T> findTypeOf (@Nonnull final T object)
{
<span class="fc" id="L366"> var ownerClass = object.getClass();</span>
<span class="fc bfc" id="L368" title="All 2 branches covered."> if (ownerClass.toString().contains("MockitoMock"))</span>
{
<span class="fc" id="L370"> ownerClass = ownerClass.getInterfaces()[0]; // 1st is the original class, 2nd is CGLIB proxy</span>
<span class="pc bpc" id="L372" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="nc" id="L374"> log.trace(">>>> owner is a mock {} implementing {}",</span>
<span class="nc" id="L375"> shortName(ownerClass), shortNames(List.of(ownerClass.getInterfaces())));</span>
<span class="nc" id="L376"> log.trace(">>>> owner class replaced with {}", shortName(ownerClass));</span>
}
}
<span class="fc" id="L380"> return (Class<T>)ownerClass;</span>
}
}
</pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.7.202105040129</span></div></body></html>