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