All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.wcm.testing.mock.aem.MockLanguageManager Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2020 wcm.io
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package io.wcm.testing.mock.aem;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ProviderType;
import org.osgi.service.component.annotations.Component;

import com.day.cq.commons.Language;
import com.day.cq.commons.LanguageUtil;
import com.day.cq.commons.inherit.HierarchyNodeInheritanceValueMap;
import com.day.cq.commons.jcr.JcrConstants;
import com.day.cq.wcm.api.LanguageManager;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;
import com.day.text.Text;

/**
 * Mock implementation of {@link LanguageManager}.
 */
@Component(service = LanguageManager.class)
@ProviderType
public final class MockLanguageManager implements LanguageManager {

  /**
   * @deprecated Deprecated
   */
  @Override
  @Deprecated(forRemoval = true)
  public Map getAdjacentInfo(final ResourceResolver resourceResolver, final String path) {
    return Optional.ofNullable(getAdjacentLanguageInfo(resourceResolver, path))
        .map(Map::entrySet)
        .map(Collection::stream)
        .map(entries -> entries.collect(toLinkedMap(i -> i.getKey().getLocale(), Map.Entry::getValue)))
        .orElse(null);
  }

  @Override
  @SuppressWarnings("null")
  public Map getAdjacentLanguageInfo(final ResourceResolver resourceResolver, final String path) {
    return Optional.ofNullable(LanguageUtil.getLanguageRoot(path))
        .map(root -> path.substring(root.length()))
        .map(relPath -> relPath.startsWith("/") ? relPath.substring(1) : relPath)
        .map(relPath -> this.getLanguageRootStream(resourceResolver, path)
            .map(info -> info.getChild(relPath, resourceResolver))
            .collect(toLinkedMap(InfoImpl::getLanguage, i -> (Info)i)))
        .orElse(null);
  }

  @Override
  public Locale getLanguage(final Resource resource) {
    return this.getLanguage(resource, true);
  }

  @Override
  public Language getCqLanguage(final Resource resource) {
    return this.getCqLanguage(resource, true);
  }

  @Override
  public Locale getLanguage(final Resource resource, final boolean respectContent) {
    return Optional.ofNullable(getCqLanguage(resource, respectContent))
        .map(Language::getLocale)
        .orElse(null);
  }

  @Override
  @SuppressWarnings("null")
  public Language getCqLanguage(final Resource resource, final boolean respectContent) {
    Optional page = Optional.ofNullable(resource.getResourceResolver().adaptTo(PageManager.class))
        .map(pm -> pm.getContainingPage(resource));

    if (respectContent) {
      return page
          .map(Page::getContentResource)
          .map(HierarchyNodeInheritanceValueMap::new)
          .map(vm -> vm.getInherited(JcrConstants.JCR_LANGUAGE, String.class))
          .map(LanguageUtil::getLanguage)
          .orElseGet(() -> this.getCqLanguage(resource, false));
    }

    return page
        .map(Page::getPath)
        .map(LanguageUtil::getLanguageRoot)
        .map(Text::getName)
        .map(Language::new)
        .orElse(null);
  }

  @Override
  public Collection getLanguages(final ResourceResolver resourceResolver, final String path) {
    return this.getCqLanguages(resourceResolver, path).stream()
        .map(Language::getLocale)
        .collect(Collectors.toList());
  }

  @Override
  public Collection getCqLanguages(final ResourceResolver resourceResolver, final String path) {
    return this.getLanguageRootStream(resourceResolver, path)
        .map(InfoImpl::getLanguage)
        .collect(Collectors.toList());
  }

  @Override
  @SuppressWarnings("null")
  public Collection getLanguageRoots(final ResourceResolver resourceResolver, final String path) {
    return this.getLanguageRootStream(resourceResolver, path)
        .map(InfoImpl::getResource)
        .filter(Objects::nonNull)
        .map(res -> res.adaptTo(Page.class))
        .filter(Objects::nonNull)
        .collect(Collectors.toList());
  }

  @Override
  @SuppressWarnings({ "null"})
  public Page getLanguageRoot(final Resource resource) {
    return getLanguageRoot(resource, false);
  }

  @Override
  public @Nullable Page getLanguageRoot(Resource res, boolean respectContent) {
    Resource languageRootResource = getLanguageRootResource(res, respectContent);
    return (languageRootResource != null) ?
            languageRootResource.adaptTo(Page.class) :
            null;
  }

  @Override
  public Resource getLanguageRootResource(Resource res) {
    return getLanguageRootResource(res, false);
  }

