Content of file ScmFileSystemProvider.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>ScmFileSystemProvider.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</a> &gt; <a href="index.source.html" class="el_package">it.tidalwave.northernwind.frontend.filesystem.scm.spi</a> &gt; <span class="el_source">ScmFileSystemProvider.java</span></div><h1>ScmFileSystemProvider.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.frontend.filesystem.scm.spi;

import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.annotation.concurrent.NotThreadSafe;
import javax.inject.Inject;
import java.time.ZonedDateTime;
import java.beans.PropertyVetoException;
import java.util.Optional;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.beans.factory.BeanFactory;
import org.openide.filesystems.LocalFileSystem;
import it.tidalwave.util.ProcessExecutorException;
import it.tidalwave.messagebus.MessageBus;
import it.tidalwave.northernwind.core.model.ResourceFileSystem;
import it.tidalwave.northernwind.core.model.ResourceFileSystemChangedEvent;
import it.tidalwave.northernwind.core.model.ResourceFileSystemProvider;
import it.tidalwave.northernwind.frontend.filesystem.impl.ResourceFileSystemNetBeansPlatform;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/***********************************************************************************************************************
 *
 * A {@code ResourceFileSystemProvider} based on a SCM. This is an abstract support class that needs to be extended
 * by concrete implementations (such as Git or Mercurial).
 * This provider polls for changes in the SCM that are made available with a new tag named
 * {@code published-&lt;version&gt;} and fetches them. In order to atomically expose changes, in spite of the fact that the
 * underlying operation might require some time to update all files, two working directories are used:
 *
 * &lt;ol&gt;
 *     &lt;li&gt;the {@code exposedWorkingDirectory} is exposed and not affected by next change;&lt;/li&gt;
 *     &lt;li&gt;the {@code alternateWorkingDirectory} is kept behind the scenes and updated; when its update is completed, the
 *     two repositories are swapped and a {@link ResourceFileSystemChangedEvent} is fired.&lt;/li&gt;
 * &lt;/ol&gt;
 *
 * @author Fabrizio Giudici
 *
 **********************************************************************************************************************/
