<?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>RoleManagerSupport.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 :: DCI :: Marshal XStream</a> > <a href="../index.html" class="el_bundle">it-tidalwave-role</a> > <a href="index.source.html" class="el_package">it.tidalwave.role.spi</a> > <span class="el_source">RoleManagerSupport.java</span></div><h1>RoleManagerSupport.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 javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.util.annotation.VisibleForTesting;
import it.tidalwave.role.ContextManager;
import it.tidalwave.role.spi.impl.DatumAndRole;
import it.tidalwave.role.spi.impl.MultiMap;
import lombok.extern.slf4j.Slf4j;
import static it.tidalwave.role.spi.impl.LogUtil.*;
/***********************************************************************************************************************
*
* A basic implementation of a {@link RoleManager}. 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="L65">@Slf4j</span>
<span class="fc" id="L66">public abstract class RoleManagerSupport implements RoleManager</span>
{
<span class="fc" id="L68"> @VisibleForTesting final MultiMap<DatumAndRole, Class<?>> roleMapByDatumAndRole = new MultiMap<>();</span>
// FIXME: use ConcurrentHashMap
use ConcurrentHashMap
<span class="fc" id="L71"> @VisibleForTesting final Set<DatumAndRole> 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="L81"> log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));</span>
<span class="fc" id="L82"> final Class<?> datumType = findTypeOf(datum);</span>
<span class="fc" id="L83"> final List<T> roles = new ArrayList<>();</span>
<span class="fc" id="L84"> final var roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);</span>
<span class="fc bfc" id="L86" title="All 2 branches covered."> outer: for (final var roleImplementationType : roleImplementationTypes)</span>
{
<span class="pc bpc" id="L88" title="1 of 2 branches missed."> for (final var constructor : roleImplementationType.getDeclaredConstructors())</span>
{
<span class="fc" id="L90"> log.trace(">>>> trying constructor {}", constructor);</span>
<span class="fc" id="L91"> final var parameterTypes = constructor.getParameterTypes();</span>
<span class="fc" id="L92"> Class<?> contextType = null;</span>
<span class="fc" id="L93"> Object context = null;</span>
try
{
<span class="fc" id="L97"> contextType = findContextTypeForRole(roleImplementationType);</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.Locator.find();</span>
<span class="fc" id="L100"> log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));</span>
try
{
<span class="fc" id="L104"> context = contextManager.findContextOfType(contextType);</span>
}
<span class="fc" id="L106"> catch (NotFoundException e)</span>
{
<span class="fc" id="L108"> log.trace(">>>> role {} discarded, can't find context: {}",</span>
<span class="fc" id="L109"> shortName(roleImplementationType), shortName(contextType));</span>
<span class="fc" id="L110"> continue outer;</span>
<span class="fc" id="L111"> }</span>
}
<span class="fc" id="L113"> catch (NotFoundException e)</span>
{
// ok, no context
<span class="fc" id="L116"> }</span>
try
{
<span class="fc" id="L120"> final var params = getParameterValues(parameterTypes, datumType, datum, contextType, context);</span>
<span class="fc" id="L121"> roles.add(roleType.cast(constructor.newInstance(params)));</span>
<span class="fc" id="L122"> break;</span>
}
<span class="nc" id="L124"> catch (InstantiationException | IllegalAccessException</span>
| IllegalArgumentException | InvocationTargetException e)
{
<span class="nc" id="L127"> log.error("Could not instantiate role of type " + roleImplementationType, e);</span>
}
}
<span class="fc" id="L130"> }</span>
<span class="pc bpc" id="L132" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L134"> log.trace(">>>> findRoles() returning: {}", shortIds(roles));</span>
}
<span class="fc" id="L137"> 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,
@Nullable final Class<?> contextClass,
@Nullable final Object context)
{
<span class="fc" id="L159"> final List<Object> values = new ArrayList<>();</span>
<span class="fc bfc" id="L161" title="All 2 branches covered."> for (final var parameterType : parameterTypes)</span>
{
<span class="fc bfc" id="L163" title="All 2 branches covered."> if (parameterType.isAssignableFrom(datumClass))</span>
{
<span class="fc" id="L165"> values.add(datum);</span>
}
<span class="fc bfc" id="L167" title="All 4 branches covered."> else if ((contextClass != null) && parameterType.isAssignableFrom(contextClass))</span>
{
<span class="fc" id="L169"> values.add(context);</span>
}
else // generic injection
{
<span class="fc" id="L173"> values.add(getBean(parameterType));</span>
}
}
<span class="fc" id="L177"> log.trace(">>>> constructor parameters: {}", values);</span>
<span class="fc" id="L178"> 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="L199"> final var datumAndRole = new DatumAndRole(datumType, roleType);</span>
<span class="fc bfc" id="L201" title="All 2 branches covered."> if (!alreadyScanned.contains(datumAndRole))</span>
{
<span class="fc" id="L203"> alreadyScanned.add(datumAndRole);</span>
<span class="fc" id="L204"> final var before = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc bfc" id="L206" title="All 2 branches covered."> for (final var superDatumAndRole : datumAndRole.getSuper())</span>
{
<span class="fc" id="L208"> roleMapByDatumAndRole.addAll(datumAndRole, roleMapByDatumAndRole.getValues(superDatumAndRole));</span>
<span class="fc" id="L209"> }</span>
<span class="fc" id="L211"> final var after = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc" id="L212"> logChanges(datumAndRole, before, after);</span>
}
<span class="fc" id="L215"> return (Set<Class<? extends T>>)(Set)roleMapByDatumAndRole.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="L227"> log.debug("scan({})", shortNames(roleImplementationTypes));</span>
<span class="fc bfc" id="L229" title="All 2 branches covered."> for (final var roleImplementationType : roleImplementationTypes)</span>
{
<span class="fc bfc" id="L231" title="All 2 branches covered."> for (final var datumType : findDatumTypesForRole(roleImplementationType))</span>
{
<span class="fc bfc" id="L233" title="All 2 branches covered."> for (final var roleType : findAllImplementedInterfacesOf(roleImplementationType))</span>
{
<span class="pc bpc" id="L235" title="1 of 2 branches missed."> if (!"org.springframework.beans.factory.aspectj.ConfigurableObject".equals(roleType.getName()))</span>
{
<span class="fc" id="L237"> roleMapByDatumAndRole.add(new DatumAndRole(datumType, roleType), roleImplementationType);</span>
}
<span class="fc" id="L239"> }</span>
}
<span class="fc" id="L241"> }</span>
<span class="fc" id="L243"> logRoles();</span>
<span class="fc" id="L244"> }</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="L258"> final SortedSet<Class<?>> interfaces = new TreeSet<>(Comparator.comparing(Class::getName));</span>
<span class="fc" id="L259"> interfaces.addAll(List.of(clazz.getInterfaces()));</span>
<span class="fc bfc" id="L261" title="All 2 branches covered."> for (final var interface_ : interfaces)</span>
{
<span class="fc" id="L263"> interfaces.addAll(findAllImplementedInterfacesOf(interface_));</span>
<span class="fc" id="L264"> }</span>
<span class="fc bfc" id="L266" title="All 2 branches covered."> if (clazz.getSuperclass() != null)</span>
{
<span class="fc" id="L268"> interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));</span>
}
<span class="fc" id="L271"> 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
*
******************************************************************************************************************/
@Nullable
protected abstract <T> T getBean (@Nonnull Class<T> beanType);
/*******************************************************************************************************************
*
* Returns the type of the context associated to the given role implementation type.
*
* @param roleImplementationType the role type
* @return the context type
* @throws NotFoundException if no context is found
*
******************************************************************************************************************/
@Nonnull
protected abstract Class<?> findContextTypeForRole (@Nonnull Class<?> roleImplementationType)
throws NotFoundException;
/*******************************************************************************************************************
*
* Returns the valid datum types for the given role implementation type.
*
* @param roleImplementationType the role type
* @return the datum types
*
******************************************************************************************************************/
@Nonnull
protected abstract Class<?>[] findDatumTypesForRole (@Nonnull Class<?> roleImplementationType);
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
private void logChanges (@Nonnull final DatumAndRole datumAndRole,
@Nonnull final Set<Class<?>> before,
@Nonnull final Set<Class<?>> after)
{
<span class="fc" id="L318"> after.removeAll(before);</span>
<span class="fc bfc" id="L320" title="All 2 branches covered."> if (!after.isEmpty())</span>
{
<span class="fc" id="L322"> log.debug(">>>>>>> added implementations: {} -> {}", datumAndRole, shortNames(after));</span>
<span class="pc bpc" id="L324" title="1 of 2 branches missed."> if (log.isTraceEnabled()) // yes, trace</span>
{
<span class="fc" id="L326"> logRoles();</span>
}
}
<span class="fc" id="L329"> }</span>
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void logRoles()
{
<span class="fc" id="L337"> log.debug("Configured roles:");</span>
<span class="fc" id="L339"> final List<Entry<DatumAndRole, Set<Class<?>>>> entries = new ArrayList<>(roleMapByDatumAndRole.entrySet());</span>
<span class="fc" id="L340"> entries.sort(Comparator.comparing((Entry<DatumAndRole, Set<Class<?>>> e) -> e.getKey()</span>
<span class="fc" id="L341"> .getDatumClass()</span>
<span class="fc" id="L342"> .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().getDatumClass()),</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="fc" id="L374"> log.trace(">>>> owner is a mock {} implementing {}",</span>
<span class="fc" id="L375"> shortName(ownerClass), shortNames(List.of(ownerClass.getInterfaces())));</span>
<span class="fc" 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>