<?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 - Roles</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">/*
* #%L
* *********************************************************************************************************************
*
* These Foolish Things - Miscellaneous utilities
* http://thesefoolishthings.tidalwave.it - git clone git@bitbucket.org:tidalwave/thesefoolishthings-src.git
* %%
* Copyright (C) 2009 - 2021 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.
*
* *********************************************************************************************************************
*
* $Id$
*
* *********************************************************************************************************************
* #L%
*/
package it.tidalwave.role.spi;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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.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.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
* @version $Id$
*
**********************************************************************************************************************/
<span class="fc" id="L69">@Slf4j</span>
<span class="fc" id="L70">public abstract class RoleManagerSupport implements RoleManager</span>
{
<span class="fc" id="L72"> private final static Comparator<Class<?>> CLASS_COMPARATOR = new Comparator<Class<?>>()</span>
<span class="fc" id="L73"> {</span>
@Override
public int compare (final @Nonnull Class<?> class1, final @Nonnull Class<?> class2)
{
<span class="fc" id="L77"> return class1.getName().compareTo(class2.getName());</span>
}
};
<span class="fc" id="L81"> private final ContextManager contextManager = ContextManager.Locator.find();</span>
<span class="fc" id="L83"> /* VisibleForTesting */ final MultiMap<DatumAndRole, Class<?>> roleMapByDatumAndRole = new MultiMap<>();</span>
// FIXME: use ConcurrentHashMap// FIXME: use ConcurrentHashMap
use ConcurrentHashMap
<span class="fc" id="L86"> /* VisibleForTesting */ final Set<DatumAndRole> alreadyScanned = new HashSet<>();</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override @Nonnull
public <ROLE_TYPE> List<? extends ROLE_TYPE> findRoles (final @Nonnull Object datum,
final @Nonnull Class<ROLE_TYPE> roleType)
{
<span class="fc" id="L97"> log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));</span>
<span class="fc" id="L98"> final Class<?> datumType = findTypeOf(datum);</span>
<span class="fc" id="L99"> final List<ROLE_TYPE> roles = new ArrayList<>();</span>
<span class="fc" id="L100"> final Set<Class<? extends ROLE_TYPE>> roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);</span>
<span class="fc bfc" id="L102" title="All 2 branches covered.">outer: for (final Class<? extends ROLE_TYPE> roleImplementationType : roleImplementationTypes)</span>
{
<span class="pc bpc" id="L104" title="1 of 2 branches missed."> for (final Constructor<?> constructor : roleImplementationType.getDeclaredConstructors())</span>
{
<span class="fc" id="L106"> log.trace(">>>> trying constructor {}", constructor);</span>
<span class="fc" id="L107"> final Class<?>[] parameterTypes = constructor.getParameterTypes();</span>
<span class="fc" id="L108"> Class<?> contextType = null;</span>
<span class="fc" id="L109"> Object context = null;</span>
try
{
<span class="fc" id="L113"> contextType = findContextTypeForRole(roleImplementationType);</span>
<span class="fc" id="L114"> log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));</span>
try
{
<span class="fc" id="L118"> context = contextManager.findContextOfType(contextType);</span>
}
<span class="fc" id="L120"> catch (NotFoundException e)</span>
{
<span class="fc" id="L122"> log.trace(">>>> role {} discarded, can't find context: {}",</span>
<span class="fc" id="L123"> shortName(roleImplementationType), shortName(contextType));</span>
<span class="fc" id="L124"> continue outer;</span>
<span class="fc" id="L125"> }</span>
}
<span class="fc" id="L127"> catch (NotFoundException e)</span>
{
// ok, no context
<span class="fc" id="L130"> }</span>
try
{
<span class="fc" id="L134"> final Object[] params = getParameterValues(parameterTypes, datumType, datum, contextType, context);</span>
<span class="fc" id="L135"> roles.add(roleType.cast(constructor.newInstance(params)));</span>
<span class="fc" id="L136"> break;</span>
}
<span class="nc" id="L138"> catch (InstantiationException | IllegalAccessException</span>
| IllegalArgumentException | InvocationTargetException e)
{
<span class="nc" id="L141"> log.error("Could not instantiate role of type " + roleImplementationType, e);</span>
}
}
<span class="fc" id="L144"> }</span>
<span class="pc bpc" id="L146" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L148"> log.trace(">>>> findRoles() returning: {}", shortIds((Collection)roles));</span>
}
<span class="fc" id="L151"> 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 (final @Nonnull Class<?>[] parameterTypes,
final @Nonnull Class<?> datumClass,
final @Nonnull Object datum,
final @Nullable Class<?> contextClass,
final @Nullable Object context)
{
<span class="fc" id="L173"> final List<Object> values = new ArrayList<>();</span>
<span class="fc bfc" id="L175" title="All 2 branches covered."> for (Class<?> parameterType : parameterTypes)</span>
{
<span class="fc bfc" id="L177" title="All 2 branches covered."> if (parameterType.isAssignableFrom(datumClass))</span>
{
<span class="fc" id="L179"> values.add(datum);</span>
}
<span class="fc bfc" id="L181" title="All 4 branches covered."> else if ((contextClass != null) && parameterType.isAssignableFrom(contextClass))</span>
{
<span class="fc" id="L183"> values.add(context);</span>
}
else // generic injection
{
<span class="fc" id="L187"> values.add(getBean(parameterType));</span>
}
}
<span class="fc" id="L191"> log.trace(">>>> constructor parameters: {}", values);</span>
<span class="fc" id="L192"> 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
* @param the types of role implementations
*
******************************************************************************************************************/
@Nonnull
/* VisibleForTesting */ synchronized <RT> Set<Class<? extends RT>> findRoleImplementationsFor (
final @Nonnull Class<?> datumType,
final @Nonnull Class<RT> roleType)
{
<span class="fc" id="L213"> final DatumAndRole datumAndRole = new DatumAndRole(datumType, roleType);</span>
<span class="pc bpc" id="L215" title="1 of 2 branches missed."> if (!alreadyScanned.contains(datumAndRole))</span>
{
<span class="fc" id="L217"> alreadyScanned.add(datumAndRole);</span>
<span class="fc" id="L218"> final Set<Class<?>> before = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc bfc" id="L220" title="All 2 branches covered."> for (final DatumAndRole superDatumAndRole : datumAndRole.getSuper())</span>
{
<span class="fc" id="L222"> roleMapByDatumAndRole.addAll(datumAndRole, roleMapByDatumAndRole.getValues(superDatumAndRole));</span>
<span class="fc" id="L223"> }</span>
<span class="fc" id="L225"> final Set<Class<?>> after = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc" id="L226"> logChanges(datumAndRole, before, after);</span>
}
<span class="fc" id="L229"> return (Set<Class<? extends RT>>)(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 void scan (final @Nonnull Collection<Class<?>> roleImplementationTypes)
{
<span class="fc" id="L241"> log.debug("scan({})", shortNames(roleImplementationTypes));</span>
<span class="fc bfc" id="L243" title="All 2 branches covered."> for (final Class<?> roleImplementationType : roleImplementationTypes)</span>
{
<span class="fc bfc" id="L245" title="All 2 branches covered."> for (final Class<?> datumType : findDatumTypesForRole(roleImplementationType))</span>
{
<span class="fc bfc" id="L247" title="All 2 branches covered."> for (final Class<?> roleType : findAllImplementedInterfacesOf(roleImplementationType))</span>
{
<span class="pc bpc" id="L249" title="1 of 2 branches missed."> if (!roleType.getName().equals("org.springframework.beans.factory.aspectj.ConfigurableObject"))</span>
{
<span class="fc" id="L251"> roleMapByDatumAndRole.add(new DatumAndRole(datumType, roleType), roleImplementationType);</span>
}
<span class="fc" id="L253"> }</span>
}
<span class="fc" id="L255"> }</span>
<span class="fc" id="L257"> logRoles();</span>
<span class="fc" id="L258"> }</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 (final @Nonnull Class<?> clazz)
{
<span class="fc" id="L272"> final SortedSet<Class<?>> interfaces = new TreeSet<>(CLASS_COMPARATOR);</span>
<span class="fc" id="L273"> interfaces.addAll(Arrays.asList(clazz.getInterfaces()));</span>
<span class="fc bfc" id="L275" title="All 2 branches covered."> for (final Class<?> interface_ : interfaces)</span>
{
<span class="fc" id="L277"> interfaces.addAll(findAllImplementedInterfacesOf(interface_));</span>
<span class="fc" id="L278"> }</span>
<span class="fc bfc" id="L280" title="All 2 branches covered."> if (clazz.getSuperclass() != null)</span>
{
<span class="fc" id="L282"> interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));</span>
}
<span class="fc" id="L285"> 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 (final @Nonnull DatumAndRole datumAndRole,
final @Nonnull Set<Class<?>> before,
final @Nonnull Set<Class<?>> after)
{
<span class="fc" id="L332"> after.removeAll(before);</span>
<span class="fc bfc" id="L334" title="All 2 branches covered."> if (!after.isEmpty())</span>
{
<span class="fc" id="L336"> log.debug(">>>>>>> added implementations: {} -> {}", datumAndRole, after);</span>
<span class="pc bpc" id="L338" title="1 of 2 branches missed."> if (log.isTraceEnabled()) // yes, trace</span>
{
<span class="fc" id="L340"> logRoles();</span>
}
}
<span class="fc" id="L343"> }</span>
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void logRoles()
{
<span class="fc" id="L351"> log.debug("Configured roles:");</span>
<span class="fc" id="L353"> final List<Entry<DatumAndRole, Set<Class<?>>>> entries = new ArrayList<>(roleMapByDatumAndRole.entrySet());</span>
<span class="fc" id="L354"> Collections.sort(entries, new Comparator<Entry<DatumAndRole, Set<Class<?>>>>()</span>
<span class="fc" id="L355"> {</span>
@Override
public int compare (final @Nonnull Entry<DatumAndRole, Set<Class<?>>> e1,
final @Nonnull Entry<DatumAndRole, Set<Class<?>>> e2)
{
<span class="fc" id="L360"> final int s1 = e1.getKey().getDatumClass().getName().compareTo(</span>
<span class="fc" id="L361"> e2.getKey().getDatumClass().getName());</span>
<span class="fc bfc" id="L363" title="All 2 branches covered."> if (s1 != 0)</span>
{
<span class="fc" id="L365"> return s1;</span>
}
<span class="fc" id="L368"> return e1.getKey().getRoleClass().getName().compareTo(</span>
<span class="fc" id="L369"> e2.getKey().getRoleClass().getName());</span>
}
});
<span class="fc bfc" id="L373" title="All 2 branches covered."> for (final Entry<DatumAndRole, Set<Class<?>>> entry : entries)</span>
{
<span class="fc" id="L375"> log.debug(">>>> {}: {} -> {}",</span>
<span class="fc" id="L376"> new Object[] { shortName(entry.getKey().getDatumClass()),</span>
<span class="fc" id="L377"> shortName(entry.getKey().getRoleClass()),</span>
<span class="fc" id="L378"> shortNames(entry.getValue())});</span>
<span class="fc" id="L379"> }</span>
<span class="fc" id="L380"> }</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 (final @Nonnull T object)
{
<span class="fc" id="L394"> Class<?> ownerClass = object.getClass();</span>
<span class="fc bfc" id="L396" title="All 2 branches covered."> if (ownerClass.toString().contains("MockitoMock"))</span>
{
<span class="fc" id="L398"> ownerClass = ownerClass.getInterfaces()[0]; // 1st is the original class, 2nd is CGLIB proxy</span>
<span class="pc bpc" id="L400" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L402"> log.trace(">>>> owner is a mock {} implementing {}",</span>
<span class="fc" id="L403"> shortName(ownerClass), shortNames(Arrays.asList(ownerClass.getInterfaces())));</span>
<span class="fc" id="L404"> log.trace(">>>> owner class replaced with {}", shortName(ownerClass));</span>
}
}
<span class="fc" id="L408"> 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.6.202009150832</span></div></body></html>