  @Override
  public Resource getLanguageRootResource(Resource res, boolean respectContent) {
    String rootPath = getLanguageRootPath(res, respectContent);
    if (rootPath == null) {
      return null;
    }
    return res.getResourceResolver().getResource(rootPath);
  }

  @Override
  public Collection getLanguageRootResources(ResourceResolver resolver, String path) {
    Iterator resources = getLanguageRootSiblings(resolver, path, false);
    if (resources == null) {
      return Collections.emptySet();
    }
    List roots = new ArrayList<>();
    while (resources.hasNext()) {
      Resource res = resources.next();
      Locale locale = LanguageUtil.getLocale(Text.getName(res.getPath()));
      if (locale != null) {
        roots.add(res);
      }
    }
    return roots;
  }

  @Override
  public Collection getLanguageRootResources(ResourceResolver resolver, String path, boolean respectContent) {
    Iterator siblings = getLanguageRootSiblings(resolver, path, respectContent);
    if (siblings == null) {
      return Collections.emptySet();
    }
    List roots = new ArrayList<>();
    boolean additionalLanguageRootsFound = false;
    while (siblings.hasNext()) {
      Resource sibling = siblings.next();
      String locale = getLanguageRootLocale(sibling, respectContent);
      if (locale != null) {
        roots.add(sibling);
        continue;
      }
      additionalLanguageRootsFound |= addLanguageRootsFromChildren(roots, sibling, respectContent);
    }
    if (additionalLanguageRootsFound) {
      return roots;
    }
    Resource currentResource = resolver.getResource(path);
    if (currentResource != null) {
      Resource langRoot = getLanguageRootResource(currentResource, true);
      if (langRoot != null) {
        Resource langRootParent = langRoot.getParent();
        if (langRootParent != null) {
          Resource langRootGrandParent = langRootParent.getParent();
          ArrayList nonLangRootUncles = new ArrayList<>();
          if (langRootGrandParent != null) {
            Iterator langRootUncles = langRootGrandParent.listChildren();
            while (langRootUncles.hasNext()) {
              Resource langRootUncle = langRootUncles.next();
              if (langRootUncle.getName().equals(langRootParent.getName())) {
                continue;
              }
              String gcLocale = getLanguageRootLocale(langRootUncle, respectContent);
              if (gcLocale != null && !isCountryNode(langRootUncle, respectContent)) {
                roots.add(langRootUncle);
                additionalLanguageRootsFound = true;
                continue;
              }
              nonLangRootUncles.add(langRootUncle);
            }
          }
          if (!additionalLanguageRootsFound) {
            return roots;
          }
          for (Resource nonLangRootUncle : nonLangRootUncles) {
            addLanguageRootsFromChildren(roots, nonLangRootUncle, respectContent);
          }
        }
      }
    }
    return roots;
  }

  private String getLanguageRootPath(Resource res, boolean respectContent) {
    String path = res.getPath();
    if (respectContent) {
      int idx = path.indexOf("/jcr:content");
      if (idx > 0) {
        path = path.substring(0, idx);
      }
      Resource hr = res.getResourceResolver().getResource(path);
      while (hr != null && !StringUtils.equals(hr.getPath(), "/")) {
        ValueMap props = hr.getValueMap();
        if (props.get("jcr:content/cq:isLanguageRoot", Boolean.FALSE)) {
          String iso = props.get("jcr:content/jcr:language", "");
          Language locale = iso.isEmpty() ? null : LanguageUtil.getLanguage(iso);
          if (locale != null) {
            return hr.getPath();
          }
        }
        hr = hr.getParent();
      }
    }
    return LanguageUtil.getLanguageRoot(path);
  }

  @Nullable
  private Iterator getLanguageRootSiblings(ResourceResolver resolver, String path, boolean respectContent) {
    if (path == null) {
      return null;
    }
    Resource res = resolver.getResource(path);
    if (res == null) {
      return null;
    }
    String root = getLanguageRootPath(res, respectContent);
    if (root == null) {
      return null;
    }
    String parent = Text.getRelativeParent(root, 1);
    Resource parentResource = resolver.getResource(parent);
    if (parentResource == null) {
      return null;
    }
    return resolver.listChildren(parentResource);
  }

  private boolean isCountryNode(Resource resource, boolean respectContent) {
    Iterator children = resource.listChildren();
    while (children.hasNext()) {
      Resource child = children.next();
      String gcLocale = getLanguageRootLocale(child, respectContent);
      if (gcLocale != null) {
        return true;
      }
    }
    return false;
  }

  private boolean addLanguageRootsFromChildren(List roots, Resource resource, boolean respectContent) {
    Iterator children = resource.listChildren();
    boolean additionalLanguageRootsFound = false;
    while (children.hasNext()) {
      Resource child = children.next();
      String childLocale = getLanguageRootLocale(child, respectContent);
      if (childLocale != null) {
        roots.add(child);
        additionalLanguageRootsFound = true;
      }
    }
    return additionalLanguageRootsFound;
  }

