Package: ScmFileSystemProvider
ScmFileSystemProvider
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ScmFileSystemProvider() |
|
|
|
|
|
||||||||||||||||||||
checkForUpdates() |
|
|
|
|
|
||||||||||||||||||||
fetchChangesetsAndSearchForNewTag() |
|
|
|
|
|
||||||||||||||||||||
getFileSystem() |
|
|
|
|
|
||||||||||||||||||||
getFolderAsString() |
|
|
|
|
|
||||||||||||||||||||
getRemoteRepositoryUrl() |
|
|
|
|
|
||||||||||||||||||||
initialize() |
|
|
|
|
|
||||||||||||||||||||
lambda$checkForUpdates$0(String) |
|
|
|
|
|
||||||||||||||||||||
lambda$checkForUpdates$1(String) |
|
|
|
|
|
||||||||||||||||||||
setFolderAsString(String) |
|
|
|
|
|
||||||||||||||||||||
setRemoteRepositoryUrl(String) |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
||||||||||||||||||||
swapRepositories() |
|
|
|
|
|
Coverage
1: /*
2: * #%L
3: * *********************************************************************************************************************
4: *
5: * NorthernWind - lightweight CMS
6: * http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
7: * %%
8: * Copyright (C) 2011 - 2023 Tidalwave s.a.s. (http://tidalwave.it)
9: * %%
10: * *********************************************************************************************************************
11: *
12: * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
13: * the License. You may obtain a copy of the License at
14: *
15: * http://www.apache.org/licenses/LICENSE-2.0
16: *
17: * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
18: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
19: * specific language governing permissions and limitations under the License.
20: *
21: * *********************************************************************************************************************
22: *
23: *
24: * *********************************************************************************************************************
25: * #L%
26: */
27: package it.tidalwave.northernwind.frontend.filesystem.scm.spi;
28:
29: import javax.annotation.Nonnull;
30: import javax.annotation.PostConstruct;
31: import javax.annotation.concurrent.NotThreadSafe;
32: import javax.inject.Inject;
33: import java.time.ZonedDateTime;
34: import java.beans.PropertyVetoException;
35: import java.util.Optional;
36: import java.io.File;
37: import java.io.IOException;
38: import java.nio.file.Path;
39: import java.net.URI;
40: import java.net.URISyntaxException;
41: import org.springframework.beans.factory.BeanFactory;
42: import org.openide.filesystems.LocalFileSystem;
43: import it.tidalwave.util.ProcessExecutorException;
44: import it.tidalwave.messagebus.MessageBus;
45: import it.tidalwave.northernwind.core.model.ResourceFileSystem;
46: import it.tidalwave.northernwind.core.model.ResourceFileSystemChangedEvent;
47: import it.tidalwave.northernwind.core.model.ResourceFileSystemProvider;
48: import it.tidalwave.northernwind.frontend.filesystem.impl.ResourceFileSystemNetBeansPlatform;
49: import lombok.Getter;
50: import lombok.Setter;
51: import lombok.extern.slf4j.Slf4j;
52:
53: /***********************************************************************************************************************
54: *
55: * A {@code ResourceFileSystemProvider} based on a SCM. This is an abstract support class that needs to be extended
56: * by concrete implementations (such as Git or Mercurial).
57: * This provider polls for changes in the SCM that are made available with a new tag named
58: * {@code published-<version>} and fetches them. In order to atomically expose changes, in spite of the fact that the
59: * underlying operation might require some time to update all files, two working directories are used:
60: *
61: * <ol>
62: * <li>the {@code exposedWorkingDirectory} is exposed and not affected by next change;</li>
63: * <li>the {@code alternateWorkingDirectory} is kept behind the scenes and updated; when its update is completed, the
64: * two repositories are swapped and a {@link ResourceFileSystemChangedEvent} is fired.</li>
65: * </ol>
66: *
67: * @author Fabrizio Giudici
68: *
69: **********************************************************************************************************************/
70: @NotThreadSafe @Slf4j
71: public abstract class ScmFileSystemProvider implements ResourceFileSystemProvider
72: {
73: /** The URL of the remote repository. */
74: @Getter @Setter
75: private String remoteRepositoryUrl;
76:
77: /** The folder of the local work area. */
78: @Getter @Setter
79: private String folderAsString;
80:
81: /** The path of the working directory. */
82: private Path folder;
83:
84: /** The file system used to map the local work area. */
85: /* visible for tests */ final LocalFileSystem fileSystemDelegate = new LocalFileSystem();
86:
87: @Getter
88: private final ResourceFileSystem fileSystem = new ResourceFileSystemNetBeansPlatform(fileSystemDelegate);
89:
90: private final ScmWorkingDirectory[] workingDirectories = new ScmWorkingDirectory[2];
91:
92: /** The exposed working directory. */
93: /* visible for tests */ ScmWorkingDirectory exposedWorkingDirectory;
94:
95: /** The alternate working directory. */
96: /* visible for tests */ ScmWorkingDirectory alternateWorkingDirectory;
97:
98: /** The index of the exposed repository (0 or 1). */
99: private int repositorySelector;
100:
101: /** A counter of swaps, used for testing. */
102: /* visible for tests */int swapCounter;
103:
104: @Inject
105: private BeanFactory beanFactory;
106:
107: /** The message bus where to fire events. */
108: // @Inject @Named("applicationMessageBus") FIXME doesn't work in the test
109: private MessageBus messageBus;
110:
111: /*******************************************************************************************************************
112: *
113: * Makes sure both repository repositories are populated and activates one of them.
114: *
115: ******************************************************************************************************************/
116: @PostConstruct
117: private void initialize()
118: throws IOException, PropertyVetoException, URISyntaxException, InterruptedException
119: {
120: folder = new File(folderAsString).toPath();
121:
122:• for (var i = 0; i < 2; i++)
123: {
124: workingDirectories[i] = createWorkingDirectory(folder.resolve("" + (i + 1)));
125:
126:• if (workingDirectories[i].isEmpty())
127: {
128: workingDirectories[i].cloneFrom(new URI(remoteRepositoryUrl));
129: }
130: }
131:
132: messageBus = beanFactory.getBean("applicationMessageBus", MessageBus.class); // FIXME
133:
134: swapRepositories(); // initialization
135: swapCounter = 0;
136: }
137:
138: /*******************************************************************************************************************
139: *
140: * Checks whether there are incoming changes. See the class' documentation for more information.
141: *
142: ******************************************************************************************************************/
143: public void checkForUpdates()
144: {
145: try
146: {
147: final var newTag = fetchChangesetsAndSearchForNewTag();
148:
149:• if (newTag.isEmpty())
150: {
151: log.info(">>>> no changes");
152: }
153: else
154: {
155: final var tag = newTag.get();
156: log.info(">>>> new tag: {}", tag);
157: alternateWorkingDirectory.checkOut(tag);
158: swapRepositories();
159: messageBus.publish(new ResourceFileSystemChangedEvent(this, ZonedDateTime.now()));
160: alternateWorkingDirectory.fetchChangesets();
161: alternateWorkingDirectory.checkOut(tag);
162: }
163: }
164: catch (ProcessExecutorException e)
165: {
166: log.warn(">>>> error when checking for updates in {}: exit code is {}",
167: alternateWorkingDirectory.getFolder(),
168: e.getExitCode());
169: e.getStdout().forEach(s -> log.warn(">>>> STDOUT: {}", s));
170: e.getStderr().forEach(s -> log.warn(">>>> STDERR: {}", s));
171: }
172: catch (Exception e)
173: {
174: log.warn(">>>> error when checking for updates in " + alternateWorkingDirectory.getFolder(), e);
175: }
176: }
177:
178: /*******************************************************************************************************************
179: *
180: * Creates a new {@link ScmWorkingDirectory} at the given path.
181: *
182: * @param path the path of the repository.
183: * @return a {@code ScmWorkingDirectory}
184: *
185: ******************************************************************************************************************/
186: @Nonnull
187: public abstract ScmWorkingDirectory createWorkingDirectory (@Nonnull Path path);
188:
189: /*******************************************************************************************************************
190: *
191: * Swaps the repositories.
192: *
193: * @throws IOException in case of error
194: * @throws PropertyVetoException in case of error
195: *
196: ******************************************************************************************************************/
197: private void swapRepositories()
198: throws IOException, PropertyVetoException
199: {
200: exposedWorkingDirectory = workingDirectories[repositorySelector];
201: repositorySelector = (repositorySelector + 1) % 2;
202: alternateWorkingDirectory = workingDirectories[repositorySelector];
203: fileSystemDelegate.setRootDirectory(exposedWorkingDirectory.getFolder().toFile());
204: swapCounter++;
205:
206: log.info("New exposed working directory: {}", exposedWorkingDirectory.getFolder());
207: log.info("New alternate working directory: {}", alternateWorkingDirectory.getFolder());
208: }
209:
210: /*******************************************************************************************************************
211: *
212: * Fetches changesets from the repository and searches for a new tag.
213: *
214: * @return the new tag
215: * @throws IOException in case of error
216: * @throws InterruptedException in case of error
217: *
218: ******************************************************************************************************************/
219: @Nonnull
220: private Optional<Tag> fetchChangesetsAndSearchForNewTag()
221: throws IOException, InterruptedException
222: {
223: log.info("Checking for updates in {} ...", alternateWorkingDirectory.getFolder());
224:
225: alternateWorkingDirectory.fetchChangesets();
226: final var latestTag = alternateWorkingDirectory.getLatestTagMatching("^published-.*");
227: final var currentTag = exposedWorkingDirectory.getCurrentTag();
228:
229:• if (latestTag.isEmpty())
230: {
231: return Optional.empty();
232: }
233:
234:• if (currentTag.isEmpty())
235: {
236: log.info(">>>> repo must be initialized - latest tag: {}", latestTag.map(Tag::getName).orElse("<none>"));
237: return latestTag;
238: }
239:
240:• if (!latestTag.equals(currentTag))
241: {
242: return latestTag;
243: }
244:
245: return Optional.empty();
246: }
247: }