GraniteUi.java

/*
 * #%L
 * wcm.io
 * %%
 * Copyright (C) 2014 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.wcm.ui.granite.util;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ProviderType;

import com.adobe.granite.ui.components.Value;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;

/**
 * Helper methods for GraniteUI components.
 * <p>
 * It uses some heuristics to get the current content path in context of a GraniteUI component:
 * </p>
 * <ol>
 * <li>Inside create page wizard, try to get content path from referrer header</li>
 * <li>Try to get content path from request suffix</li>
 * <li>Try to get content path from "item" request parameter (inside multifield component)</li>
 * </ol>
 */
@ProviderType
public final class GraniteUi {

  static final String CREATEPAGEWITZARD_PROPERTIES_URI = "/mnt/overlay/wcm/core/content/sites/createpagewizard/properties.html";
  static final String CREATEPAGEWITZARD_URI = "/mnt/overlay/wcm/core/content/sites/createpagewizard.html";
  static final String HEADER_REFERER = "Referer";

  private GraniteUi() {
    // static methods only
  }

  /**
   * Current content resource
   * @param request Request
   * @return Current content resource or null
   */
  public static @Nullable Resource getContentResource(@NotNull HttpServletRequest request) {
    String contentPath = getContentPath(request);
    if (contentPath != null) {
      SlingHttpServletRequest slingRequest = (SlingHttpServletRequest)request;
      return slingRequest.getResourceResolver().getResource(contentPath);
    }
    return null;
  }

  /**
   * Get current content resource
   * If it does not exist, go up the content path and return the first resource that exists.
   * @param request Request
   * @return Current content resource or the first existing parent/ancestor.
   */
  public static @Nullable Resource getContentResourceOrParent(@NotNull HttpServletRequest request) {
    String contentPath = getContentPath(request);
    return getContentResourceOrParentFromPath((SlingHttpServletRequest)request, contentPath);
  }

  /**
   * Current content page. If the current resource does not exist the content page
   * of the next-existing parent resource is returned.
   * @param request Request
   * @return Current content page or null
   */
  public static @Nullable Page getContentPage(@NotNull HttpServletRequest request) {
    SlingHttpServletRequest slingRequest = (SlingHttpServletRequest)request;
    Resource contentResource = getContentResourceOrParent(request);
    if (contentResource != null) {
      PageManager pageManager = slingRequest.getResourceResolver().adaptTo(PageManager.class);
      if (pageManager != null) {
        return pageManager.getContainingPage(contentResource);
      }
    }
    return null;
  }

  /**
   * From the list of resource types get the first one that exists.
   * @param resourceResolver Resource resolver
   * @param resourceTypes ResourceTypes
   * @return Existing resource type
   */
  public static @Nullable String getExistingResourceType(@NotNull ResourceResolver resourceResolver, @NotNull String @NotNull... resourceTypes) {
    for (String path : resourceTypes) {
      if (resourceResolver.getResource(path) != null) {
        return path;
      }
    }
    return null;
  }

  /**
   * Current content path
   * @param request Request
   * @return Current content path or null
   */
  @SuppressWarnings("deprecation")
  private static @Nullable String getContentPath(@NotNull HttpServletRequest request) {

    String contentPath = (String)request.getAttribute(Value.CONTENTPATH_ATTRIBUTE);

    // if we are currently in create page wizard try to extract content path from referer,
    // as it is not available via other ways
    if (!isValidContentPath(contentPath) && StringUtils.contains(request.getRequestURI(), CREATEPAGEWITZARD_PROPERTIES_URI)) {
      String referer = request.getHeader(HEADER_REFERER);
      if (referer != null && StringUtils.contains(referer, CREATEPAGEWITZARD_URI)) {
        contentPath = StringUtils.substringAfter(referer, CREATEPAGEWITZARD_URI);
      }
    }

    if (!isValidContentPath(contentPath)) {
      // fallback to suffix if CONTENTPATH_ATTRIBUTE is not set
      // (e.g. in inside a /libs/granite/ui/components/foundation/form/multifield component)
      contentPath = ((SlingHttpServletRequest)request).getRequestPathInfo().getSuffix();
    }

    if (!isValidContentPath(contentPath)) {
      // fallback to suffix item parameter in query string
      // (e.g. in inside a /libs/granite/ui/components/foundation/form/multifield component)
      contentPath = request.getParameter("item");
    }

    if (!isValidContentPath(contentPath)) {
      contentPath = null;
    }

    return contentPath;
  }

  private static boolean isValidContentPath(@Nullable String path) {
    if (path == null) {
      return false;
    }
    return StringUtils.startsWith(path, "/");
  }

  private static Resource getContentResourceOrParentFromPath(SlingHttpServletRequest slingRequest, String contentPath) {
    if (StringUtils.isNotEmpty(contentPath)) {
      Resource contentResource = slingRequest.getResourceResolver().getResource(contentPath);
      if (contentResource != null) {
        return contentResource;
      }
      else {
        return getContentResourceOrParentFromPath(slingRequest, ResourceUtil.getParent(contentPath));
      }
    }
    return null;
  }

}