LinkBuilderImpl.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.impl;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.wcm.handler.commons.dom.Anchor;
import io.wcm.handler.link.Link;
import io.wcm.handler.link.LinkArgs;
import io.wcm.handler.link.LinkBuilder;
import io.wcm.handler.link.LinkComponentPropertyResolver;
import io.wcm.handler.link.LinkNameConstants;
import io.wcm.handler.link.LinkRequest;
import io.wcm.handler.url.UrlMode;
import io.wcm.handler.url.VanityMode;
import io.wcm.wcm.commons.component.ComponentPropertyResolverFactory;

/**
 * Default implementation or {@link LinkBuilder}.
 */
final class LinkBuilderImpl implements LinkBuilder {

  private final LinkHandlerImpl linkHandler;

  private final Resource resource;
  private final Page page;
  private final String reference;
  private LinkArgs linkArgs = new LinkArgs();

  private static final Logger log = LoggerFactory.getLogger(LinkBuilderImpl.class);

  LinkBuilderImpl(@Nullable Resource resource, @NotNull LinkHandlerImpl linkHandler,
      @NotNull ComponentPropertyResolverFactory componentPropertyResolverFactory) {
    this.resource = resource;
    this.page = null;
    this.reference = null;
    this.linkHandler = linkHandler;
    resolveWindowTargetAndFallbackProperties(componentPropertyResolverFactory);
  }

  LinkBuilderImpl(@NotNull LinkRequest linkRequest, @NotNull LinkHandlerImpl linkHandler,
      @NotNull ComponentPropertyResolverFactory componentPropertyResolverFactory) {
    this.resource = linkRequest.getResource();
    this.page = linkRequest.getPage();
    this.reference = linkRequest.getReference();
    this.linkHandler = linkHandler;
    // clone link args to make sure the original object is not modified
    this.linkArgs = linkRequest.getLinkArgs().clone();
    resolveWindowTargetAndFallbackProperties(componentPropertyResolverFactory);
  }

  LinkBuilderImpl(@Nullable Page page, @NotNull LinkHandlerImpl linkHandler) {
    this.resource = null;
    this.page = page;
    this.reference = null;
    this.linkHandler = linkHandler;
  }

  LinkBuilderImpl(@Nullable String reference, @NotNull LinkHandlerImpl linkHandler) {
    this.resource = null;
    this.page = null;
    this.reference = reference;
    this.linkHandler = linkHandler;
  }

  private void resolveWindowTargetAndFallbackProperties(@NotNull ComponentPropertyResolverFactory componentPropertyResolverFactory) {
    if (resource == null) {
      return;
    }

    // resolve default settings from content policies and component properties
    try (LinkComponentPropertyResolver resolver = new LinkComponentPropertyResolver(resource, componentPropertyResolverFactory)) {
      linkArgs.linkTargetUrlFallbackProperty(resolver.getLinkTargetUrlFallbackProperty());
      linkArgs.linkTargetWindowTargetFallbackProperty(resolver.getLinkTargetWindowTargetFallbackProperty());
    }
    catch (Exception ex) {
      log.warn("Error closing component property resolver.", ex);
    }

    // get window target from resource
    linkArgs.windowTarget(getWindowTargetFromResource(resource, linkArgs));
  }

  private static String getWindowTargetFromResource(@NotNull Resource resource, @NotNull LinkArgs linkArgs) {
    ValueMap props = resource.getValueMap();
    String windowTarget = null;

    // check if a link target URL is set in the fallback property
    String[] linkTargetWindowTargetFallbackProperty = linkArgs.getLinkTargetWindowTargetFallbackProperty();
    if (linkTargetWindowTargetFallbackProperty != null) {
      for (String propertyName : linkTargetWindowTargetFallbackProperty) {
        windowTarget = props.get(propertyName, String.class);
        if (StringUtils.isNotBlank(windowTarget)) {
          break;
        }
      }
    }

    // read from resource with default property name
    if (StringUtils.isBlank(windowTarget)) {
      windowTarget = props.get(LinkNameConstants.PN_LINK_WINDOW_TARGET, String.class);
    }
    return windowTarget;
  }

  @Override
  @SuppressWarnings({ "null", "unused" })
  public @NotNull LinkBuilder args(@NotNull LinkArgs value) {
    if (value == null) {
      throw new IllegalArgumentException("LinkArgs is null.");
    }
    // clone link args to make sure the original object is not modified
    this.linkArgs = value.clone();
    return this;
  }

  @Override
  public @NotNull LinkBuilder urlMode(@Nullable UrlMode value) {
    this.linkArgs.urlMode(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder vanityMode(@Nullable VanityMode value) {
    this.linkArgs.vanityMode(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder dummyLink(boolean value) {
    this.linkArgs.dummyLink(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder dummyLinkUrl(@Nullable String value) {
    this.linkArgs.dummyLinkUrl(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder selectors(@Nullable String value) {
    this.linkArgs.selectors(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder extension(@Nullable String value) {
    this.linkArgs.extension(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder suffix(@Nullable String value) {
    this.linkArgs.suffix(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder queryString(@Nullable String value) {
    this.linkArgs.queryString(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder fragment(@Nullable String value) {
    this.linkArgs.fragment(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder windowTarget(@Nullable String value) {
    this.linkArgs.windowTarget(value);
    return this;
  }

  @Override
  public @NotNull LinkBuilder disableSuffixSelector(boolean disableSuffixSelector) {
    this.linkArgs.disableSuffixSelector(disableSuffixSelector);
    return this;
  }

  @Override
  @SuppressFBWarnings("NP_METHOD_PARAMETER_TIGHTENS_ANNOTATION")
  public @NotNull LinkBuilder linkTargetUrlFallbackProperty(@NotNull String @Nullable... propertyNames) {
    this.linkArgs.linkTargetUrlFallbackProperty(propertyNames);
    return this;
  }

  @Override
  public @NotNull LinkBuilder property(@NotNull String key, @Nullable Object value) {
    this.linkArgs.property(key, value);
    return this;
  }

  @Override
  public @NotNull Link build() {
    LinkRequest request = new LinkRequest(this.resource, this.page, this.reference, this.linkArgs);
    return linkHandler.processRequest(request);
  }

  @Override
  public String buildMarkup() {
    return build().getMarkup();
  }

  @Override
  public Anchor buildAnchor() {
    return build().getAnchor();
  }

  @Override
  public String buildUrl() {
    return build().getUrl();
  }

}