  private String getLanguageRootLocale(Resource res, boolean respectContent) {
    if (null == res) {
      return null;
    }
    if (respectContent && !StringUtils.equals(res.getPath(), "/")) {
      ValueMap props = res.getValueMap();
      if (props.get("jcr:content/cq:isLanguageRoot", Boolean.FALSE)) {
        String iso = props.get("jcr:content/jcr:language", "");
        Language language1 = iso.isEmpty() ? null : LanguageUtil.getLanguage(iso);
        if (language1 != null) {
          return language1.getLocale().toString();
        }
      }
    }
    Language language = LanguageUtil.getLanguage(res.getName());
    if (language != null) {
      return res.getName();
    }
    return null;
  }

  @SuppressWarnings("null")
  private Stream getLanguageRootStream(final ResourceResolver resourceResolver, final String path) {
    return Optional.ofNullable(LanguageUtil.getLanguageRoot(path))
        .map(resourceResolver::getResource)
        .map(Resource::getParent)
        .map(Resource::listChildren)
        .map(childIterator -> StreamSupport.stream(((Iterable)() -> childIterator).spliterator(), false))
        .orElseGet(Stream::empty)
        .filter(res -> Objects.nonNull(LanguageUtil.getLanguage(res.getName())))
        .map(res -> new InfoImpl(res.getPath(), res, LanguageUtil.getLanguage(res.getName())));
  }

  /**
   * Collector for collecting a stream to a linked hash map.
   * @param keyMapper A mapping function to produce keys.
   * @param valueMapper A mapping function to produce values.
   * @param  The type of input elements to the reduction operation.
   * @param  The output type of the key mapping function.
   * @param  The output type of the value mapping function.
   * @return A Collector which collects elements into a LinkedHashMap whose keys are the result of applying a key
   *         mapping function to the input elements, and whose values are the result of applying a value mapping
   *         function to
   *         all input elements equal to the key and combining them using the merge function
   */
  private static  Collector> toLinkedMap(
      final Function keyMapper, final Function valueMapper) {
    return Collectors.toMap(
        keyMapper,
        valueMapper,
        (u, v) -> {
          throw new IllegalStateException(String.format("Duplicate key %s", u));
        },
        LinkedHashMap::new);
  }

  // --- unsupported operations ---

  @Override
  public String getIsoCountry(final Locale locale) {
    throw new UnsupportedOperationException();
  }

  @Override
  public Tree compareLanguageTrees(final ResourceResolver resourceResolver, final String path) {
    throw new UnsupportedOperationException();
  }


  private static final class InfoImpl implements LanguageManager.Info {

    private final String path;
    private final Resource resource;
    private final Language language;

    InfoImpl(@NotNull final String path, @Nullable final Resource resource, @NotNull final Language language) {
      this.path = path;
      this.resource = resource;
      this.language = language;
    }

    @Override
    public String getPath() {
      return this.path;
    }

    @Override
    public boolean exists() {
      return this.resource != null;
    }

    @Override
    public boolean hasContent() {
      return Optional.ofNullable(this.resource)
          .map(res -> resource.getChild(JcrConstants.JCR_CONTENT))
          .isPresent();
    }

    @Override
    @SuppressWarnings("null")
    public long getLastModified() {
      return Optional.ofNullable(this.resource)
          .map(res -> resource.getChild(JcrConstants.JCR_CONTENT))
          .map(Resource::getValueMap)
          .map(vm -> vm.get(JcrConstants.JCR_LASTMODIFIED, Long.class))
          .orElse(0L);
    }

    /**
     * The resource located at {@link #getPath()}, if it exists.
     * @return The resource.
     */
    @Nullable
    private Resource getResource() {
      return this.resource;
    }

    /**
     * Get the language.
     * @return The language.
     */
    @NotNull
    private Language getLanguage() {
      return this.language;
    }

    /**
     * Gets the InfoImpl for a child resource under the current InfoImpl's path.
     * 

* This constructs a new InfoImpl using the path getPath() + / + relPath. *

* @param relPath Path relative to the current path. * @param resourceResolver A resource resolver. * @return A new InfoImpl for the resource specified at relPath. */ private InfoImpl getChild(@NotNull final String relPath, @NotNull final ResourceResolver resourceResolver) { if (relPath.isEmpty()) { return this; } String childPath = String.join("/", this.path, relPath); Resource child = resourceResolver.getResource(childPath); return new InfoImpl(childPath, child, this.getLanguage()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy