DiaryPhotoCollectionProvider.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.List;
  32. import java.util.Map;
  33. import java.util.concurrent.ConcurrentHashMap;
  34. import java.util.stream.IntStream;
  35. import javax.xml.parsers.ParserConfigurationException;
  36. import javax.xml.xpath.XPath;
  37. import javax.xml.xpath.XPathExpression;
  38. import javax.xml.xpath.XPathExpressionException;
  39. import java.io.IOException;
  40. import java.nio.file.Paths;
  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.VirtualMediaFolder.EntityCollectionFactory;
  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.comparing;
  53. import static java.util.stream.Collectors.toList;
  54. import static javax.xml.xpath.XPathConstants.NODESET;

  55. /***********************************************************************************************************************
  56.  *
  57.  * @author  Fabrizio Giudici
  58.  *
  59.  **********************************************************************************************************************/
  60. @Slf4j
  61. public class DiaryPhotoCollectionProvider extends PhotoCollectionProviderSupport
  62.   {
  63.     private static final String URL_DIARY_TEMPLATE = "%s/diary/%d/";

  64.     private static final String REGEXP_URL_HOST_AND_PORT = "http:\\/\\/[^\\/]*";

  65.     private static final XPathExpression XPATH_DIARY_EXPR;

  66.     /**
  67.      * A local cache for themes is advisable because multiple calls will be performed.
  68.      */
  69.     private final Map<Integer, List<GalleryDescription>> diaryCache = new ConcurrentHashMap<>();

  70.     /*******************************************************************************************************************
  71.      *
  72.      ******************************************************************************************************************/
  73.     static
  74.       {
  75.         try
  76.           {
  77.             final XPath xpath = XPATH_FACTORY.newXPath();
  78.             XPATH_DIARY_EXPR = xpath.compile("//div[@class='nw-calendar']//li/a");
  79.           }
  80.         catch (XPathExpressionException e)
  81.           {
  82.             throw new ExceptionInInitializerError(e);
  83.           }
  84.       }

  85.     /*******************************************************************************************************************
  86.      *
  87.      ******************************************************************************************************************/
  88.     public DiaryPhotoCollectionProvider()
  89.       {
  90.         this(URL_STOPPINGDOWN);
  91.       }

  92.     /*******************************************************************************************************************
  93.      *
  94.      ******************************************************************************************************************/
  95.     public DiaryPhotoCollectionProvider (@Nonnull final String baseUrl)
  96.       {
  97.         super(baseUrl);
  98.       }

  99.     /*******************************************************************************************************************
  100.      *
  101.      ******************************************************************************************************************/
  102.     @Override
  103.     @Nonnull
  104.     public PathAwareFinder findPhotos(@Nonnull final MediaFolder parent) {
  105.         return parent.finderOf(
  106.                 p1 -> IntStream.range(1999, 2016 + 1) // FIXME: use current year
  107.                         .boxed()
  108.                         .map(year -> new VirtualMediaFolder(p1,
  109.                                 Paths.get("" + year),
  110.                                 "" + year,
  111.                                 (EntityCollectionFactory)(p2 -> entriesFactory(p2, year))))
  112.                         .collect(toList()));
  113.     }

  114.     /*******************************************************************************************************************
  115.      *
  116.      * {@inheritDoc}
  117.      *
  118.      ******************************************************************************************************************/
  119.     @Override
  120.     protected void clearCachesImpl()
  121.       {
  122.         super.clearCachesImpl();
  123.         diaryCache.clear();
  124.       }

  125.     /*******************************************************************************************************************
  126.      *
  127.      ******************************************************************************************************************/
  128.     @Nonnull
  129.     private Collection<PathAwareEntity> entriesFactory (@Nonnull final MediaFolder parent, final int year)
  130.       {
  131.         return parseDiary(year).stream().map(gallery -> gallery.createFolder(parent, this::findPhotos))
  132.                                         .collect(toList());
  133.       }

  134.     /*******************************************************************************************************************
  135.      *
  136.      ******************************************************************************************************************/
  137.     @Nonnull
  138.     @VisibleForTesting List<GalleryDescription> parseDiary (final int year)
  139.       {
  140.         final String diaryUrl = String.format(URL_DIARY_TEMPLATE, baseUrl, year);
  141.         log.debug("parseDiary({})", diaryUrl);

  142.         return diaryCache.computeIfAbsent(year, key ->
  143.           {
  144.             try
  145.               {
  146.                 final Document document = downloadXml(diaryUrl);
  147.                 final NodeList entryNodes = (NodeList)XPATH_DIARY_EXPR.evaluate(document, NODESET);
  148.                 final List<GalleryDescription> galleryDescriptions = new ArrayList<>();

  149.                 for (int i = 0; i < entryNodes.getLength(); i++)
  150.                   {
  151.                     final Node entryNode = entryNodes.item(i);
  152.                     final String href = getAttribute(entryNode, "href").replaceAll(REGEXP_URL_HOST_AND_PORT, "");
  153.                     final String url = String.format(URL_GALLERY_TEMPLATE, baseUrl, href).replace("//", "/")
  154.                                                                                          .replace(":/", "://")
  155.                                                .replaceAll("(^.*)\\/([0-9]{2})\\/([0-9]{2})\\/(.*)$", "$1/$2-$3/$4");
  156.                     final String date = href.substring(href.length() - 11, href.length() - 1);
  157.                     final String displayName = date + " - " + entryNode.getTextContent();
  158.                     galleryDescriptions.add(new GalleryDescription(displayName, url));
  159.                   }

  160.                 galleryDescriptions.sort(comparing(GalleryDescription::getUrl));

  161.                 return galleryDescriptions;
  162.               }
  163.             catch (SAXException | IOException | XPathExpressionException | ParserConfigurationException e)
  164.               {
  165.                 throw new RuntimeException(e);
  166.               }
  167.           });
  168.       }
  169.   }