PhotoCollectionProviderSupport.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.Map;
  33. import javax.xml.xpath.XPathConstants;
  34. import javax.xml.xpath.XPathExpressionException;
  35. import javax.xml.parsers.DocumentBuilderFactory;
  36. import javax.xml.parsers.ParserConfigurationException;
  37. import javax.xml.xpath.XPath;
  38. import javax.xml.xpath.XPathExpression;
  39. import javax.xml.xpath.XPathFactory;
  40. import java.io.IOException;
  41. import it.tidalwave.util.annotation.VisibleForTesting;
  42. import org.w3c.dom.Document;
  43. import org.w3c.dom.DOMException;
  44. import org.w3c.dom.Node;
  45. import org.w3c.dom.NodeList;
  46. import org.xml.sax.SAXException;
  47. import org.springframework.scheduling.annotation.Scheduled;
  48. import it.tidalwave.bluemarine2.model.MediaFolder;
  49. import it.tidalwave.bluemarine2.model.spi.PathAwareEntity;
  50. import it.tidalwave.bluemarine2.model.spi.PathAwareFinder;
  51. import lombok.RequiredArgsConstructor;
  52. import lombok.extern.slf4j.Slf4j;

  53. /***********************************************************************************************************************
  54.  *
  55.  * @author  Fabrizio Giudici
  56.  *
  57.  **********************************************************************************************************************/
  58. @RequiredArgsConstructor @Slf4j
  59. public class PhotoCollectionProviderSupport implements PhotoCollectionProvider
  60.   {
  61.     protected static final String URL_STOPPINGDOWN = System.getProperty("stoppingdown", "http://stoppingdown.net");

  62.     protected static final String URL_GALLERY_TEMPLATE = "%s%s/images.xml";

  63.     protected static final DocumentBuilderFactory PARSER_FACTORY = DocumentBuilderFactory.newInstance();

  64.     // FIXME: XPath stuff is not thread-safe - fix!
  65.     protected static final XPathFactory XPATH_FACTORY = XPathFactory.newInstance();

  66.     private static final XPathExpression XPATH_STILLIMAGE_EXPR;

  67.     @Nonnull
  68.     protected final String baseUrl;

  69.     /**
  70.      * A local cache for finders. It's advisable, since clients will frequently retrieve a finder because of pagination.
  71.      */
  72.     private final Map<String, Collection<PathAwareEntity>> photoCollectionCache = new ConcurrentHashMap<>();

  73.     /*******************************************************************************************************************
  74.      *
  75.      ******************************************************************************************************************/
  76.     static
  77.       {
  78.         try
  79.           {
  80.             final XPath xpath = XPATH_FACTORY.newXPath();
  81.             XPATH_STILLIMAGE_EXPR = xpath.compile("/gallery/stillImage");
  82.           }
  83.         catch (XPathExpressionException e)
  84.           {
  85.             throw new ExceptionInInitializerError(e);
  86.           }
  87.       }

  88.     /*******************************************************************************************************************
  89.      *
  90.      * {@inheritDoc}
  91.      *
  92.      ******************************************************************************************************************/
  93.     @Override @Nonnull
  94.     public PathAwareFinder findPhotos (@Nonnull final MediaFolder parent)
  95.       {
  96.         throw new UnsupportedOperationException("must be implemented in subclasses");
  97.       }

  98.     /*******************************************************************************************************************
  99.      *
  100.      *
  101.      *
  102.      ******************************************************************************************************************/
  103.     @Scheduled(fixedDelay = 14_400_000) // 12 hours TODO: yes, can use properties here
  104.     private void clearCaches()
  105.       {
  106.         log.info("clearCaches()");
  107.         clearCachesImpl();
  108.       }

  109.     /*******************************************************************************************************************
  110.      *
  111.      * {@inheritDoc}
  112.      *
  113.      ******************************************************************************************************************/
  114.     protected void clearCachesImpl()
  115.       {
  116.         photoCollectionCache.clear();
  117.       }

  118.     /*******************************************************************************************************************
  119.      *
  120.      * Creates a collection of entities for the given gallery URL.
  121.      *
  122.      * @param   parent      the parent node
  123.      * @param   galleryUrl  the gallery URL
  124.      * @return              the collection of entities
  125.      *
  126.      ******************************************************************************************************************/
  127.     @Nonnull
  128.     @VisibleForTesting Collection<PathAwareEntity> findPhotos (@Nonnull final MediaFolder parent,
  129.                                                                @Nonnull final String galleryUrl)
  130.       {
  131.         log.debug("findPhotos({}, {}", parent, galleryUrl);

  132.         return photoCollectionCache.computeIfAbsent(galleryUrl, u ->
  133.           {
  134.             try
  135.               {
  136.                 final Document document = downloadXml(galleryUrl);
  137.                 final NodeList nodes = (NodeList)XPATH_STILLIMAGE_EXPR.evaluate(document, XPathConstants.NODESET);

  138.                 final Collection<PathAwareEntity> photoItems = new ArrayList<>();

  139.                 for (int i = 0; i < nodes.getLength(); i++)
  140.                   {
  141.                     final Node node = nodes.item(i);
  142.                     final String id = getAttribute(node, "id");
  143.                     final String title = getAttribute(node, "title");
  144.                     photoItems.add(new PhotoItem(parent, id, title));
  145.                   }

  146.                 return photoItems;
  147.               }
  148.             catch (SAXException | IOException | XPathExpressionException | ParserConfigurationException e)
  149.               {
  150.                 throw new RuntimeException(e);
  151.               }
  152.           });
  153.       }

  154.     /*******************************************************************************************************************
  155.      *
  156.      ******************************************************************************************************************/
  157.     // FIXME: implement a local cache on disk
  158.     @Nonnull
  159.     protected Document downloadXml (@Nonnull String url)
  160.       throws SAXException, ParserConfigurationException, IOException
  161.       {
  162.         log.info("downloadXml({})", url);

  163.         url = url.replaceAll("(^.*)\\/([0-9]{2})-([0-9]{2})\\/(.*)$", "$1/$2/$3/$4");

  164.         if (url.startsWith("file:") && url.endsWith("/")) // To support local test resources
  165.           {
  166.             url += "/index.xhtml";
  167.           }

  168.         return PARSER_FACTORY.newDocumentBuilder().parse(url);
  169.       }

  170.     /*******************************************************************************************************************
  171.      *
  172.      ******************************************************************************************************************/
  173.     @Nonnull
  174.     protected static String getAttribute (@Nonnull final Node node, @Nonnull final String attrName)
  175.       throws DOMException
  176.       {
  177.         return node.getAttributes().getNamedItem(attrName).getNodeValue();
  178.       }
  179.   }