Content of file DefaultSite.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>DefaultSite.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">NorthernWind :: Filesystems :: SCM :: Mercurial</a> &gt; <a href="../index.html" class="el_bundle">it-tidalwave-northernwind-core-default</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.northernwind.core.impl.model</a> &gt; <span class="el_source">DefaultSite.java</span></div><h1>DefaultSite.java</h1><pre class="source lang-java linenums">/*
 * #%L
 * *********************************************************************************************************************
 *
 * NorthernWind - lightweight CMS
 * http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
 * %%
 * Copyright (C) 2011 - 2023 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.
 *
 * *********************************************************************************************************************
 *
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.northernwind.core.impl.model;

import javax.annotation.Nonnull;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.io.IOException;
import org.springframework.beans.factory.annotation.Configurable;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.northernwind.core.model.Content;
import it.tidalwave.northernwind.core.model.Media;
import it.tidalwave.northernwind.core.model.ModelFactory;
import it.tidalwave.northernwind.core.model.Resource;
import it.tidalwave.northernwind.core.model.ResourceFile;
import it.tidalwave.northernwind.core.model.ResourceFileSystem;
import it.tidalwave.northernwind.core.model.ResourceFileSystemProvider;
import it.tidalwave.northernwind.core.model.ResourcePath;
import it.tidalwave.northernwind.core.model.Site;
import it.tidalwave.northernwind.core.model.SiteFinder;
import it.tidalwave.northernwind.core.model.SiteNode;
import it.tidalwave.northernwind.core.model.Template;
import it.tidalwave.northernwind.core.model.spi.LinkPostProcessor;
import it.tidalwave.northernwind.core.model.spi.RequestHolder;
import it.tidalwave.northernwind.core.impl.text.St4TemplateFactory;
import it.tidalwave.northernwind.core.impl.util.RegexTreeMap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/***********************************************************************************************************************
 *
 * The default implementation of {@link Site}.
 *
 * @author  Fabrizio Giudici
 *
 **********************************************************************************************************************/