<span class="fc" id="L70">@NotThreadSafe @Slf4j</span>
<span class="fc" id="L71">public abstract class ScmFileSystemProvider implements ResourceFileSystemProvider</span>
  {
    /** The URL of the remote repository. */
<span class="pc" id="L74">    @Getter @Setter</span>
    private String remoteRepositoryUrl;

    /** The folder of the local work area. */
<span class="pc" id="L78">    @Getter @Setter</span>
    private String folderAsString;

    /** The path of the working directory. */
    private Path folder;

    /** The file system used to map the local work area. */
<span class="fc" id="L85">    /* visible for tests */ final LocalFileSystem fileSystemDelegate = new LocalFileSystem();</span>

<span class="pc" id="L87">    @Getter</span>
    private final ResourceFileSystem fileSystem = new ResourceFileSystemNetBeansPlatform(fileSystemDelegate);

<span class="fc" id="L90">    private final ScmWorkingDirectory[] workingDirectories = new ScmWorkingDirectory[2];</span>

    /** The exposed working directory. */
    /* visible for tests */ ScmWorkingDirectory exposedWorkingDirectory;

    /** The alternate working directory. */
    /* visible for tests */ ScmWorkingDirectory alternateWorkingDirectory;

    /** The index of the exposed repository (0 or 1). */
    private int repositorySelector;

    /** A counter of swaps, used for testing. */
    /* visible for tests */int swapCounter;

    @Inject
    private BeanFactory beanFactory;

    /** The message bus where to fire events. */
    //    @Inject @Named(&quot;applicationMessageBus&quot;) FIXME doesn't work in the test
doesn't work in the test
private MessageBus messageBus; /******************************************************************************************************************* * * Makes sure both repository repositories are populated and activates one of them. * ******************************************************************************************************************/ @PostConstruct private void initialize() throws IOException, PropertyVetoException, URISyntaxException, InterruptedException { <span class="fc" id="L120"> folder = new File(folderAsString).toPath();</span> <span class="fc bfc" id="L122" title="All 2 branches covered."> for (var i = 0; i &lt; 2; i++)</span> { <span class="fc" id="L124"> workingDirectories[i] = createWorkingDirectory(folder.resolve(&quot;&quot; + (i + 1)));</span> <span class="pc bpc" id="L126" title="1 of 2 branches missed."> if (workingDirectories[i].isEmpty())</span> { <span class="fc" id="L128"> workingDirectories[i].cloneFrom(new URI(remoteRepositoryUrl));</span> } } <span class="fc" id="L132"> messageBus = beanFactory.getBean(&quot;applicationMessageBus&quot;, MessageBus.class); // FIXME</span> <span class="fc" id="L134"> swapRepositories(); // initialization</span> <span class="fc" id="L135"> swapCounter = 0;</span> <span class="fc" id="L136"> }</span> /******************************************************************************************************************* * * Checks whether there are incoming changes. See the class' documentation for more information. * ******************************************************************************************************************/ public void checkForUpdates() { try { <span class="fc" id="L147"> final var newTag = fetchChangesetsAndSearchForNewTag();</span> <span class="fc bfc" id="L149" title="All 2 branches covered."> if (newTag.isEmpty())</span> { <span class="fc" id="L151"> log.info(&quot;&gt;&gt;&gt;&gt; no changes&quot;);</span> } else { <span class="fc" id="L155"> final var tag = newTag.get();</span> <span class="fc" id="L156"> log.info(&quot;&gt;&gt;&gt;&gt; new tag: {}&quot;, tag);</span> <span class="fc" id="L157"> alternateWorkingDirectory.checkOut(tag);</span> <span class="fc" id="L158"> swapRepositories();</span> <span class="fc" id="L159"> messageBus.publish(new ResourceFileSystemChangedEvent(this, ZonedDateTime.now()));</span> <span class="fc" id="L160"> alternateWorkingDirectory.fetchChangesets();</span> <span class="fc" id="L161"> alternateWorkingDirectory.checkOut(tag);</span> } } <span class="nc" id="L164"> catch (ProcessExecutorException e)</span> { <span class="nc" id="L166"> log.warn(&quot;&gt;&gt;&gt;&gt; error when checking for updates in {}: exit code is {}&quot;,</span> <span class="nc" id="L167"> alternateWorkingDirectory.getFolder(),</span> <span class="nc" id="L168"> e.getExitCode());</span> <span class="nc" id="L169"> e.getStdout().forEach(s -&gt; log.warn(&quot;&gt;&gt;&gt;&gt; STDOUT: {}&quot;, s));</span> <span class="nc" id="L170"> e.getStderr().forEach(s -&gt; log.warn(&quot;&gt;&gt;&gt;&gt; STDERR: {}&quot;, s));</span> } <span class="nc" id="L172"> catch (Exception e)</span> { <span class="nc" id="L174"> log.warn(&quot;&gt;&gt;&gt;&gt; error when checking for updates in &quot; + alternateWorkingDirectory.getFolder(), e);</span> <span class="pc" id="L175"> }</span> <span class="fc" id="L176"> }</span> /******************************************************************************************************************* * * Creates a new {@link ScmWorkingDirectory} at the given path. * * @param path the path of the repository. * @return a {@code ScmWorkingDirectory} * ******************************************************************************************************************/ @Nonnull public abstract ScmWorkingDirectory createWorkingDirectory (@Nonnull Path path); /******************************************************************************************************************* * * Swaps the repositories. * * @throws IOException in case of error * @throws PropertyVetoException in case of error * ******************************************************************************************************************/ private void swapRepositories() throws IOException, PropertyVetoException { <span class="fc" id="L200"> exposedWorkingDirectory = workingDirectories[repositorySelector];</span> <span class="fc" id="L201"> repositorySelector = (repositorySelector + 1) % 2;</span> <span class="fc" id="L202"> alternateWorkingDirectory = workingDirectories[repositorySelector];</span> <span class="fc" id="L203"> fileSystemDelegate.setRootDirectory(exposedWorkingDirectory.getFolder().toFile());</span> <span class="fc" id="L204"> swapCounter++;</span> <span class="fc" id="L206"> log.info(&quot;New exposed working directory: {}&quot;, exposedWorkingDirectory.getFolder());</span> <span class="fc" id="L207"> log.info(&quot;New alternate working directory: {}&quot;, alternateWorkingDirectory.getFolder());</span> <span class="fc" id="L208"> }</span> /******************************************************************************************************************* * * Fetches changesets from the repository and searches for a new tag. * * @return the new tag * @throws IOException in case of error * @throws InterruptedException in case of error * ******************************************************************************************************************/ @Nonnull private Optional&lt;Tag&gt; fetchChangesetsAndSearchForNewTag() throws IOException, InterruptedException { <span class="fc" id="L223"> log.info(&quot;Checking for updates in {} ...&quot;, alternateWorkingDirectory.getFolder());</span> <span class="fc" id="L225"> alternateWorkingDirectory.fetchChangesets();</span> <span class="fc" id="L226"> final var latestTag = alternateWorkingDirectory.getLatestTagMatching(&quot;^published-.*&quot;);</span> <span class="fc" id="L227"> final var currentTag = exposedWorkingDirectory.getCurrentTag();</span> <span class="pc bpc" id="L229" title="1 of 2 branches missed."> if (latestTag.isEmpty())</span> { <span class="nc" id="L231"> return Optional.empty();</span> } <span class="pc bpc" id="L234" title="1 of 2 branches missed."> if (currentTag.isEmpty())</span> { <span class="nc" id="L236"> log.info(&quot;&gt;&gt;&gt;&gt; repo must be initialized - latest tag: {}&quot;, latestTag.map(Tag::getName).orElse(&quot;&lt;none&gt;&quot;));</span> <span class="nc" id="L237"> return latestTag;</span> } <span class="fc bfc" id="L240" title="All 2 branches covered."> if (!latestTag.equals(currentTag))</span> { <span class="fc" id="L242"> return latestTag;</span> } <span class="fc" id="L245"> return Optional.empty();</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>