<?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> > <a href="index.source.html" class="el_package">it.tidalwave.northernwind.frontend.filesystem.scm.spi</a> > <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 "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.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-<version>} 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:
*
* <ol>
* <li>the {@code exposedWorkingDirectory} is exposed and not affected by next change;</li>
* <li>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.</li>
* </ol>
*
* @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("applicationMessageBus") FIXME 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 < 2; i++)</span>
{
<span class="fc" id="L124"> workingDirectories[i] = createWorkingDirectory(folder.resolve("" + (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("applicationMessageBus", 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(">>>> no changes");</span>
}
else
{
<span class="fc" id="L155"> final var tag = newTag.get();</span>
<span class="fc" id="L156"> log.info(">>>> new tag: {}", 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(">>>> error when checking for updates in {}: exit code is {}",</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 -> log.warn(">>>> STDOUT: {}", s));</span>
<span class="nc" id="L170"> e.getStderr().forEach(s -> log.warn(">>>> STDERR: {}", s));</span>
}
<span class="nc" id="L172"> catch (Exception e)</span>
{
<span class="nc" id="L174"> log.warn(">>>> error when checking for updates in " + 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("New exposed working directory: {}", exposedWorkingDirectory.getFolder());</span>
<span class="fc" id="L207"> log.info("New alternate working directory: {}", 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<Tag> fetchChangesetsAndSearchForNewTag()
throws IOException, InterruptedException
{
<span class="fc" id="L223"> log.info("Checking for updates in {} ...", alternateWorkingDirectory.getFolder());</span>
<span class="fc" id="L225"> alternateWorkingDirectory.fetchChangesets();</span>
<span class="fc" id="L226"> final var latestTag = alternateWorkingDirectory.getLatestTagMatching("^published-.*");</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(">>>> repo must be initialized - latest tag: {}", latestTag.map(Tag::getName).orElse("<none>"));</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>