Skip to contentMethod: ThemesPhotoCollectionProvider(String)
1: /*
2: * *********************************************************************************************************************
3: *
4: * blueMarine II: Semantic Media Centre
5: * http://tidalwave.it/projects/bluemarine2
6: *
7: * Copyright (C) 2015 - 2021 by 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
12: * the License. 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
17: * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
18: * specific language governing permissions and limitations under the License.
19: *
20: * *********************************************************************************************************************
21: *
22: * git clone https://bitbucket.org/tidalwave/bluemarine2-src
23: * git clone https://github.com/tidalwave-it/bluemarine2-src
24: *
25: * *********************************************************************************************************************
26: */
27: package it.tidalwave.bluemarine2.service.stoppingdown.impl;
28:
29: import javax.annotation.Nonnull;
30: import java.util.ArrayList;
31: import java.util.Collection;
32: import java.util.List;
33: import java.util.Map;
34: import java.util.concurrent.ConcurrentHashMap;
35: import java.io.IOException;
36: import java.nio.file.Path;
37: import java.nio.file.Paths;
38: import javax.xml.parsers.ParserConfigurationException;
39: import javax.xml.xpath.XPath;
40: import javax.xml.xpath.XPathExpression;
41: import javax.xml.xpath.XPathExpressionException;
42: import org.w3c.dom.Document;
43: import org.w3c.dom.Node;
44: import org.w3c.dom.NodeList;
45: import org.xml.sax.SAXException;
46: import it.tidalwave.util.annotation.VisibleForTesting;
47: import it.tidalwave.bluemarine2.model.MediaFolder;
48: import it.tidalwave.bluemarine2.model.VirtualMediaFolder;
49: import it.tidalwave.bluemarine2.model.spi.PathAwareEntity;
50: import it.tidalwave.bluemarine2.model.spi.PathAwareFinder;
51: import lombok.extern.slf4j.Slf4j;
52: import static java.util.Comparator.*;
53: import static java.util.stream.Collectors.*;
54: import static javax.xml.xpath.XPathConstants.*;
55:
56: /***********************************************************************************************************************
57: *
58: * @author Fabrizio Giudici
59: *
60: **********************************************************************************************************************/
61: @Slf4j
62: public class ThemesPhotoCollectionProvider extends PhotoCollectionProviderSupport
63: {
64: private static final String URL_THEMES_TEMPLATE = "%s/themes/";
65:
66: private static final Path PATH_SUBJECTS = Paths.get("subjects");
67:
68: private static final Path PATH_PLACES = Paths.get("places");
69:
70: @VisibleForTesting static final XPathExpression XPATH_SUBJECTS_THUMBNAIL_EXPR;
71:
72: @VisibleForTesting static final XPathExpression XPATH_PLACES_THUMBNAIL_EXPR;
73:
74: private static final XPathExpression XPATH_THUMBNAIL_URL_EXPR;
75:
76: private static final XPathExpression XPATH_THUMBNAIL_DESCRIPTION_EXPR;
77:
78: /**
79: * A local cache for themes is advisable because multiple calls will be performed.
80: */
81: private final Map<XPathExpression, List<GalleryDescription>> themesCache = new ConcurrentHashMap<>();
82:
83: /*******************************************************************************************************************
84: *
85: ******************************************************************************************************************/
86: static
87: {
88: try
89: {
90: final XPath xpath = XPATH_FACTORY.newXPath();
91: // XPATH_SUBJECTS_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/h3[1]/following-sibling::div[@class='thumbnail']");
92: // XPATH_PLACES_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/h3[2]/following-sibling::div[@class='thumbnail']");
93: XPATH_SUBJECTS_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/div[@class='row'][1]//div[@class='thumbnail']");
94: XPATH_PLACES_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/div[@class='row'][2]//div[@class='thumbnail']");
95: XPATH_THUMBNAIL_URL_EXPR = xpath.compile("a/@href");
96: XPATH_THUMBNAIL_DESCRIPTION_EXPR = xpath.compile(".//div[@class='caption']/h3/text()");
97: }
98: catch (XPathExpressionException e)
99: {
100: throw new ExceptionInInitializerError(e);
101: }
102: }
103:
104: /*******************************************************************************************************************
105: *
106: ******************************************************************************************************************/
107: public ThemesPhotoCollectionProvider()
108: {
109: this(URL_STOPPINGDOWN);
110: }
111:
112: /*******************************************************************************************************************
113: *
114: ******************************************************************************************************************/
115: public ThemesPhotoCollectionProvider (@Nonnull final String baseUrl)
116: {
117: super(baseUrl);
118: }
119:
120: /*******************************************************************************************************************
121: *
122: * {@inheritDoc}
123: *
124: ******************************************************************************************************************/
125: @Override @Nonnull
126: public PathAwareFinder findPhotos (@Nonnull final MediaFolder parent)
127: {
128: return parent.finderOf(p -> List.of(
129: new VirtualMediaFolder(p, PATH_PLACES, "Places", this::placesFactory),
130: new VirtualMediaFolder(p, PATH_SUBJECTS, "Subjects", this::subjectsFactory)));
131: }
132:
133: /*******************************************************************************************************************
134: *
135: * {@inheritDoc}
136: *
137: ******************************************************************************************************************/
138: @Override
139: protected void clearCachesImpl()
140: {
141: super.clearCachesImpl();
142: themesCache.clear();
143: }
144:
145: /*******************************************************************************************************************
146: *
147: ******************************************************************************************************************/
148: @Nonnull
149: private Collection<PathAwareEntity> subjectsFactory (@Nonnull final MediaFolder parent)
150: {
151: return parseThemes(XPATH_SUBJECTS_THUMBNAIL_EXPR).stream()
152: .map(gallery -> gallery.createFolder(parent, this::findPhotos))
153: .collect(toList());
154: }
155:
156: /*******************************************************************************************************************
157: *
158: ******************************************************************************************************************/
159: @Nonnull
160: private Collection<PathAwareEntity> placesFactory (@Nonnull final MediaFolder parent)
161: {
162: return parseThemes(XPATH_PLACES_THUMBNAIL_EXPR).stream()
163: .map(gallery -> gallery.createFolder(parent, this::findPhotos))
164: .collect(toList());
165: }
166:
167: /*******************************************************************************************************************
168: *
169: ******************************************************************************************************************/
170: @Nonnull
171: @VisibleForTesting List<GalleryDescription> parseThemes (@Nonnull final XPathExpression expr)
172: {
173: final String themeUrl = String.format(URL_THEMES_TEMPLATE, baseUrl);
174: log.debug("parseThemes({}, {})", themeUrl, expr);
175:
176: return themesCache.computeIfAbsent(expr, key ->
177: {
178: try
179: {
180: final Document document = downloadXml(themeUrl);
181: final NodeList thumbnailNodes = (NodeList)expr.evaluate(document, NODESET);
182: final List<GalleryDescription> galleryDescriptions = new ArrayList<>();
183:
184: for (int i = 0; i < thumbnailNodes.getLength(); i++)
185: {
186: final Node thumbnailNode = thumbnailNodes.item(i);
187: final String description = (String)XPATH_THUMBNAIL_DESCRIPTION_EXPR.evaluate(thumbnailNode, STRING);
188: final String href = (String)XPATH_THUMBNAIL_URL_EXPR.evaluate(thumbnailNode, STRING);
189: final String url = String.format(URL_GALLERY_TEMPLATE, baseUrl, href).replace("//", "/")
190: .replace(":/", "://");
191: galleryDescriptions.add(new GalleryDescription(description, url));
192: }
193:
194: galleryDescriptions.sort(comparing(GalleryDescription::getDisplayName));
195:
196: return galleryDescriptions;
197: }
198: catch (SAXException | IOException | XPathExpressionException | ParserConfigurationException e)
199: {
200: throw new RuntimeException(e);
201: }
202: });
203: }
204: }