Skip to content

Package: DefaultSite

DefaultSite

nameinstructionbranchcomplexitylinemethod
DefaultSite(Site.Builder)
M: 6 C: 163
96%
M: 11 C: 11
50%
M: 11 C: 1
8%
M: 0 C: 22
100%
M: 0 C: 1
100%
createContent(ResourceFile, ResourcePath)
M: 0 C: 13
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
createLink(ResourcePath)
M: 0 C: 32
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 6
100%
M: 0 C: 1
100%
createMedia(ResourceFile, ResourcePath)
M: 0 C: 13
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
createResource(ResourceFile, ResourcePath)
M: 0 C: 13
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
createSiteNode(ResourceFile, ResourcePath)
M: 2 C: 54
96%
M: 2 C: 4
67%
M: 2 C: 2
50%
M: 0 C: 10
100%
M: 0 C: 1
100%
find(Class)
M: 0 C: 20
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
findMandatoryFolder(ResourceFileSystem, String)
M: 0 C: 8
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getConfiguredLocales()
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
getTemplate(Class, Optional, String)
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getTemplate(Class, ResourcePath)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
getTemplateFactoryFor(Class)
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
initialize()
M: 0 C: 189
100%
M: 1 C: 1
50%
M: 1 C: 1
50%
M: 0 C: 36
100%
M: 0 C: 1
100%
lambda$logConfiguration$3(String, Object)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$new$0(ResourceFile)
M: 0 C: 13
100%
M: 1 C: 3
75%
M: 1 C: 2
67%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$new$1(ResourceFile)
M: 0 C: 13
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
lambda$traverse$2(ResourcePath, Predicate, BiConsumer, ResourceFile)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
logConfiguration(String, Map)
M: 0 C: 7
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
static {...}
M: 0 C: 5
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
M: 0 C: 1
100%
toString()
M: 11 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
traverse(ResourceFile, Predicate, BiConsumer)
M: 0 C: 7
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 2
100%
M: 0 C: 1
100%
traverse(ResourcePath, ResourceFile, Predicate, BiConsumer)
M: 0 C: 45
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 6
100%
M: 0 C: 1
100%

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.core.impl.model;
27:
28: import javax.annotation.Nonnull;
29: import javax.inject.Inject;
30: import javax.inject.Named;
31: import java.util.ArrayList;
32: import java.util.HashMap;
33: import java.util.List;
34: import java.util.Locale;
35: import java.util.Map;
36: import java.util.Optional;
37: import java.util.TreeMap;
38: import java.util.concurrent.CopyOnWriteArrayList;
39: import java.util.function.BiConsumer;
40: import java.util.function.Predicate;
41: import java.io.IOException;
42: import org.springframework.beans.factory.annotation.Configurable;
43: import it.tidalwave.util.NotFoundException;
44: import it.tidalwave.northernwind.core.model.Content;
45: import it.tidalwave.northernwind.core.model.Media;
46: import it.tidalwave.northernwind.core.model.ModelFactory;
47: import it.tidalwave.northernwind.core.model.Resource;
48: import it.tidalwave.northernwind.core.model.ResourceFile;
49: import it.tidalwave.northernwind.core.model.ResourceFileSystem;
50: import it.tidalwave.northernwind.core.model.ResourceFileSystemProvider;
51: import it.tidalwave.northernwind.core.model.ResourcePath;
52: import it.tidalwave.northernwind.core.model.Site;
53: import it.tidalwave.northernwind.core.model.SiteFinder;
54: import it.tidalwave.northernwind.core.model.SiteNode;
55: import it.tidalwave.northernwind.core.model.Template;
56: import it.tidalwave.northernwind.core.model.spi.LinkPostProcessor;
57: import it.tidalwave.northernwind.core.model.spi.RequestHolder;
58: import it.tidalwave.northernwind.core.impl.text.St4TemplateFactory;
59: import it.tidalwave.northernwind.core.impl.util.RegexTreeMap;
60: import lombok.Getter;
61: import lombok.extern.slf4j.Slf4j;
62:
63: /***************************************************************************************************************************************************************
64: *
65: * The default implementation of {@link Site}.
66: *
67: * @author Fabrizio Giudici
68: *
69: **************************************************************************************************************************************************************/
70: @Configurable @Slf4j
71: /* package */ class DefaultSite implements InternalSite
72: {
73: @Inject
74: private List<LinkPostProcessor> linkPostProcessors;
75:
76: @Inject
77: private RequestHolder requestHolder;
78:
79: @Nonnull
80: private final ModelFactory modelFactory;
81:
82: @Inject @Named("fileSystemProvider") @Getter
83: private ResourceFileSystemProvider fileSystemProvider;
84:
85: @Nonnull
86: /* package */ final String documentPath;
87:
88: @Nonnull
89: /* package */ final String mediaPath;
90:
91: @Nonnull
92: /* package */ final String libraryPath;
93:
94: @Nonnull
95: /* package */ final String nodePath;
96:
97: @Getter
98: /* package */ final boolean logConfigurationEnabled;
99:
100: @Getter @Nonnull
101: /* package */ final String contextPath;
102:
103: /* package */ final List<String> ignoredFolders = new ArrayList<>();
104:
105: private ResourceFile documentFolder;
106:
107: private ResourceFile libraryFolder;
108:
109: private ResourceFile mediaFolder;
110:
111: @Getter
112: private ResourceFile nodeFolder;
113:
114: // Note that this class can't be final neither use ImmutableMaps, since during the traversing of the filesystem
115: // resources need to access the partially created internal structure.
116:
117: /* package */ final Map<String, Content> documentMapByRelativePath = new TreeMap<>();
118:
119: /* package */ final Map<String, Resource> libraryMapByRelativePath = new TreeMap<>();
120:
121: /* package */ final Map<String, Media> mediaMapByRelativePath = new TreeMap<>();
122:
123: /* package */ final Map<String, SiteNode> nodeMapByRelativePath = new TreeMap<>();
124:
125: /* package */ final RegexTreeMap<SiteNode> nodeMapByRelativeUri = new RegexTreeMap<>();
126:
127: private final Map<Class<?>, Map<String, ?>> relativePathMapsByType = new HashMap<>();
128:
129: private final Map<Class<?>, RegexTreeMap<?>> relativeUriMapsByType = new HashMap<>();
130:
131: private final List<Locale> configuredLocales = new ArrayList<>();
132:
133: private final Predicate<ResourceFile> FOLDER_FILTER =
134:• file -> file.isFolder() && !ignoredFolders.contains(file.getName());
135:
136: private final Predicate<ResourceFile> FILE_FILTER =
137:• file -> file.isData() && !ignoredFolders.contains(file.getName());
138:
139: /***********************************************************************************************************************************************************
140: *
141: **********************************************************************************************************************************************************/
142: protected DefaultSite (@Nonnull final Site.Builder siteBuilder)
143:• {
144: this.modelFactory = siteBuilder.getModelFactory();
145: this.contextPath = siteBuilder.getContextPath();
146: this.documentPath = siteBuilder.getDocumentPath();
147: this.mediaPath = siteBuilder.getMediaPath();
148: this.libraryPath = siteBuilder.getLibraryPath();
149: this.nodePath = siteBuilder.getNodePath();
150: this.logConfigurationEnabled = siteBuilder.isLogConfigurationEnabled();
151: this.configuredLocales.addAll(siteBuilder.getConfiguredLocales());
152: this.ignoredFolders.addAll(siteBuilder.getIgnoredFolders());
153:• }
154:
155: /***********************************************************************************************************************************************************
156: * {@inheritDoc}
157: **********************************************************************************************************************************************************/
158: @Override @Nonnull @SuppressWarnings("unchecked")
159: public <Type> SiteFinder<Type> find (@Nonnull final Class<Type> type)
160: {
161: final var relativePathMap = (Map<String, Type>)relativePathMapsByType.get(type);
162: final var relativeUriMap = (RegexTreeMap<Type>)relativeUriMapsByType.get(type);
163: return new DefaultSiteFinder<>(type.getSimpleName(), relativePathMap, relativeUriMap);
164: }
165:
166: /***********************************************************************************************************************************************************
167: * {@inheritDoc}
168: **********************************************************************************************************************************************************/
169: @Override @Nonnull
170: public String createLink (@Nonnull final ResourcePath relativeUri)
171: {
172: final var link = ResourcePath.of(contextPath).appendedWith(relativeUri);
173: var linkAsString = requestHolder.get().getBaseUrl() + link.asString();
174:
175:• for (final var linkPostProcessor : linkPostProcessors)
176: {
177: linkAsString = linkPostProcessor.postProcess(linkAsString);
178: }
179:
180: return linkAsString;
181: }
182:
183: /***********************************************************************************************************************************************************
184: * {@inheritDoc}
185: **********************************************************************************************************************************************************/
186: @Override @Nonnull
187: public List<Locale> getConfiguredLocales()
188: {
189: return new CopyOnWriteArrayList<>(configuredLocales);
190: }
191:
192: /***********************************************************************************************************************************************************
193: * {@inheritDoc}
194: **********************************************************************************************************************************************************/
195: @Override @Nonnull
196: public Template getTemplate (@Nonnull final Class<?> clazz,
197: @Nonnull final Optional<? extends ResourcePath> templatePath,
198: @Nonnull final String embeddedResourceName)
199: {
200: return getTemplateFactoryFor(clazz).getTemplate(templatePath, embeddedResourceName);
201: }
202:
203: /***********************************************************************************************************************************************************
204: * {@inheritDoc}
205: **********************************************************************************************************************************************************/
206: @Override @Nonnull
207: public Optional<String> getTemplate (@Nonnull final Class<?> clazz, @Nonnull final ResourcePath templatePath)
208: {
209: return getTemplateFactoryFor(clazz).getTemplate(templatePath);
210: }
211:
212: /***********************************************************************************************************************************************************
213: * {@inheritDoc}
214: **********************************************************************************************************************************************************/
215: @Override @Nonnull
216: public String toString()
217: {
218: return String.format("DefaultSite(@%x)", System.identityHashCode(this));
219: }
220:
221: /***********************************************************************************************************************************************************
222: *
223: **********************************************************************************************************************************************************/
224: /* package */ void initialize()
225: throws IOException, NotFoundException
226: {
227: log.info("initialize()");
228:
229: relativePathMapsByType.put(Content.class, documentMapByRelativePath);
230: relativePathMapsByType.put(Media.class, mediaMapByRelativePath);
231: relativePathMapsByType.put(Resource.class, libraryMapByRelativePath);
232: relativePathMapsByType.put(SiteNode.class, nodeMapByRelativePath);
233:
234: relativeUriMapsByType.put(SiteNode.class, nodeMapByRelativeUri);
235:
236: log.info(">>>> fileSystemProvider: {}", fileSystemProvider);
237: final var fileSystem = fileSystemProvider.getFileSystem();
238: documentFolder = findMandatoryFolder(fileSystem, documentPath);
239: libraryFolder = findMandatoryFolder(fileSystem, libraryPath);
240: mediaFolder = findMandatoryFolder(fileSystem, mediaPath);
241: nodeFolder = findMandatoryFolder(fileSystem, nodePath);
242:
243: log.info(">>>> contextPath: {}", contextPath);
244: log.info(">>>> ignoredFolders: {}", ignoredFolders);
245: log.info(">>>> fileSystem: {}", fileSystem);
246: log.info(">>>> documentPath: {}", documentFolder.getPath().asString());
247: log.info(">>>> libraryPath: {}", libraryFolder.getPath().asString());
248: log.info(">>>> mediaPath: {}", mediaFolder.getPath().asString());
249: log.info(">>>> nodePath: {}", nodeFolder.getPath().asString());
250: log.info(">>>> locales: {}", configuredLocales);
251:
252: documentMapByRelativePath.clear();
253: libraryMapByRelativePath.clear();
254: mediaMapByRelativePath.clear();
255: nodeMapByRelativePath.clear();
256: nodeMapByRelativeUri.clear();
257:
258: traverse(libraryFolder, FILE_FILTER, this::createResource);
259: traverse(mediaFolder, FILE_FILTER, this::createMedia);
260: traverse(documentFolder, FOLDER_FILTER, this::createContent);
261: traverse(nodeFolder, FOLDER_FILTER, this::createSiteNode);
262:
263:• if (logConfigurationEnabled)
264: {
265: logConfiguration("Documents by relative path:", documentMapByRelativePath);
266: logConfiguration("Library by relative path:", libraryMapByRelativePath);
267: logConfiguration("Media by relative path:", mediaMapByRelativePath);
268: logConfiguration("Nodes by relative path:", nodeMapByRelativePath);
269: logConfiguration("Nodes by relative URI:", nodeMapByRelativeUri);
270: }
271: }
272:
273: /***********************************************************************************************************************************************************
274: *
275: **********************************************************************************************************************************************************/
276: private void createContent (@Nonnull final ResourceFile folder, @Nonnull final ResourcePath relativePath)
277: {
278: documentMapByRelativePath.put(relativePath.asString(), modelFactory.createContent().withFolder(folder).build());
279: }
280:
281: /***********************************************************************************************************************************************************
282: *
283: **********************************************************************************************************************************************************/
284: private void createMedia (@Nonnull final ResourceFile file, @Nonnull final ResourcePath relativePath)
285: {
286: mediaMapByRelativePath.put(relativePath.asString(), modelFactory.createMedia().withFile(file).build());
287: }
288:
289: /***********************************************************************************************************************************************************
290: *
291: **********************************************************************************************************************************************************/
292: private void createResource (@Nonnull final ResourceFile file, @Nonnull final ResourcePath relativePath)
293: {
294: libraryMapByRelativePath.put(relativePath.asString(), modelFactory.createResource().withFile(file).build());
295: }
296:
297: /***********************************************************************************************************************************************************
298: *
299: **********************************************************************************************************************************************************/
300: private void createSiteNode (@Nonnull final ResourceFile folder, @Nonnull final ResourcePath relativePath)
301: {
302: final var siteNode = modelFactory.createSiteNode(this, folder);
303: nodeMapByRelativePath.put(relativePath.asString(), siteNode);
304:
305:• if (!siteNode.isPlaceHolder())
306: {
307: final var relativeUri = siteNode.getRelativeUri();
308: // Nodes which manage path params are registered with a relativeUri having a wildcard suffix
309:• if (siteNode.getProperty(SiteNode.P_MANAGES_PATH_PARAMS).orElse(false))
310: {
311:• final var suffix = relativeUri.asString().endsWith("/") ? "(|.*$)" : "(|/.*$)";
312: nodeMapByRelativeUri.putRegex("^" + RegexTreeMap.escape(relativeUri.asString()) + suffix, siteNode);
313: }
314: else
315: {
316: nodeMapByRelativeUri.put(relativeUri.asString(), siteNode);
317: }
318: }
319: }
320:
321: /***********************************************************************************************************************************************************
322: * Traverse the file system with a consumer.
323: *
324: * @param folder the folder to traverse
325: * @param fileFilter the filter for directory contents
326: * @param consumer the consumer
327: **********************************************************************************************************************************************************/
328: private void traverse (@Nonnull final ResourceFile folder,
329: @Nonnull final Predicate<? super ResourceFile> fileFilter,
330: @Nonnull final BiConsumer<ResourceFile, ? super ResourcePath> consumer)
331: {
332: traverse(folder.getPath(), folder, fileFilter, consumer);
333: }
334:
335: /***********************************************************************************************************************************************************
336: * Traverse the file system with a {@link Predicate}.
337: *
338: * @param file the file to traverse
339: * @param fileFilter the filter for directory contents
340: * @param consumer the consumer
341: **********************************************************************************************************************************************************/
342: private static void traverse (@Nonnull final ResourcePath rootPath,
343: @Nonnull final ResourceFile file,
344: @Nonnull final Predicate<? super ResourceFile> fileFilter,
345: @Nonnull final BiConsumer<? super ResourceFile, ? super ResourcePath> consumer)
346: {
347: log.trace("traverse({}, {}, {}, {})", rootPath, file, fileFilter, consumer);
348: final var relativePath = file.getPath().urlDecoded().relativeTo(rootPath);
349:
350:• if (fileFilter.test(file))
351: {
352: consumer.accept(file, relativePath);
353: }
354:
355: file.findChildren().results().forEach(child -> traverse(rootPath, child, fileFilter, consumer));
356: }
357:
358: /***********************************************************************************************************************************************************
359: * Logs the configuration contained in the given map of properties.
360: **********************************************************************************************************************************************************/
361: private static void logConfiguration (@Nonnull final String name, final Map<String, ?> propertyMap)
362: {
363: log.info(name);
364: propertyMap.forEach((key, value) -> log.info(">>>> {}: {}", key, value));
365: }
366:
367: /***********************************************************************************************************************************************************
368: * FIXME Wrapper against ResourceFileSystem: its methods should throw NFE by themselves
369: **********************************************************************************************************************************************************/
370: @Nonnull
371: private static ResourceFile findMandatoryFolder (@Nonnull final ResourceFileSystem fileSystem,
372: @Nonnull final String path)
373: throws NotFoundException
374: {
375: return NotFoundException.throwWhenNull(fileSystem.findFileByPath(path), "Cannot find folder: " + path);
376: // don't log fileSystem.getRoot() since if fileSystem is broken it can trigger secondary errors
377: // FileUtil.toFile(fileSystem.getRoot()).getAbsolutePath() + "/" + path);
378: }
379:
380: /***********************************************************************************************************************************************************
381: *
382: **********************************************************************************************************************************************************/
383: @Nonnull
384: private St4TemplateFactory getTemplateFactoryFor (@Nonnull final Class<?> clazz)
385: {
386: // TODO: implement a cache
387: return new St4TemplateFactory(clazz, this);
388: }
389: }