LinkHandlerConfig.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.handler.link.spi;

import static com.day.cq.wcm.api.NameConstants.PN_REDIRECT_TARGET;

import java.util.Collections;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.osgi.annotation.versioning.ConsumerType;

import com.day.cq.wcm.api.Page;

import io.wcm.handler.link.markup.DummyLinkMarkupBuilder;
import io.wcm.handler.link.markup.SimpleLinkMarkupBuilder;
import io.wcm.handler.link.processor.DefaultInternalLinkInheritUrlParamLinkPostProcessor;
import io.wcm.handler.link.type.ExternalLinkType;
import io.wcm.handler.link.type.InternalCrossContextLinkType;
import io.wcm.handler.link.type.InternalLinkType;
import io.wcm.handler.link.type.MediaLinkType;
import io.wcm.handler.url.ui.SiteRoot;
import io.wcm.sling.commons.adapter.AdaptTo;
import io.wcm.sling.commons.caservice.ContextAwareService;
import io.wcm.sling.commons.resource.ResourceType;
import io.wcm.wcm.commons.util.Path;

/**
 * {@link LinkHandlerConfig} OSGi services provide application-specific configuration for link handling.
 * Applications can set service properties or bundle headers as defined in {@link ContextAwareService} to apply this
 * configuration only for resources that match the relevant resource paths.
 */
@ConsumerType
public abstract class LinkHandlerConfig implements ContextAwareService {

  private static final List<Class<? extends LinkType>> DEFAULT_LINK_TYPES = List.of(
      InternalLinkType.class,
      ExternalLinkType.class,
      MediaLinkType.class);

  private static final List<Class<? extends LinkMarkupBuilder>> DEFAULT_LINK_MARKUP_BUILDERS = List.of(
      SimpleLinkMarkupBuilder.class,
      DummyLinkMarkupBuilder.class);

  private static final List<Class<? extends LinkProcessor>> DEFAULT_POST_PROCESSORS = List.of(
      DefaultInternalLinkInheritUrlParamLinkPostProcessor.class);

  static final String REDIRECT_RESOURCE_TYPE = "wcm-io/handler/link/components/page/redirect";

  /**
   * Default content root path.
   */
  @SuppressWarnings("java:S1075") // no file path
  public static final String DEFAULT_ROOT_PATH_CONTENT = "/content";

  /**
   * Default media/asset root path.
   */
  @SuppressWarnings("java:S1075") // no file path
  public static final String DEFAULT_ROOT_PATH_MEDIA = "/content/dam";

  /**
   * @return Supported link types
   */
  public @NotNull List<Class<? extends LinkType>> getLinkTypes() {
    return DEFAULT_LINK_TYPES;
  }

  /**
   * @return Available link markup builders
   */
  public @NotNull List<Class<? extends LinkMarkupBuilder>> getMarkupBuilders() {
    return DEFAULT_LINK_MARKUP_BUILDERS;
  }

  /**
   * @return List of link metadata pre processors (optional). The processors are applied in list order.
   */
  public @NotNull List<Class<? extends LinkProcessor>> getPreProcessors() {
    // no processors
    return Collections.emptyList();
  }

  /**
   * @return List of link metadata post processors (optional). The processors are applied in list order.
   */
  public @NotNull List<Class<? extends LinkProcessor>> getPostProcessors() {
    return DEFAULT_POST_PROCESSORS;
  }

  /**
   * Detected if page is acceptable as link target.
   * This is used by {@link io.wcm.handler.link.type.InternalLinkType}, other {@link LinkType} implementations
   * may implement other logic.
   * @param page Page
   * @return true if Page is acceptable as link target.
   */
  public boolean isValidLinkTarget(@NotNull Page page) {
    // by default accept all pages
    return true;
  }

  /**
   * Detected if page contains redirect link information
   * @param page Page
   * @return true if Page is a redirect page
   */
  public boolean isRedirect(@NotNull Page page) {
    return ResourceType.is(page.getContentResource(), REDIRECT_RESOURCE_TYPE)
        || StringUtils.isNotBlank(page.getProperties().get(PN_REDIRECT_TARGET, String.class));
  }

  /**
   * Get root path for picking links using path field widgets.
   * @param page Context page
   * @param linkTypeId Link type ID
   * @return Root path or null
   */
  public @Nullable String getLinkRootPath(@NotNull Page page, @NotNull String linkTypeId) {
    if (StringUtils.equals(linkTypeId, InternalLinkType.ID)) {
      // inside an experience fragment it does not make sense to use a site root path
      if (Path.isExperienceFragmentPath(page.getPath()) || Path.isEditableTemplatePath(page.getPath())) {
        return DEFAULT_ROOT_PATH_CONTENT;
      }
      return AdaptTo.notNull(page.getContentResource(), SiteRoot.class).getRootPath(page);
    }
    else if (StringUtils.equals(linkTypeId, InternalCrossContextLinkType.ID)) {
      return DEFAULT_ROOT_PATH_CONTENT;
    }
    else if (StringUtils.equals(linkTypeId, MediaLinkType.ID)) {
      return DEFAULT_ROOT_PATH_MEDIA;
    }
    return null;
  }

}