Content of file RepositoryFinderSupport.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>RepositoryFinderSupport.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">blueMarine II :: Headless Service</a> &gt; <a href="../index.html" class="el_bundle">it-tidalwave-bluemarine2-catalog</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.bluemarine2.model.impl.catalog.finder</a> &gt; <span class="el_source">RepositoryFinderSupport.java</span></div><h1>RepositoryFinderSupport.java</h1><pre class="source lang-java linenums"><span class="nc" id="L1">/*</span>
 * *********************************************************************************************************************
 *
 * blueMarine II: Semantic Media Centre
 * http://tidalwave.it/projects/bluemarine2
 *
 * Copyright (C) 2015 - 2021 by Tidalwave s.a.s. (http://tidalwave.it)
 *
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the &quot;License&quot;); 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 &quot;AS IS&quot; 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/bluemarine2-src
 * git clone https://github.com/tidalwave-it/bluemarine2-src
 *
 * *********************************************************************************************************************
 */
package it.tidalwave.bluemarine2.model.impl.catalog.finder;

import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.util.StreamUtils;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.query.QueryLanguage;
import org.eclipse.rdf4j.query.TupleQuery;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import it.tidalwave.util.Finder;
import it.tidalwave.util.Id;
import it.tidalwave.util.LoggingUtilities;
import it.tidalwave.util.ReflectionUtils;
import it.tidalwave.util.Task;
import it.tidalwave.util.spi.FinderSupport;
import it.tidalwave.role.ContextManager;
import it.tidalwave.bluemarine2.util.ImmutableTupleQueryResult;
import it.tidalwave.bluemarine2.model.impl.catalog.factory.RepositoryEntityFactory;
import it.tidalwave.bluemarine2.model.spi.CacheManager;
import it.tidalwave.bluemarine2.model.spi.CacheManager.Cache;
import it.tidalwave.bluemarine2.model.spi.SourceAwareFinder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import static java.util.stream.Collectors.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static it.tidalwave.bluemarine2.util.RdfUtilities.streamOf;
import static it.tidalwave.bluemarine2.model.vocabulary.BMMO.*;