<span class="fc" id="L71">@Configurable @Slf4j</span>
/* package */ class DefaultSite implements InternalSite
  {
    @Inject
    private List&lt;LinkPostProcessor&gt; linkPostProcessors;

    @Inject
    private RequestHolder requestHolder;

    @Nonnull
    private final ModelFactory modelFactory;

<span class="nc" id="L83">    @Inject @Named(&quot;fileSystemProvider&quot;) @Getter</span>
    private ResourceFileSystemProvider fileSystemProvider;

    @Nonnull
    /* package */ final String documentPath;

    @Nonnull
    /* package */ final String mediaPath;

    @Nonnull
    /* package */ final String libraryPath;

    @Nonnull
    /* package */ final String nodePath;

<span class="nc" id="L98">    @Getter</span>
    /* package */ final boolean logConfigurationEnabled;

<span class="fc" id="L101">    @Getter @Nonnull</span>
    /* package */ final String contextPath;

<span class="fc" id="L104">    /* package */ final List&lt;String&gt; ignoredFolders = new ArrayList&lt;&gt;();</span>

    private ResourceFile documentFolder;

    private ResourceFile libraryFolder;

    private ResourceFile mediaFolder;

<span class="nc" id="L112">    @Getter</span>
    private ResourceFile nodeFolder;

    // Note that this class can't be final neither use ImmutableMaps, since during the traversing of the filesystem
    // resources need to access the partially created internal structure.

<span class="fc" id="L118">    /* package */  final Map&lt;String, Content&gt; documentMapByRelativePath = new TreeMap&lt;&gt;();</span>

<span class="fc" id="L120">    /* package */  final Map&lt;String, Resource&gt; libraryMapByRelativePath = new TreeMap&lt;&gt;();</span>

<span class="fc" id="L122">    /* package */  final Map&lt;String, Media&gt; mediaMapByRelativePath = new TreeMap&lt;&gt;();</span>

<span class="fc" id="L124">    /* package */  final Map&lt;String, SiteNode&gt; nodeMapByRelativePath = new TreeMap&lt;&gt;();</span>

<span class="fc" id="L126">    /* package */  final RegexTreeMap&lt;SiteNode&gt; nodeMapByRelativeUri = new RegexTreeMap&lt;&gt;();</span>

<span class="fc" id="L128">    private final Map&lt;Class&lt;?&gt;, Map&lt;String, ?&gt;&gt; relativePathMapsByType = new HashMap&lt;&gt;();</span>

<span class="fc" id="L130">    private final Map&lt;Class&lt;?&gt;, RegexTreeMap&lt;?&gt;&gt; relativeUriMapsByType = new HashMap&lt;&gt;();</span>

<span class="fc" id="L132">    private final List&lt;Locale&gt; configuredLocales = new ArrayList&lt;&gt;();</span>

<span class="fc" id="L134">    private final Predicate&lt;ResourceFile&gt; FOLDER_FILTER =</span>
<span class="pc bpc" id="L135" title="1 of 4 branches missed.">            file -&gt; file.isFolder() &amp;&amp; !ignoredFolders.contains(file.getName());</span>

<span class="fc" id="L137">    private final Predicate&lt;ResourceFile&gt; FILE_FILTER =</span>
<span class="fc bfc" id="L138" title="All 4 branches covered.">            file -&gt; file.isData() &amp;&amp; !ignoredFolders.contains(file.getName());</span>

    /*******************************************************************************************************************
     *
     *
     *
     ******************************************************************************************************************/
    protected DefaultSite (@Nonnull final Site.Builder siteBuilder)
<span class="pc bpc" id="L146" title="9 of 18 branches missed.">      {</span>
<span class="fc" id="L147">        this.modelFactory = siteBuilder.getModelFactory();</span>
<span class="fc" id="L148">        this.contextPath = siteBuilder.getContextPath();</span>
<span class="fc" id="L149">        this.documentPath = siteBuilder.getDocumentPath();</span>
<span class="fc" id="L150">        this.mediaPath = siteBuilder.getMediaPath();</span>
<span class="fc" id="L151">        this.libraryPath = siteBuilder.getLibraryPath();</span>
<span class="fc" id="L152">        this.nodePath = siteBuilder.getNodePath();</span>
<span class="fc" id="L153">        this.logConfigurationEnabled = siteBuilder.isLogConfigurationEnabled();</span>
<span class="fc" id="L154">        this.configuredLocales.addAll(siteBuilder.getConfiguredLocales());</span>
<span class="fc" id="L155">        this.ignoredFolders.addAll(siteBuilder.getIgnoredFolders());</span>
<span class="pc bpc" id="L156" title="2 of 4 branches missed.">      }</span>

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull @SuppressWarnings(&quot;unchecked&quot;)
    public &lt;Type&gt; SiteFinder&lt;Type&gt; find (@Nonnull final Class&lt;Type&gt; type)
      {
<span class="fc" id="L166">        final var relativePathMap = (Map&lt;String, Type&gt;)relativePathMapsByType.get(type);</span>
<span class="fc" id="L167">        final var relativeUriMap = (RegexTreeMap&lt;Type&gt;)relativeUriMapsByType.get(type);</span>
<span class="fc" id="L168">        return new DefaultSiteFinder&lt;&gt;(type.getSimpleName(), relativePathMap, relativeUriMap);</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public String createLink (@Nonnull final ResourcePath relativeUri)
      {
<span class="fc" id="L179">        final var link = ResourcePath.of(contextPath).appendedWith(relativeUri);</span>
<span class="fc" id="L180">        var linkAsString = requestHolder.get().getBaseUrl() + link.asString();</span>

<span class="fc bfc" id="L182" title="All 2 branches covered.">        for (final var linkPostProcessor : linkPostProcessors)</span>
          {
<span class="fc" id="L184">            linkAsString = linkPostProcessor.postProcess(linkAsString);</span>
<span class="fc" id="L185">          }</span>

<span class="fc" id="L187">        return linkAsString;</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public List&lt;Locale&gt; getConfiguredLocales()
      {
<span class="fc" id="L198">        return new CopyOnWriteArrayList&lt;&gt;(configuredLocales);</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public Template getTemplate (@Nonnull final Class&lt;?&gt; clazz,
                                 @Nonnull final Optional&lt;? extends ResourcePath&gt; templatePath,
                                 @Nonnull final String embeddedResourceName)
      {
<span class="nc" id="L211">        return getTemplateFactoryFor(clazz).getTemplate(templatePath, embeddedResourceName);</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public Optional&lt;String&gt; getTemplate (@Nonnull final Class&lt;?&gt; clazz, @Nonnull final ResourcePath templatePath)
      {
<span class="nc" id="L222">        return getTemplateFactoryFor(clazz).getTemplate(templatePath);</span>
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public String toString()
      {
<span class="nc" id="L233">        return String.format(&quot;DefaultSite(@%x)&quot;, System.identityHashCode(this));</span>
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    /* package */ void initialize()
      throws IOException, NotFoundException
      {
<span class="fc" id="L243">        log.info(&quot;initialize()&quot;);</span>

<span class="fc" id="L245">        relativePathMapsByType.put(Content.class,  documentMapByRelativePath);</span>
<span class="fc" id="L246">        relativePathMapsByType.put(Media.class,    mediaMapByRelativePath);</span>
<span class="fc" id="L247">        relativePathMapsByType.put(Resource.class, libraryMapByRelativePath);</span>
<span class="fc" id="L248">        relativePathMapsByType.put(SiteNode.class, nodeMapByRelativePath);</span>

<span class="fc" id="L250">        relativeUriMapsByType.put(SiteNode.class, nodeMapByRelativeUri);</span>

<span class="fc" id="L252">        log.info(&quot;&gt;&gt;&gt;&gt; fileSystemProvider: {}&quot;, fileSystemProvider);</span>
<span class="fc" id="L253">        final var fileSystem = fileSystemProvider.getFileSystem();</span>
<span class="fc" id="L254">        documentFolder = findMandatoryFolder(fileSystem, documentPath);</span>
<span class="fc" id="L255">        libraryFolder  = findMandatoryFolder(fileSystem, libraryPath);</span>
<span class="fc" id="L256">        mediaFolder    = findMandatoryFolder(fileSystem, mediaPath);</span>
<span class="fc" id="L257">        nodeFolder     = findMandatoryFolder(fileSystem, nodePath);</span>

<span class="fc" id="L259">        log.info(&quot;&gt;&gt;&gt;&gt; contextPath:        {}&quot;, contextPath);</span>
<span class="fc" id="L260">        log.info(&quot;&gt;&gt;&gt;&gt; ignoredFolders:     {}&quot;, ignoredFolders);</span>
<span class="fc" id="L261">        log.info(&quot;&gt;&gt;&gt;&gt; fileSystem:         {}&quot;, fileSystem);</span>
<span class="fc" id="L262">        log.info(&quot;&gt;&gt;&gt;&gt; documentPath:       {}&quot;, documentFolder.getPath().asString());</span>
<span class="fc" id="L263">        log.info(&quot;&gt;&gt;&gt;&gt; libraryPath:        {}&quot;, libraryFolder.getPath().asString());</span>
<span class="fc" id="L264">        log.info(&quot;&gt;&gt;&gt;&gt; mediaPath:          {}&quot;, mediaFolder.getPath().asString());</span>
<span class="fc" id="L265">        log.info(&quot;&gt;&gt;&gt;&gt; nodePath:           {}&quot;, nodeFolder.getPath().asString());</span>
<span class="fc" id="L266">        log.info(&quot;&gt;&gt;&gt;&gt; locales:            {}&quot;, configuredLocales);</span>

<span class="fc" id="L268">        documentMapByRelativePath.clear();</span>
<span class="fc" id="L269">        libraryMapByRelativePath.clear();</span>
<span class="fc" id="L270">        mediaMapByRelativePath.clear();</span>
<span class="fc" id="L271">        nodeMapByRelativePath.clear();</span>
<span class="fc" id="L272">        nodeMapByRelativeUri.clear();</span>

<span class="fc" id="L274">        traverse(libraryFolder,  FILE_FILTER,   this::createResource);</span>
<span class="fc" id="L275">        traverse(mediaFolder,    FILE_FILTER,   this::createMedia);</span>
<span class="fc" id="L276">        traverse(documentFolder, FOLDER_FILTER, this::createContent);</span>
<span class="fc" id="L277">        traverse(nodeFolder,     FOLDER_FILTER, this::createSiteNode);</span>

<span class="pc bpc" id="L279" title="1 of 2 branches missed.">        if (logConfigurationEnabled)</span>
          {
<span class="fc" id="L281">            logConfiguration(&quot;Documents by relative path:&quot;, documentMapByRelativePath);</span>
<span class="fc" id="L282">            logConfiguration(&quot;Library by relative path:&quot;,   libraryMapByRelativePath);</span>
<span class="fc" id="L283">            logConfiguration(&quot;Media by relative path:&quot;,     mediaMapByRelativePath);</span>
<span class="fc" id="L284">            logConfiguration(&quot;Nodes by relative path:&quot;,     nodeMapByRelativePath);</span>
<span class="fc" id="L285">            logConfiguration(&quot;Nodes by relative URI:&quot;,      nodeMapByRelativeUri);</span>
          }
<span class="fc" id="L287">      }</span>

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    private void createContent (@Nonnull final ResourceFile folder, @Nonnull final ResourcePath relativePath)
      {
<span class="fc" id="L294">        documentMapByRelativePath.put(relativePath.asString(), modelFactory.createContent().withFolder(folder).build());</span>
<span class="fc" id="L295">      }</span>

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    private void createMedia (@Nonnull final ResourceFile file, @Nonnull final ResourcePath relativePath)
      {
<span class="fc" id="L302">        mediaMapByRelativePath.put(relativePath.asString(), modelFactory.createMedia().withFile(file).build());</span>
<span class="fc" id="L303">      }</span>

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    private void createResource (@Nonnull final ResourceFile file, @Nonnull final ResourcePath relativePath)
      {
<span class="fc" id="L310">        libraryMapByRelativePath.put(relativePath.asString(), modelFactory.createResource().withFile(file).build());</span>
<span class="fc" id="L311">      }</span>

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    private void createSiteNode (@Nonnull final ResourceFile folder, @Nonnull final ResourcePath relativePath)
      {
<span class="fc" id="L318">        final var siteNode = modelFactory.createSiteNode(this, folder);</span>
<span class="fc" id="L319">        nodeMapByRelativePath.put(relativePath.asString(), siteNode);</span>

<span class="pc bpc" id="L321" title="1 of 2 branches missed.">        if (!siteNode.isPlaceHolder())</span>
          {
<span class="fc" id="L323">            final var relativeUri = siteNode.getRelativeUri();</span>
            // Nodes which manage path params are registered with a relativeUri having a wildcard suffix
<span class="fc bfc" id="L325" title="All 2 branches covered.">            if (siteNode.getProperty(SiteNode.P_MANAGES_PATH_PARAMS).orElse(false))</span>
              {
<span class="pc bpc" id="L327" title="1 of 2 branches missed.">                final var suffix = relativeUri.asString().endsWith(&quot;/&quot;) ? &quot;(|.*$)&quot; : &quot;(|/.*$)&quot;;</span>
<span class="fc" id="L328">                nodeMapByRelativeUri.putRegex(&quot;^&quot; + RegexTreeMap.escape(relativeUri.asString()) + suffix, siteNode);</span>
<span class="fc" id="L329">              }</span>
            else
              {
<span class="fc" id="L332">                nodeMapByRelativeUri.put(relativeUri.asString(), siteNode);</span>
              }
          }
<span class="fc" id="L335">      }</span>

    /*******************************************************************************************************************
     *
     * Traverse the file system with a consumer.
     *
     * @param  folder      the folder to traverse
     * @param  fileFilter  the filter for directory contents
     * @param  consumer    the consumer
     *
     ******************************************************************************************************************/
    private void traverse (@Nonnull final ResourceFile folder,
                           @Nonnull final Predicate&lt;? super ResourceFile&gt; fileFilter,
                           @Nonnull final BiConsumer&lt;ResourceFile, ? super ResourcePath&gt; consumer)
      {
<span class="fc" id="L350">        traverse(folder.getPath(), folder, fileFilter, consumer);</span>
<span class="fc" id="L351">      }</span>

    /*******************************************************************************************************************
     *
     * Traverse the file system with a {@link Predicate}.
     *
     * @param  file        the file to traverse
     * @param  fileFilter  the filter for directory contents
     * @param  consumer    the consumer
     *
     ******************************************************************************************************************/
    private static void traverse (@Nonnull final ResourcePath rootPath,
                                  @Nonnull final ResourceFile file,
                                  @Nonnull final Predicate&lt;? super ResourceFile&gt; fileFilter,
                                  @Nonnull final BiConsumer&lt;? super ResourceFile, ? super ResourcePath&gt; consumer)
      {
<span class="fc" id="L367">        log.trace(&quot;traverse({}, {}, {}, {})&quot;, rootPath, file, fileFilter, consumer);</span>
<span class="fc" id="L368">        final var relativePath = file.getPath().urlDecoded().relativeTo(rootPath);</span>

<span class="fc bfc" id="L370" title="All 2 branches covered.">        if (fileFilter.test(file))</span>
          {
<span class="fc" id="L372">            consumer.accept(file, relativePath);</span>
          }

<span class="fc" id="L375">        file.findChildren().results().forEach(child -&gt; traverse(rootPath, child, fileFilter, consumer));</span>
<span class="fc" id="L376">      }</span>

    /*******************************************************************************************************************
     *
     * Logs the configuration contained in the given map of properties.
     *
     ******************************************************************************************************************/
    private static void logConfiguration (@Nonnull final String name, final Map&lt;String, ?&gt; propertyMap)
      {
<span class="fc" id="L385">        log.info(name);</span>
<span class="fc" id="L386">        propertyMap.forEach((key, value) -&gt; log.info(&quot;&gt;&gt;&gt;&gt; {}: {}&quot;, key, value));</span>
<span class="fc" id="L387">      }</span>

    /*******************************************************************************************************************
     *
     * FIXME Wrapper against ResourceFileSystem: its methods should throw NFE by themselves
     *
     ******************************************************************************************************************/
    @Nonnull
    private static ResourceFile findMandatoryFolder (@Nonnull final ResourceFileSystem fileSystem,
                                                     @Nonnull final String path)
      throws NotFoundException
      {
<span class="fc" id="L399">        return NotFoundException.throwWhenNull(fileSystem.findFileByPath(path), &quot;Cannot find folder: &quot; + path);</span>
        // don't log fileSystem.getRoot() since if fileSystem is broken it can trigger secondary errors
                            // FileUtil.toFile(fileSystem.getRoot()).getAbsolutePath() + &quot;/&quot;  + path);
      }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Nonnull
    private St4TemplateFactory getTemplateFactoryFor (@Nonnull final Class&lt;?&gt; clazz)
      {
        // TODO: implement a cache
implement a cache
<span class="nc" id="L411"> return new St4TemplateFactory(clazz, this);</span> } } </pre><div class="footer"><span class="right">Created with <a href="http://www.jacoco.org/jacoco">JaCoCo</a> 0.8.9.202303310957</span></div></body></html>