<?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.
*
* *********************************************************************************************************************
*
*
* *********************************************************************************************************************
* #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.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="L67">@Slf4j</span>
<span class="fc" id="L68">public abstract class RoleManagerSupport implements RoleManager</span>
{
<span class="fc" id="L70"> private final static Comparator<Class<?>> CLASS_COMPARATOR = new Comparator<Class<?>>()</span>
<span class="fc" id="L71"> {</span>
@Override
public int compare (final @Nonnull Class<?> class1, final @Nonnull Class<?> class2)
{
<span class="fc" id="L75"> return class1.getName().compareTo(class2.getName());</span>
}
};
<span class="fc" id="L79"> private final ContextManager contextManager = ContextManager.Locator.find();</span>
<span class="fc" id="L81"> /* VisibleForTesting */ final MultiMap<DatumAndRole, Class<?>> roleMapByDatumAndRole = new MultiMap<>();</span>
// FIXME: use ConcurrentHashMap// FIXME: use ConcurrentHashMap
use ConcurrentHashMap
<span class="fc" id="L84"> /* 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="L95"> log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));</span>
<span class="fc" id="L96"> final Class<?> datumType = findTypeOf(datum);</span>
<span class="fc" id="L97"> final List<ROLE_TYPE> roles = new ArrayList<>();</span>
<span class="fc" id="L98"> final Set<Class<? extends ROLE_TYPE>> roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);</span>
<span class="fc bfc" id="L100" title="All 2 branches covered.">outer: for (final Class<? extends ROLE_TYPE> roleImplementationType : roleImplementationTypes)</span>
{
<span class="pc bpc" id="L102" title="1 of 2 branches missed."> for (final Constructor<?> constructor : roleImplementationType.getDeclaredConstructors())</span>
{
<span class="fc" id="L104"> log.trace(">>>> trying constructor {}", constructor);</span>
<span class="fc" id="L105"> final Class<?>[] parameterTypes = constructor.getParameterTypes();</span>
<span class="fc" id="L106"> Class<?> contextType = null;</span>
<span class="fc" id="L107"> Object context = null;</span>
try
{
<span class="fc" id="L111"> contextType = findContextTypeForRole(roleImplementationType);</span>
<span class="fc" id="L112"> log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));</span>
try
{
<span class="fc" id="L116"> context = contextManager.findContextOfType(contextType);</span>
}
<span class="fc" id="L118"> catch (NotFoundException e)</span>
{
<span class="fc" id="L120"> log.trace(">>>> role {} discarded, can't find context: {}",</span>
<span class="fc" id="L121"> shortName(roleImplementationType), shortName(contextType));</span>
<span class="fc" id="L122"> continue outer;</span>
<span class="fc" id="L123"> }</span>
}
<span class="fc" id="L125"> catch (NotFoundException e)</span>
{
// ok, no context
<span class="fc" id="L128"> }</span>
try
{
<span class="fc" id="L132"> final Object[] params = getParameterValues(parameterTypes, datumType, datum, contextType, context);</span>
<span class="fc" id="L133"> roles.add(roleType.cast(constructor.newInstance(params)));</span>
<span class="fc" id="L134"> break;</span>
}
<span class="nc" id="L136"> catch (InstantiationException | IllegalAccessException</span>
| IllegalArgumentException | InvocationTargetException e)
{
<span class="nc" id="L139"> log.error("Could not instantiate role of type " + roleImplementationType, e);</span>
}
}
<span class="fc" id="L142"> }</span>
<span class="pc bpc" id="L144" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L146"> log.trace(">>>> findRoles() returning: {}", shortIds((Collection)roles));</span>
}
<span class="fc" id="L149"> 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="L171"> final List<Object> values = new ArrayList<>();</span>
<span class="fc bfc" id="L173" title="All 2 branches covered."> for (Class<?> parameterType : parameterTypes)</span>
{
<span class="fc bfc" id="L175" title="All 2 branches covered."> if (parameterType.isAssignableFrom(datumClass))</span>
{
<span class="fc" id="L177"> values.add(datum);</span>
}
<span class="fc bfc" id="L179" title="All 4 branches covered."> else if ((contextClass != null) && parameterType.isAssignableFrom(contextClass))</span>
{
<span class="fc" id="L181"> values.add(context);</span>
}
else // generic injection
{
<span class="fc" id="L185"> values.add(getBean(parameterType));</span>
}
}
<span class="fc" id="L189"> log.trace(">>>> constructor parameters: {}", values);</span>
<span class="fc" id="L190"> 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="L211"> final DatumAndRole datumAndRole = new DatumAndRole(datumType, roleType);</span>
<span class="pc bpc" id="L213" title="1 of 2 branches missed."> if (!alreadyScanned.contains(datumAndRole))</span>
{
<span class="fc" id="L215"> alreadyScanned.add(datumAndRole);</span>
<span class="fc" id="L216"> final Set<Class<?>> before = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc bfc" id="L218" title="All 2 branches covered."> for (final DatumAndRole superDatumAndRole : datumAndRole.getSuper())</span>
{
<span class="fc" id="L220"> roleMapByDatumAndRole.addAll(datumAndRole, roleMapByDatumAndRole.getValues(superDatumAndRole));</span>
<span class="fc" id="L221"> }</span>
<span class="fc" id="L223"> final Set<Class<?>> after = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc" id="L224"> logChanges(datumAndRole, before, after);</span>
}
<span class="fc" id="L227"> 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="L239"> log.debug("scan({})", shortNames(roleImplementationTypes));</span>
<span class="fc bfc" id="L241" title="All 2 branches covered."> for (final Class<?> roleImplementationType : roleImplementationTypes)</span>
{
<span class="fc bfc" id="L243" title="All 2 branches covered."> for (final Class<?> datumType : findDatumTypesForRole(roleImplementationType))</span>
{
<span class="fc bfc" id="L245" title="All 2 branches covered."> for (final Class<?> roleType : findAllImplementedInterfacesOf(roleImplementationType))</span>
{
<span class="pc bpc" id="L247" title="1 of 2 branches missed."> if (!roleType.getName().equals("org.springframework.beans.factory.aspectj.ConfigurableObject"))</span>
{
<span class="fc" id="L249"> roleMapByDatumAndRole.add(new DatumAndRole(datumType, roleType), roleImplementationType);</span>
}
<span class="fc" id="L251"> }</span>
}
<span class="fc" id="L253"> }</span>
<span class="fc" id="L255"> logRoles();</span>
<span class="fc" id="L256"> }</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="L270"> final SortedSet<Class<?>> interfaces = new TreeSet<>(CLASS_COMPARATOR);</span>
<span class="fc" id="L271"> interfaces.addAll(Arrays.asList(clazz.getInterfaces()));</span>
<span class="fc bfc" id="L273" title="All 2 branches covered."> for (final Class<?> interface_ : interfaces)</span>
{
<span class="fc" id="L275"> interfaces.addAll(findAllImplementedInterfacesOf(interface_));</span>
<span class="fc" id="L276"> }</span>
<span class="fc bfc" id="L278" title="All 2 branches covered."> if (clazz.getSuperclass() != null)</span>
{
<span class="fc" id="L280"> interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));</span>
}
<span class="fc" id="L283"> 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="L330"> after.removeAll(before);</span>
<span class="fc bfc" id="L332" title="All 2 branches covered."> if (!after.isEmpty())</span>
{
<span class="fc" id="L334"> log.debug(">>>>>>> added implementations: {} -> {}", datumAndRole, shortNames(after));</span>
<span class="pc bpc" id="L336" title="1 of 2 branches missed."> if (log.isTraceEnabled()) // yes, trace</span>
{
<span class="fc" id="L338"> logRoles();</span>
}
}
<span class="fc" id="L341"> }</span>
/*******************************************************************************************************************
*
*
******************************************************************************************************************/
public void logRoles()
{
<span class="fc" id="L349"> log.debug("Configured roles:");</span>
<span class="fc" id="L351"> final List<Entry<DatumAndRole, Set<Class<?>>>> entries = new ArrayList<>(roleMapByDatumAndRole.entrySet());</span>
<span class="fc" id="L352"> Collections.sort(entries, new Comparator<Entry<DatumAndRole, Set<Class<?>>>>()</span>
<span class="fc" id="L353"> {</span>
@Override
public int compare (final @Nonnull Entry<DatumAndRole, Set<Class<?>>> e1,
final @Nonnull Entry<DatumAndRole, Set<Class<?>>> e2)
{
<span class="fc" id="L358"> final int s1 = e1.getKey().getDatumClass().getName().compareTo(</span>
<span class="fc" id="L359"> e2.getKey().getDatumClass().getName());</span>
<span class="fc bfc" id="L361" title="All 2 branches covered."> if (s1 != 0)</span>
{
<span class="fc" id="L363"> return s1;</span>
}
<span class="fc" id="L366"> return e1.getKey().getRoleClass().getName().compareTo(</span>
<span class="fc" id="L367"> e2.getKey().getRoleClass().getName());</span>
}
});
<span class="fc bfc" id="L371" title="All 2 branches covered."> for (final Entry<DatumAndRole, Set<Class<?>>> entry : entries)</span>
{
<span class="fc" id="L373"> log.debug(">>>> {}: {} -> {}",</span>
<span class="fc" id="L374"> new Object[] { shortName(entry.getKey().getDatumClass()),</span>
<span class="fc" id="L375"> shortName(entry.getKey().getRoleClass()),</span>
<span class="fc" id="L376"> shortNames(entry.getValue())});</span>
<span class="fc" id="L377"> }</span>
<span class="fc" id="L378"> }</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="L392"> Class<?> ownerClass = object.getClass();</span>
<span class="fc bfc" id="L394" title="All 2 branches covered."> if (ownerClass.toString().contains("MockitoMock"))</span>
{
<span class="fc" id="L396"> ownerClass = ownerClass.getInterfaces()[0]; // 1st is the original class, 2nd is CGLIB proxy</span>
<span class="pc bpc" id="L398" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L400"> log.trace(">>>> owner is a mock {} implementing {}",</span>
<span class="fc" id="L401"> shortName(ownerClass), shortNames(Arrays.asList(ownerClass.getInterfaces())));</span>
<span class="fc" id="L402"> log.trace(">>>> owner class replaced with {}", shortName(ownerClass));</span>
}
}
<span class="fc" id="L406"> 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>