ThemesPhotoCollectionProvider.java

  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. import javax.annotation.Nonnull;
  29. import java.util.ArrayList;
  30. import java.util.Collection;
  31. import java.util.concurrent.ConcurrentHashMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.io.IOException;
  35. import java.nio.file.Paths;
  36. import java.nio.file.Path;
  37. import javax.xml.parsers.ParserConfigurationException;
  38. import javax.xml.xpath.XPath;
  39. import javax.xml.xpath.XPathExpression;
  40. import javax.xml.xpath.XPathExpressionException;
  41. import it.tidalwave.util.annotation.VisibleForTesting;
  42. import org.xml.sax.SAXException;
  43. import org.w3c.dom.Document;
  44. import org.w3c.dom.Node;
  45. import org.w3c.dom.NodeList;
  46. import it.tidalwave.bluemarine2.model.MediaFolder;
  47. import it.tidalwave.bluemarine2.model.VirtualMediaFolder;
  48. import it.tidalwave.bluemarine2.model.spi.PathAwareEntity;
  49. import it.tidalwave.bluemarine2.model.spi.PathAwareFinder;
  50. import lombok.extern.slf4j.Slf4j;
  51. import static java.util.Comparator.comparing;
  52. import static java.util.stream.Collectors.*;
  53. import static javax.xml.xpath.XPathConstants.*;

  54. /***********************************************************************************************************************
  55.  *
  56.  * @author  Fabrizio Giudici
  57.  *
  58.  **********************************************************************************************************************/
  59. @Slf4j
  60. public class ThemesPhotoCollectionProvider extends PhotoCollectionProviderSupport
  61.   {
  62.     private static final String URL_THEMES_TEMPLATE = "%s/themes/";

  63.     private static final Path PATH_SUBJECTS = Paths.get("subjects");

  64.     private static final Path PATH_PLACES = Paths.get("places");

  65.     @VisibleForTesting static final XPathExpression XPATH_SUBJECTS_THUMBNAIL_EXPR;

  66.     @VisibleForTesting static final XPathExpression XPATH_PLACES_THUMBNAIL_EXPR;

  67.     private static final XPathExpression XPATH_THUMBNAIL_URL_EXPR;

  68.     private static final XPathExpression XPATH_THUMBNAIL_DESCRIPTION_EXPR;

  69.     /**
  70.      * A local cache for themes is advisable because multiple calls will be performed.
  71.      */
  72.     private final Map<XPathExpression, List<GalleryDescription>> themesCache = new ConcurrentHashMap<>();

  73.     /*******************************************************************************************************************
  74.      *
  75.      ******************************************************************************************************************/
  76.     static
  77.       {
  78.         try
  79.           {
  80.             final XPath xpath = XPATH_FACTORY.newXPath();
  81. //            XPATH_SUBJECTS_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/h3[1]/following-sibling::div[@class='thumbnail']");
  82. //            XPATH_PLACES_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/h3[2]/following-sibling::div[@class='thumbnail']");
  83.             XPATH_SUBJECTS_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/div[@class='row'][1]//div[@class='thumbnail']");
  84.             XPATH_PLACES_THUMBNAIL_EXPR = xpath.compile("//div[@class='container-fluid']/div[@class='row'][2]//div[@class='thumbnail']");
  85.             XPATH_THUMBNAIL_URL_EXPR = xpath.compile("a/@href");
  86.             XPATH_THUMBNAIL_DESCRIPTION_EXPR = xpath.compile(".//div[@class='caption']/h3/text()");
  87.           }
  88.         catch (XPathExpressionException e)
  89.           {
  90.             throw new ExceptionInInitializerError(e);
  91.           }
  92.       }

  93.     /*******************************************************************************************************************
  94.      *
  95.      ******************************************************************************************************************/
  96.     public ThemesPhotoCollectionProvider()
  97.       {
  98.         this(URL_STOPPINGDOWN);
  99.       }

  100.     /*******************************************************************************************************************
  101.      *
  102.      ******************************************************************************************************************/
  103.     public ThemesPhotoCollectionProvider (@Nonnull final String baseUrl)
  104.       {
  105.         super(baseUrl);
  106.       }

  107.     /*******************************************************************************************************************
  108.      *
  109.      * {@inheritDoc}
  110.      *
  111.      ******************************************************************************************************************/
  112.     @Override @Nonnull
  113.     public PathAwareFinder findPhotos (@Nonnull final MediaFolder parent)
  114.       {
  115.         return parent.finderOf(p -> List.of(
  116.                 new VirtualMediaFolder(p, PATH_PLACES,   "Places",   this::placesFactory),
  117.                 new VirtualMediaFolder(p, PATH_SUBJECTS, "Subjects", this::subjectsFactory)));
  118.       }

  119.     /*******************************************************************************************************************
  120.      *
  121.      * {@inheritDoc}
  122.      *
  123.      ******************************************************************************************************************/
  124.     @Override
  125.     protected void clearCachesImpl()
  126.       {
  127.         super.clearCachesImpl();
  128.         themesCache.clear();
  129.       }

  130.     /*******************************************************************************************************************
  131.      *
  132.      ******************************************************************************************************************/
  133.     @Nonnull
  134.     private Collection<PathAwareEntity> subjectsFactory (@Nonnull final MediaFolder parent)
  135.       {
  136.         return parseThemes(XPATH_SUBJECTS_THUMBNAIL_EXPR).stream()
  137.                                                          .map(gallery -> gallery.createFolder(parent, this::findPhotos))
  138.                                                          .collect(toList());
  139.       }

  140.     /*******************************************************************************************************************
  141.      *
  142.      ******************************************************************************************************************/
  143.     @Nonnull
  144.     private Collection<PathAwareEntity> placesFactory (@Nonnull final MediaFolder parent)
  145.       {
  146.         return parseThemes(XPATH_PLACES_THUMBNAIL_EXPR).stream()
  147.                                                        .map(gallery -> gallery.createFolder(parent, this::findPhotos))
  148.                                                        .collect(toList());
  149.       }

  150.     /*******************************************************************************************************************
  151.      *
  152.      ******************************************************************************************************************/
  153.     @Nonnull
  154.     @VisibleForTesting List<GalleryDescription> parseThemes (@Nonnull final XPathExpression expr)
  155.       {
  156.         final String themeUrl = String.format(URL_THEMES_TEMPLATE, baseUrl);
  157.         log.debug("parseThemes({}, {})", themeUrl, expr);

  158.         return themesCache.computeIfAbsent(expr, key ->
  159.           {
  160.             try
  161.               {
  162.                 final Document document = downloadXml(themeUrl);
  163.                 final NodeList thumbnailNodes = (NodeList)expr.evaluate(document, NODESET);
  164.                 final List<GalleryDescription> galleryDescriptions = new ArrayList<>();

  165.                 for (int i = 0; i < thumbnailNodes.getLength(); i++)
  166.                   {
  167.                     final Node thumbnailNode = thumbnailNodes.item(i);
  168.                     final String description = (String)XPATH_THUMBNAIL_DESCRIPTION_EXPR.evaluate(thumbnailNode, STRING);
  169.                     final String href = (String)XPATH_THUMBNAIL_URL_EXPR.evaluate(thumbnailNode, STRING);
  170.                     final String url = String.format(URL_GALLERY_TEMPLATE, baseUrl, href).replace("//", "/")
  171.                                                                                          .replace(":/", "://");
  172.                     galleryDescriptions.add(new GalleryDescription(description, url));
  173.                   }

  174.                 galleryDescriptions.sort(comparing(GalleryDescription::getDisplayName));

  175.                 return galleryDescriptions;
  176.               }
  177.             catch (SAXException | IOException | XPathExpressionException | ParserConfigurationException e)
  178.               {
  179.                 throw new RuntimeException(e);
  180.               }
  181.           });
  182.       }
  183.   }