PathAwareEntityFinderDelegate.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.model.impl;

  28. import javax.annotation.Nonnegative;
  29. import javax.annotation.Nonnull;
  30. import javax.annotation.Nullable;
  31. import java.util.Collection;
  32. import java.util.List;
  33. import java.util.Optional;
  34. import java.util.concurrent.CopyOnWriteArrayList;
  35. import java.util.function.Function;
  36. import java.nio.file.Path;
  37. import it.tidalwave.util.As;
  38. import it.tidalwave.util.Finder;
  39. import it.tidalwave.util.spi.FinderSupport;
  40. import it.tidalwave.util.SupplierBasedFinder;
  41. import it.tidalwave.role.SimpleComposite;
  42. import it.tidalwave.bluemarine2.model.MediaFolder;
  43. import it.tidalwave.bluemarine2.model.spi.PathAwareEntity;
  44. import it.tidalwave.bluemarine2.model.spi.PathAwareFinder;
  45. import lombok.RequiredArgsConstructor;
  46. import lombok.extern.slf4j.Slf4j;
  47. import static java.util.Collections.*;
  48. import static it.tidalwave.role.SimpleComposite._SimpleComposite_;
  49. import static lombok.AccessLevel.PRIVATE;

  50. /***********************************************************************************************************************
  51.  *
  52.  * A decorator of an {@link Finder} of {@link PathAwareEntity} that creates a virtual tree of entities. Each entity is
  53.  * given a path, which starts with the path of a {@link MediaFolder} and continues with the id of the entity.
  54.  *
  55.  * This {@code Finder} can filtered by path. If a filter path is provided, the filtering happens in memory: this means
  56.  * that even when the delegate queries a native store, all the data are first retrieved in memory.
  57.  *
  58.  * @stereotype  Finder
  59.  *
  60.  * @author  Fabrizio Giudici
  61.  *
  62.  **********************************************************************************************************************/
  63. @RequiredArgsConstructor(access = PRIVATE) @Slf4j
  64. public class PathAwareEntityFinderDelegate extends FinderSupport<PathAwareEntity, PathAwareFinder> implements PathAwareFinder
  65.   {
  66.     private static final long serialVersionUID = 4429676480224742813L;

  67.     @Nonnull
  68.     private final MediaFolder mediaFolder;

  69.     @Nonnull
  70.     private final Finder<PathAwareEntity> delegate;

  71.     @Nonnull
  72.     private final Optional<Path> optionalPath;

  73.     /*******************************************************************************************************************
  74.      *
  75.      * Creates an instance associated to a given {@link MediaFolder} and a delegate finder.
  76.      *
  77.      * @see #PathAwareEntityFinderDelegate(it.tidalwave.bluemarine2.model.MediaFolder, java.util.function.Function)
  78.      *
  79.      * @param   mediaFolder     the folder associated to this finder
  80.      * @param   delegate        the delegate finder to provide data
  81.      *
  82.      ******************************************************************************************************************/
  83.     public PathAwareEntityFinderDelegate (@Nonnull final MediaFolder mediaFolder,
  84.                                           @Nonnull final Finder<PathAwareEntity> delegate)
  85.       {
  86.         this(mediaFolder, delegate, Optional.empty());
  87.       }

  88.     /*******************************************************************************************************************
  89.      *
  90.      * Creates an instance associated to a given {@link MediaFolder} and a function for providing children. This
  91.      * constructor is typically used when the children are already present in memory (e.g. they are
  92.      * {@link VirtualMediaFolder}s. Because the function doesn't have the full semantics of a {@link Finder} - it can't
  93.      * optimise a query in function of search parameters, nor optimise the count of results - when a
  94.      * {@code PathAwareEntityFinderDelegate} is created in this way all operations will be performed in memory. If one
  95.      * can provide data from a native store and enjoy optimised queries, instead of this constructor use
  96.      * {@link #PathAwareEntityFinderDelegate(it.tidalwave.bluemarine2.model.MediaFolder, it.tidalwave.util.Finder)}
  97.      *
  98.      * @see #PathAwareEntityFinderDelegate(it.tidalwave.bluemarine2.model.MediaFolder, it.tidalwave.util.Finder)
  99.      *
  100.      * @param   mediaFolder     the folder associated to this finder
  101.      * @param   function        the function that provides children
  102.      *
  103.      ******************************************************************************************************************/
  104.     public PathAwareEntityFinderDelegate (@Nonnull final MediaFolder mediaFolder,
  105.                                           @Nonnull final Function<MediaFolder, Collection<? extends PathAwareEntity>> function)
  106.       {
  107.         this(mediaFolder, new SupplierBasedFinder<>(() -> function.apply(mediaFolder)), Optional.empty());
  108.       }

  109.     /*******************************************************************************************************************
  110.      *
  111.      * Clone constructor.
  112.      *
  113.      ******************************************************************************************************************/
  114.     public PathAwareEntityFinderDelegate (@Nonnull final PathAwareEntityFinderDelegate other,
  115.                                           @Nonnull final Object override)
  116.       {
  117.         super(other, override);
  118.         final PathAwareEntityFinderDelegate source = getSource(PathAwareEntityFinderDelegate.class, other, override);
  119.         this.mediaFolder = source.mediaFolder;
  120.         this.delegate = source.delegate;
  121.         this.optionalPath = source.optionalPath;
  122.       }

  123.     /*******************************************************************************************************************
  124.      *
  125.      * {@inheritDoc}
  126.      *
  127.      ******************************************************************************************************************/
  128.     @Override @Nonnull
  129.     public PathAwareFinder withPath (@Nonnull final Path path)
  130.       {
  131.         return clonedWith(new PathAwareEntityFinderDelegate(mediaFolder, delegate, Optional.of(path)));
  132.       }

  133.     /*******************************************************************************************************************
  134.      *
  135.      * {@inheritDoc}
  136.      *
  137.      ******************************************************************************************************************/
  138.     @Override @Nonnull
  139.     protected List<? extends PathAwareEntity> computeResults()
  140.       {
  141.         return new CopyOnWriteArrayList<>(optionalPath.flatMap(path -> filteredByPath(path).map(e -> singletonList(e)))
  142.                                                       .orElse((List)delegate.results()));
  143.       }

  144.     /*******************************************************************************************************************
  145.      *
  146.      * {@inheritDoc}
  147.      *
  148.      ******************************************************************************************************************/
  149.     @Override @Nonnegative
  150.     public int count()
  151.       {
  152.         optionalPath.ifPresent(path -> log.warn("Path present: {} - count won't be a native query", path));
  153.         return optionalPath.map(path -> filteredByPath(path).map(entity -> 1).orElse(0))
  154.                            .orElse(delegate.count());
  155.       }

  156.     /*******************************************************************************************************************
  157.      *
  158.      *
  159.      *
  160.      ******************************************************************************************************************/
  161.     @Nonnull
  162.     private Optional<? extends PathAwareEntity> filteredByPath (@Nonnull final Path path)
  163.       {
  164.         log.debug("filteredByPath({})", path);
  165.         return mediaFolder.getPath().equals(path)
  166.                                         ? Optional.of(mediaFolder)
  167.                                         : childMatchingPathHead(path).flatMap(entity -> path.equals(entity.getPath())
  168.                                                 ? Optional.of(entity)
  169.                                                 : childMatchingPath(entity, path));
  170.       }

  171.     /*******************************************************************************************************************
  172.      *
  173.      * Returns the child entity that matches the first element of the path, if present. The path can be exactly the one
  174.      * of the found entity, or it can be of one of its children.
  175.      *
  176.      * This method performs a bulk query of all children and then filters by path in memory. It is not possible to
  177.      * use a query to the native store for the path - which would be good for performance reasons - , because even
  178.      * though each segment of the path is function of some attribute of the related {@code PathAwareEntity} - typically
  179.      * the id - it is not a matter of the native store. Performance of this section relies upon memory caching. Some
  180.      * experiment showed that it's not useful to add another caching layer here, and the one in
  181.      * {@code RepositoryFinderSupport} is enough.
  182.      *
  183.      * @param   path    the path
  184.      * @return          the entity, if present
  185.      *
  186.      ******************************************************************************************************************/
  187.     @Nonnull
  188.     private Optional<PathAwareEntity> childMatchingPathHead (@Nonnull final Path path)
  189.       {
  190. //                assert filtered.size() == 1 or 0;
  191.         log.debug(">>>> bulk query to {}, filtering in memory", delegate);
  192.         return (Optional<PathAwareEntity>)delegate.results().stream()
  193.                                                   .filter(entity -> sameHead(relative(path), relative(entity.getPath())))
  194.                                                   .findFirst();
  195.       }

  196.     /*******************************************************************************************************************
  197.      *
  198.      * @param   entity
  199.      * @param   path    the path
  200.      * @return          the entity, if present
  201.      *
  202.      ******************************************************************************************************************/
  203.     @Nonnull
  204.     private static Optional<PathAwareEntity> childMatchingPath (@Nonnull final PathAwareEntity entity,
  205.                                                                 @Nonnull final Path path)
  206.       {
  207.         return ((PathAwareFinder)asSimpleComposite(entity).findChildren()).withPath(path).optionalResult();
  208.       }

  209.     /*******************************************************************************************************************
  210.      *
  211.      *
  212.      *
  213.      ******************************************************************************************************************/
  214.     @Nonnull // FIXME: this should be normally done by as()
  215.     private static SimpleComposite asSimpleComposite (@Nonnull final As object)
  216.       {
  217.         return (object instanceof SimpleComposite) ? (SimpleComposite)object : object.as(_SimpleComposite_);
  218.       }

  219.     /*******************************************************************************************************************
  220.      *
  221.      * Relativizes a path against the finder path, that is it removes the parent path. If the path can't be
  222.      * relativized, that is it doesn't start with the finder path, returns null.
  223.      *
  224.      ******************************************************************************************************************/
  225.     @Nullable
  226.     private Path relative (@Nonnull final Path path)
  227.       {
  228.         return mediaFolder.getParent().isEmpty() ? path :
  229.                 path.startsWith(mediaFolder.getPath()) ? path.subpath(mediaFolder.getPath().getNameCount(), path.getNameCount())
  230.                                                        : null;
  231.       }

  232.     /*******************************************************************************************************************
  233.      *
  234.      *
  235.      *
  236.      ******************************************************************************************************************/
  237.     private static boolean sameHead (@Nullable final Path path1, @Nullable final Path path2)
  238.       {
  239.         return (path1 != null) && (path2 != null) && path1.getName(0).equals(path2.getName(0));
  240.       }
  241.   }