<?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">/*
* *********************************************************************************************************************
*
* TheseFoolishThings: Miscellaneous utilities
* http://tidalwave.it/projects/thesefoolishthings
*
* Copyright (C) 2009 - 2021 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 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.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="L66">@Slf4j</span>
<span class="fc" id="L67">public abstract class RoleManagerSupport implements RoleManager</span>
{
<span class="fc" id="L69"> private final ContextManager contextManager = ContextManager.Locator.find();</span>
<span class="fc" id="L71"> /* VisibleForTesting */ final MultiMap<DatumAndRole, Class<?>> roleMapByDatumAndRole = new MultiMap<>();</span>
// FIXME: use ConcurrentHashMap// FIXME: use ConcurrentHashMap
use ConcurrentHashMap
<span class="fc" id="L74"> /* VisibleForTesting */ final Set<DatumAndRole> alreadyScanned = new HashSet<>();</span>
/*******************************************************************************************************************
*
* {@inheritDoc}
*
******************************************************************************************************************/
@Override @Nonnull
public <ROLE_TYPE> List<? extends ROLE_TYPE> findRoles (@Nonnull final Object datum,
@Nonnull final Class<ROLE_TYPE> roleType)
{
<span class="fc" id="L85"> log.trace("findRoles({}, {})", shortId(datum), shortName(roleType));</span>
<span class="fc" id="L86"> final Class<?> datumType = findTypeOf(datum);</span>
<span class="fc" id="L87"> final List<ROLE_TYPE> roles = new ArrayList<>();</span>
<span class="fc" id="L88"> final Set<Class<? extends ROLE_TYPE>> roleImplementationTypes = findRoleImplementationsFor(datumType, roleType);</span>
<span class="fc bfc" id="L90" title="All 2 branches covered.">outer: for (final Class<? extends ROLE_TYPE> roleImplementationType : roleImplementationTypes)</span>
{
<span class="pc bpc" id="L92" title="1 of 2 branches missed."> for (final Constructor<?> constructor : roleImplementationType.getDeclaredConstructors())</span>
{
<span class="fc" id="L94"> log.trace(">>>> trying constructor {}", constructor);</span>
<span class="fc" id="L95"> final Class<?>[] parameterTypes = constructor.getParameterTypes();</span>
<span class="fc" id="L96"> Class<?> contextType = null;</span>
<span class="fc" id="L97"> Object context = null;</span>
try
{
<span class="fc" id="L101"> contextType = findContextTypeForRole(roleImplementationType);</span>
<span class="fc" id="L102"> log.trace(">>>> contexts: {}", shortIds(contextManager.getContexts()));</span>
try
{
<span class="fc" id="L106"> context = contextManager.findContextOfType(contextType);</span>
}
<span class="fc" id="L108"> catch (NotFoundException e)</span>
{
<span class="fc" id="L110"> log.trace(">>>> role {} discarded, can't find context: {}",</span>
<span class="fc" id="L111"> shortName(roleImplementationType), shortName(contextType));</span>
<span class="fc" id="L112"> continue outer;</span>
<span class="fc" id="L113"> }</span>
}
<span class="fc" id="L115"> catch (NotFoundException e)</span>
{
// ok, no context
<span class="fc" id="L118"> }</span>
try
{
<span class="fc" id="L122"> final Object[] params = getParameterValues(parameterTypes, datumType, datum, contextType, context);</span>
<span class="fc" id="L123"> roles.add(roleType.cast(constructor.newInstance(params)));</span>
<span class="fc" id="L124"> break;</span>
}
<span class="nc" id="L126"> catch (InstantiationException | IllegalAccessException</span>
| IllegalArgumentException | InvocationTargetException e)
{
<span class="nc" id="L129"> log.error("Could not instantiate role of type " + roleImplementationType, e);</span>
}
}
<span class="fc" id="L132"> }</span>
<span class="pc bpc" id="L134" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L136"> log.trace(">>>> findRoles() returning: {}", shortIds(roles));</span>
}
<span class="fc" id="L139"> 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="L161"> final List<Object> values = new ArrayList<>();</span>
<span class="fc bfc" id="L163" title="All 2 branches covered."> for (final Class<?> parameterType : parameterTypes)</span>
{
<span class="fc bfc" id="L165" title="All 2 branches covered."> if (parameterType.isAssignableFrom(datumClass))</span>
{
<span class="fc" id="L167"> values.add(datum);</span>
}
<span class="fc bfc" id="L169" title="All 4 branches covered."> else if ((contextClass != null) && parameterType.isAssignableFrom(contextClass))</span>
{
<span class="fc" id="L171"> values.add(context);</span>
}
else // generic injection
{
<span class="fc" id="L175"> values.add(getBean(parameterType));</span>
}
}
<span class="fc" id="L179"> log.trace(">>>> constructor parameters: {}", values);</span>
<span class="fc" id="L180"> 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 <RT> Set<Class<? extends RT>> findRoleImplementationsFor (
@Nonnull final Class<?> datumType,
@Nonnull final Class<RT> roleType)
{
<span class="fc" id="L201"> final DatumAndRole datumAndRole = new DatumAndRole(datumType, roleType);</span>
<span class="pc bpc" id="L203" title="1 of 2 branches missed."> if (!alreadyScanned.contains(datumAndRole))</span>
{
<span class="fc" id="L205"> alreadyScanned.add(datumAndRole);</span>
<span class="fc" id="L206"> final Set<Class<?>> before = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc bfc" id="L208" title="All 2 branches covered."> for (final DatumAndRole superDatumAndRole : datumAndRole.getSuper())</span>
{
<span class="fc" id="L210"> roleMapByDatumAndRole.addAll(datumAndRole, roleMapByDatumAndRole.getValues(superDatumAndRole));</span>
<span class="fc" id="L211"> }</span>
<span class="fc" id="L213"> final Set<Class<?>> after = new HashSet<>(roleMapByDatumAndRole.getValues(datumAndRole));</span>
<span class="fc" id="L214"> logChanges(datumAndRole, before, after);</span>
}
<span class="fc" id="L217"> 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 (@Nonnull final Collection<Class<?>> roleImplementationTypes)
{
<span class="fc" id="L229"> log.debug("scan({})", shortNames(roleImplementationTypes));</span>
<span class="fc bfc" id="L231" title="All 2 branches covered."> for (final Class<?> roleImplementationType : roleImplementationTypes)</span>
{
<span class="fc bfc" id="L233" title="All 2 branches covered."> for (final Class<?> datumType : findDatumTypesForRole(roleImplementationType))</span>
{
<span class="fc bfc" id="L235" title="All 2 branches covered."> for (final Class<?> roleType : findAllImplementedInterfacesOf(roleImplementationType))</span>
{
<span class="pc bpc" id="L237" title="1 of 2 branches missed."> if (!"org.springframework.beans.factory.aspectj.ConfigurableObject".equals(roleType.getName()))</span>
{
<span class="fc" id="L239"> roleMapByDatumAndRole.add(new DatumAndRole(datumType, roleType), roleImplementationType);</span>
}
<span class="fc" id="L241"> }</span>
}
<span class="fc" id="L243"> }</span>
<span class="fc" id="L245"> logRoles();</span>
<span class="fc" id="L246"> }</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="L260"> final SortedSet<Class<?>> interfaces = new TreeSet<>(Comparator.comparing(Class::getName));</span>
<span class="fc" id="L261"> interfaces.addAll(Arrays.asList(clazz.getInterfaces()));</span>
<span class="fc bfc" id="L263" title="All 2 branches covered."> for (final Class<?> interface_ : interfaces)</span>
{
<span class="fc" id="L265"> interfaces.addAll(findAllImplementedInterfacesOf(interface_));</span>
<span class="fc" id="L266"> }</span>
<span class="fc bfc" id="L268" title="All 2 branches covered."> if (clazz.getSuperclass() != null)</span>
{
<span class="fc" id="L270"> interfaces.addAll(findAllImplementedInterfacesOf(clazz.getSuperclass()));</span>
}
<span class="fc" id="L273"> 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="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: {} -> {}", datumAndRole, shortNames(after));</span>
<span class="pc bpc" id="L326" title="1 of 2 branches missed."> if (log.isTraceEnabled()) // yes, trace</span>
{
<span class="fc" 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 List<Entry<DatumAndRole, Set<Class<?>>>> entries = new ArrayList<>(roleMapByDatumAndRole.entrySet());</span>
<span class="fc" id="L342"> entries.sort(Comparator.comparing((Entry<DatumAndRole, Set<Class<?>>> e) -> e.getKey()</span>
<span class="fc" id="L343"> .getDatumClass()</span>
<span class="fc" id="L344"> .getName())</span>
<span class="fc" id="L345"> .thenComparing(e -> e.getKey().getRoleClass().getName()));</span>
<span class="fc bfc" id="L347" title="All 2 branches covered."> for (final Entry<DatumAndRole, Set<Class<?>>> entry : entries)</span>
{
<span class="fc" id="L349"> log.debug(">>>> {}: {} -> {}",</span>
<span class="fc" id="L350"> shortName(entry.getKey().getDatumClass()),</span>
<span class="fc" id="L351"> shortName(entry.getKey().getRoleClass()),</span>
<span class="fc" id="L352"> shortNames(entry.getValue()));</span>
<span class="fc" id="L353"> }</span>
<span class="fc" id="L354"> }</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="L368"> Class<?> ownerClass = object.getClass();</span>
<span class="fc bfc" id="L370" title="All 2 branches covered."> if (ownerClass.toString().contains("MockitoMock"))</span>
{
<span class="fc" id="L372"> ownerClass = ownerClass.getInterfaces()[0]; // 1st is the original class, 2nd is CGLIB proxy</span>
<span class="pc bpc" id="L374" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span>
{
<span class="fc" id="L376"> log.trace(">>>> owner is a mock {} implementing {}",</span>
<span class="fc" id="L377"> shortName(ownerClass), shortNames(Arrays.asList(ownerClass.getInterfaces())));</span>
<span class="fc" id="L378"> log.trace(">>>> owner class replaced with {}", shortName(ownerClass));</span>
}
}
<span class="fc" id="L382"> 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>