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