/***********************************************************************************************************************
 *
 * A base class for creating {@link Finder}s.
 *
 * @param &lt;ENTITY&gt;  the entity the {@code Finder} should find
 * @param &lt;FINDER&gt;  the subclass
 *
 * @stereotype      Finder
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
<span class="fc" id="L88">@Configurable @Slf4j</span>
public class RepositoryFinderSupport&lt;ENTITY, FINDER extends Finder&lt;ENTITY&gt;&gt;
        extends FinderSupport&lt;ENTITY, FINDER&gt;
        implements SourceAwareFinder&lt;ENTITY, FINDER&gt;
  {
    private static final String REGEX_BINDING_TAG = &quot;^@([A-Za-z0-9]*)@&quot;;

    private static final String REGEX_BINDING_TAG_LINE = REGEX_BINDING_TAG + &quot;.*$&quot;;

    private static final String REGEX_COMMENT = &quot;^ *#.*&quot;;

    private static final String PREFIXES = &quot;PREFIX foaf:  &lt;http://xmlns.com/foaf/0.1/&gt;\n&quot;
                                         + &quot;PREFIX rdf:   &lt;http://www.w3.org/1999/02/22-rdf-syntax-ns#&gt;\n&quot;
                                         + &quot;PREFIX rel:   &lt;http://purl.org/vocab/relationship/&gt;\n&quot;
                                         + &quot;PREFIX bmmo:  &lt;http://bluemarine.tidalwave.it/2015/04/mo/&gt;\n&quot;
                                         + &quot;PREFIX mo:    &lt;http://purl.org/ontology/mo/&gt;\n&quot;
                                         + &quot;PREFIX vocab: &lt;http://dbtune.org/musicbrainz/resource/vocab/&gt;\n&quot;
                                         + &quot;PREFIX xs:    &lt;http://www.w3.org/2001/XMLSchema#&gt;\n&quot;;

    private static final String QUERY_COUNT_HOLDER = &quot;queryCount&quot;;

    private static final long serialVersionUID = 1896412264314804227L;

<span class="fc" id="L111">    private static final SimpleValueFactory FACTORY = SimpleValueFactory.getInstance();</span>

    @Nonnull
    protected final transient Repository repository;

    @Nonnull
    private final Class&lt;ENTITY&gt; entityClass;

    @Nonnull
    private final String idName;

    @Nonnull
    private final transient Optional&lt;Id&gt; id;

    @Nonnull
    private final transient Optional&lt;Value&gt; source;

    @Nonnull
    private final transient Optional&lt;Value&gt; sourceFallback;

    @Inject
    private transient ContextManager contextManager;

    @Inject
    private transient RepositoryEntityFactory entityFactory;

    @Inject
    private transient CacheManager cacheManager;

    // FIXME: move to a stats bean
<span class="fc" id="L141">    private static final AtomicInteger queryCount = new AtomicInteger();</span>

<span class="pc" id="L143">    @Getter @Setter</span>
<span class="fc" id="L144">    private static boolean dumpThreadOnQuery = false;</span>

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
<span class="pc bnc" id="L151" title="All 16 branches missed.">    @RequiredArgsConstructor(staticName = &quot;withSparql&quot;) @EqualsAndHashCode @ToString</span>
    protected static class QueryAndParameters
      {
<span class="fc" id="L154">        @Getter @Nonnull</span>
        private final String sparql;

<span class="fc" id="L157">        @Nonnull</span>
        private final List&lt;Object&gt; parameters = new ArrayList&lt;&gt;();

        @Nonnull
        public QueryAndParameters withParameter (@Nonnull final String name, @Nonnull final Optional&lt;? extends Value&gt; value)
          {
<span class="fc" id="L163">            return value.map(v -&gt; withParameter(name, v)).orElse(this);</span>
          }

        @Nonnull
        public QueryAndParameters withParameter (@Nonnull final String name, @Nonnull final Value value)
          {
<span class="fc" id="L169">            parameters.addAll(List.of(name, value));</span>
<span class="fc" id="L170">            return this;</span>
          }

        @Nonnull
        public Object[] getParameters()
          {
<span class="fc" id="L176">            return parameters.toArray();</span>
          }

        @Nonnull
        private String getCountSparql()
          {
<span class="fc" id="L182">            return String.format(&quot;SELECT (COUNT(*) AS ?%s)%n  {%n%s%n  }&quot;,</span>
                                 QUERY_COUNT_HOLDER,
<span class="fc" id="L184">                                 sparql.replaceAll(&quot;ORDER BY[\\s\\S]*&quot;, &quot;&quot;));</span>
          }
      }

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    protected RepositoryFinderSupport (@Nonnull final Repository repository, @Nonnull final String idName)
<span class="pc bpc" id="L194" title="9 of 18 branches missed.">      {</span>
<span class="fc" id="L195">        this.repository = repository;</span>
<span class="fc" id="L196">        this.entityClass = (Class&lt;ENTITY&gt;)ReflectionUtils.getTypeArguments(RepositoryFinderSupport.class, getClass()).get(0);</span>
<span class="fc" id="L197">        this.idName = idName;</span>
<span class="fc" id="L198">        this.id = Optional.empty();</span>
<span class="fc" id="L199">        this.source = Optional.of(O_SOURCE_EMBEDDED); // FIXME: resets</span>
<span class="fc" id="L200">        this.sourceFallback = Optional.empty(); // FIXME: resets</span>
<span class="pc bpc" id="L201" title="2 of 4 branches missed.">      }</span>

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    private RepositoryFinderSupport (@Nonnull final Repository repository,
                                     @Nonnull final Class&lt;ENTITY&gt; entityClass,
                                     @Nonnull final String idName,
                                     @Nonnull final Optional&lt;Id&gt; id,
                                     @Nonnull final Optional&lt;Value&gt; source,
                                     @Nonnull final Optional&lt;Value&gt; sourceFallback)
<span class="pc bpc" id="L214" title="9 of 18 branches missed.">      {</span>
<span class="fc" id="L215">        this.repository = repository;</span>
<span class="fc" id="L216">        this.entityClass = entityClass;</span>
<span class="fc" id="L217">        this.idName = idName;</span>
<span class="fc" id="L218">        this.id = id;</span>
<span class="fc" id="L219">        this.source = source;</span>
<span class="fc" id="L220">        this.sourceFallback = sourceFallback;</span>
<span class="pc bpc" id="L221" title="2 of 4 branches missed.">      }</span>

    /*******************************************************************************************************************
     *
     * Clone constructor.
     *
     ******************************************************************************************************************/
    public RepositoryFinderSupport (@Nonnull final RepositoryFinderSupport&lt;ENTITY, FINDER&gt; other,
                                    @Nonnull final Object override)
      {
<span class="pc bpc" id="L231" title="9 of 18 branches missed.">        super(other, override);</span>
<span class="fc" id="L232">        final RepositoryFinderSupport&lt;ENTITY, FINDER&gt; source = getSource(RepositoryFinderSupport.class, other, override);</span>
<span class="fc" id="L233">        this.repository = source.repository;</span>
<span class="fc" id="L234">        this.entityClass = source.entityClass;</span>
<span class="fc" id="L235">        this.idName = source.idName;</span>
<span class="fc" id="L236">        this.id = source.id;</span>
<span class="fc" id="L237">        this.source = source.source;</span>
<span class="fc" id="L238">        this.sourceFallback = source.sourceFallback;</span>
<span class="pc bpc" id="L239" title="2 of 4 branches missed.">     }</span>

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    protected final List&lt;? extends ENTITY&gt; computeNeededResults()
      {
<span class="fc" id="L249">        return query(QueryAndParameters::getSparql,</span>
<span class="fc" id="L250">                     result -&gt; createEntities(repository, entityClass, result),</span>
<span class="fc" id="L251">                     result -&gt; String.format(&quot;%d entities&quot;, result.size()));</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnegative
    public int count()
      {
<span class="fc" id="L262">        return query(QueryAndParameters::getCountSparql,</span>
<span class="fc" id="L263">                     result -&gt; Integer.parseInt(result.next().getValue(QUERY_COUNT_HOLDER).stringValue()),</span>
<span class="fc" id="L264">                     result -&gt; String.format(&quot;%d&quot;, result));</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public FINDER withId (@Nonnull final Id id)
      {
<span class="nc" id="L275">        return clonedWith(new RepositoryFinderSupport(repository,</span>
                                                 entityClass,
                                                 idName,
<span class="nc" id="L278">                                                 Optional.of(id),</span>
                                                 source,
                                                 sourceFallback));
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public FINDER importedFrom (@Nonnull final Optional&lt;Id&gt; optionalSource)
      {
<span class="fc" id="L291">        return optionalSource.map(this::importedFrom).orElse((FINDER)this);</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public FINDER importedFrom (@Nonnull final Id source)
      {
<span class="fc" id="L302">        return clonedWith(new RepositoryFinderSupport(repository,</span>
                                                 entityClass,
                                                 idName,
                                                 id,
<span class="fc" id="L306">                                                 Optional.of(FACTORY.createLiteral(source.toString())),</span>
                                                 sourceFallback));
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public FINDER withFallback (@Nonnull final Optional&lt;Id&gt; sourceFallback)
      {
<span class="fc" id="L318">        return sourceFallback.map(this::withFallback).orElse((FINDER)this);</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public FINDER withFallback (@Nonnull final Id sourceFallback)
      {
<span class="fc" id="L329">        return clonedWith(new RepositoryFinderSupport(repository,</span>
                                                 entityClass,
                                                 idName,
                                                 id,
                                                 source,
<span class="fc" id="L334">                                                 Optional.of(FACTORY.createLiteral(sourceFallback.toString()))));</span>
      }

    /*******************************************************************************************************************
     *
     * Returns the count of queries performed so far.
     *
     * @return      the count of queries
     *
     ******************************************************************************************************************/
    @Nonnegative
    public static int getQueryCount()
      {
<span class="fc" id="L347">        return queryCount.intValue();</span>
      }

    /*******************************************************************************************************************
     *
     * Resets the count of queries performed so far.
     *
     ******************************************************************************************************************/
    public static void resetQueryCount()
      {
<span class="fc" id="L357">        queryCount.set(0);</span>
<span class="fc" id="L358">      }</span>

    /*******************************************************************************************************************
     *
     * Prepares the SPARQL query and its parameters.
     *
     * @return      the SPARQL query and its parameters
     *
     ******************************************************************************************************************/
    @Nonnull
    protected /* abstract */ QueryAndParameters prepareQuery()
      {
<span class="nc" id="L370">        throw new UnsupportedOperationException(&quot;Must be implemented by subclasses&quot;);</span>
      }

    /*******************************************************************************************************************
     *
     * Performs a query, eventually using the cache.
     *
     * @param   sparqlSelector  a function that select the SPARQL statement to use
     * @param   finalizer       a function to transform the query raw result into the final result
     * @param   resultToString  a function that provide the logging string for the result
     * @return                  the found entities
     *
     ******************************************************************************************************************/
    @Nonnull
    private &lt;E&gt; E query (@Nonnull final Function&lt;QueryAndParameters, String&gt; sparqlSelector,
                         @Nonnull final Function&lt;TupleQueryResult, E&gt; finalizer,
                         @Nonnull final Function&lt;E, String&gt; resultToString)
      {
<span class="fc" id="L388">        log.info(&quot;query() - {}&quot;, entityClass);</span>
<span class="fc" id="L389">        final long baseTime = System.nanoTime();</span>
<span class="fc" id="L390">        final QueryAndParameters queryAndParameters = prepareQuery()</span>
<span class="fc" id="L391">                .withParameter(idName, id.map(this::iriFor))</span>
<span class="fc" id="L392">                .withParameter(&quot;source&quot;, source)</span>
<span class="fc bfc" id="L393" title="All 2 branches covered.">                .withParameter(&quot;fallback&quot;, sourceFallback.equals(source) ? Optional.empty() : sourceFallback);</span>
<span class="fc" id="L394">        final Object[] parameters = queryAndParameters.getParameters();</span>
<span class="fc" id="L395">        final String originalSparql = sparqlSelector.apply(queryAndParameters);</span>
<span class="fc" id="L396">        final String sparql = PREFIXES + Stream.of(originalSparql.split(&quot;\n&quot;))</span>
<span class="fc" id="L397">                                               .filter(s -&gt; matchesTag(s, parameters))</span>
<span class="fc" id="L398">                                               .map(s -&gt; s.replaceAll(REGEX_BINDING_TAG, &quot;&quot;))</span>
<span class="fc" id="L399">                                               .collect(joining(&quot;\n&quot;));</span>
<span class="fc" id="L400">        log(originalSparql, sparql, parameters);</span>
<span class="fc" id="L401">        final E result = query(sparql, finalizer, parameters);</span>
<span class="fc" id="L402">        queryCount.incrementAndGet();</span>
<span class="fc" id="L403">        final long elapsedTime = System.nanoTime() - baseTime;</span>
<span class="fc" id="L404">        log.info(&quot;&gt;&gt;&gt;&gt; query returned {} in {} msec&quot;, resultToString.apply(result), elapsedTime / 1E6);</span>
<span class="fc" id="L405">        LoggingUtilities.dumpStack(this, dumpThreadOnQuery);</span>

<span class="fc" id="L407">        return result;</span>
      }

    /*******************************************************************************************************************
     *
     * Performs a query.
     *
     * @param   sparql          the SPARQL of the query
     * @param   finalizer       a function to transform the query raw result into the final result
     * @param   parameters      an optional set of parameters of the query (&quot;name&quot;, value, &quot;name&quot;, value ,,,)
     * @return                  the found entities
     *
     ******************************************************************************************************************/
    @Nonnull
    private &lt;R&gt; R query (@Nonnull final String sparql,
                         @Nonnull final Function&lt;TupleQueryResult, R&gt; finalizer,
                         @Nonnull final Object ... parameters)
      {
<span class="fc" id="L425">        try (final RepositoryConnection connection = repository.getConnection())</span>
          {
<span class="fc" id="L427">            final TupleQuery query = connection.prepareTupleQuery(QueryLanguage.SPARQL, sparql);</span>

<span class="fc bfc" id="L429" title="All 2 branches covered.">            for (int i = 0; i &lt; parameters.length; i += 2)</span>
              {
<span class="fc" id="L431">                query.setBinding((String)parameters[i], (Value)parameters[i + 1]);</span>
              }
            //
            // Don't cache entities because they are injected with DCI roles in function of the context.
            // Caching tuples is safe.
<span class="fc" id="L436">            final Cache cache = cacheManager.getCache(RepositoryFinderSupport.class);</span>
<span class="fc" id="L437">            final String key = String.format(&quot;%s # %s&quot;, compacted(sparql), Arrays.toString(parameters));</span>

<span class="fc" id="L439">            try (final ImmutableTupleQueryResult result = cache.getCachedObject(key,</span>
<span class="fc" id="L440">                                                                () -&gt; new ImmutableTupleQueryResult(query.evaluate())))</span>
              {
                // ImmutableTupleQueryResult is not thread safe, so clone the cached instance
<span class="fc" id="L443">                return finalizer.apply(new ImmutableTupleQueryResult(result));</span>
              }
          }
      }

    /*******************************************************************************************************************
     *
     * Facility method that creates an {@link IRI} given an {@link Id}.
     *
     * @param   id  the {@code Id}
     * @return      the {@code IRI}
     *
     ******************************************************************************************************************/
    @Nonnull
    protected Value iriFor (@Nonnull final Id id)
      {
<span class="fc" id="L459">        return FACTORY.createIRI(id.stringValue());</span>
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @Nonnull
    protected Value literalFor (final boolean b)
      {
<span class="nc" id="L469">        return FACTORY.createLiteral(b);</span>
      }

    /*******************************************************************************************************************
     *
     * Reads a SPARQL statement from a named resource
     *
     * @param   clazz   the reference class
     * @param   name    the resource name
     * @return          the SPARQL statement
     *
     ******************************************************************************************************************/
    @Nonnull
    protected static String readSparql (@Nonnull final Class&lt;?&gt; clazz, @Nonnull final String name)
      {
<span class="fc" id="L484">        try (final InputStream is = clazz.getResourceAsStream(name))</span>
          {
<span class="fc" id="L486">            return Stream.of(StreamUtils.copyToString(is, UTF_8)</span>
<span class="fc" id="L487">                                        .split(&quot;\n&quot;))</span>
<span class="fc bfc" id="L488" title="All 2 branches covered.">                                        .filter(s -&gt; !s.matches(REGEX_COMMENT))</span>
<span class="fc" id="L489">                                        .collect(joining(&quot;\n&quot;));</span>
          }
<span class="nc" id="L491">        catch (IOException e)</span>
          {
<span class="nc" id="L493">            throw new RuntimeException(e);</span>
          }
      }

    /*******************************************************************************************************************
     *
     * Instantiates an entity for each given {@link TupleQueryResult}. Entities are instantiated in the DCI contents
     * associated to this {@link Finder} - see {@link #getContexts()}.
     *
     * @param   &lt;E&gt;             the static type of the entities to instantiate
     * @param   repository      the repository we're querying
     * @param   entityClass     the dynamic type of the entities to instantiate
     * #param   queryResult     the {@code TupleQueryResult}
     * @return                  the instantiated entities
     *
     ******************************************************************************************************************/
    @Nonnull
    private &lt;E&gt; List&lt;E&gt; createEntities (@Nonnull final Repository repository,
                                        @Nonnull final Class&lt;E&gt; entityClass,
                                        @Nonnull final TupleQueryResult queryResult)
      {
<span class="fc" id="L514">        return contextManager.runWithContexts(getContexts(), new Task&lt;&gt;()</span>
<span class="fc" id="L515">          {</span>
            @Override @Nonnull
            public List&lt;E&gt; run()
              {
<span class="fc" id="L519">                return streamOf(queryResult)</span>
<span class="fc" id="L520">                        .map(bindingSet -&gt; entityFactory.createEntity(repository, entityClass, bindingSet))</span>
<span class="fc" id="L521">                        .collect(toList());</span>
              }
          });
        // TODO: requires TheseFoolishThings 3.1-ALPHA-3
requires TheseFoolishThings 3.1-ALPHA-3
// return contextManager.runWithContexts(getContexts(), () -&gt; streamOf(queryResult) // .map(bindingSet -&gt; entityFactory.createEntity(repository, entityClass, bindingSet)) // .collect(toList())); } /******************************************************************************************************************* * * Returns {@code true} if the given string contains a binding tag (in the form {@code @TAG@}) that matches one * of the bindings; or if there are no binding tags. This is used as a filter to eliminate portions of SPARQL * queries that don't match any binding. * * @param string the string * @param bindings the bindings * @return {@code true} if there is a match * ******************************************************************************************************************/ private static boolean matchesTag (@Nonnull final String string, @Nonnull final Object[] bindings) { <span class="fc" id="L543"> final Pattern patternBindingTagLine = Pattern.compile(REGEX_BINDING_TAG_LINE);</span> <span class="fc" id="L544"> final Matcher matcher = patternBindingTagLine.matcher(string);</span> <span class="fc bfc" id="L546" title="All 2 branches covered."> if (!matcher.matches())</span> { <span class="fc" id="L548"> return true;</span> } <span class="fc" id="L551"> final String tag = matcher.group(1);</span> <span class="fc bfc" id="L553" title="All 2 branches covered."> for (int i = 0; i &lt; bindings.length; i+= 2)</span> { <span class="fc bfc" id="L555" title="All 2 branches covered."> if (tag.equals(bindings[i]))</span> { <span class="fc" id="L557"> return true;</span> } } <span class="fc" id="L561"> return false;</span> } /******************************************************************************************************************* * * Logs the query at various detail levels. * * @param originalSparql the original SPARQL statement * @param sparql the SPARQL statement after binding tag filtering * @param bindings the bindings * ******************************************************************************************************************/ private void log (@Nonnull final String originalSparql, @Nonnull final String sparql, @Nonnull final Object ... bindings) { <span class="pc bpc" id="L577" title="1 of 2 branches missed."> if (log.isTraceEnabled())</span> { <span class="nc" id="L579"> Stream.of(originalSparql.split(&quot;\n&quot;)).forEach(s -&gt; log.trace(&quot;&gt;&gt;&gt;&gt; original query: {}&quot;, s));</span> } <span class="pc bpc" id="L582" title="1 of 2 branches missed."> if (log.isDebugEnabled())</span> { <span class="nc" id="L584"> Stream.of(sparql.split(&quot;\n&quot;)).forEach(s -&gt; log.debug(&quot;&gt;&gt;&gt;&gt; query: {}&quot;, s));</span> } <span class="pc bpc" id="L587" title="2 of 4 branches missed."> if (!log.isDebugEnabled() &amp;&amp; log.isInfoEnabled())</span> { <span class="fc" id="L589"> log.info(&quot;&gt;&gt;&gt;&gt; query: {}&quot;, compacted(sparql));</span> } <span class="pc bpc" id="L592" title="1 of 2 branches missed."> if (log.isInfoEnabled())</span> { <span class="fc" id="L594"> log.info(&quot;&gt;&gt;&gt;&gt; query parameters: {}&quot;, Arrays.toString(bindings));</span> } <span class="fc" id="L596"> }</span> /******************************************************************************************************************* * * ******************************************************************************************************************/ @Nonnull private static String compacted (@Nonnull final String sparql) { <span class="fc" id="L605"> return sparql.replace(&quot;\n&quot;, &quot; &quot;).replaceAll(&quot;\\s+&quot;, &quot; &quot;).trim();</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>