DamAsset.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.mediasource.dam.impl;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.adapter.Adaptable;
import org.apache.sling.api.adapter.SlingAdaptable;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.day.cq.dam.api.DamConstants;

import io.wcm.handler.media.Asset;
import io.wcm.handler.media.CropDimension;
import io.wcm.handler.media.Dimension;
import io.wcm.handler.media.Media;
import io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.MediaFileType;
import io.wcm.handler.media.Rendition;
import io.wcm.handler.media.UriTemplate;
import io.wcm.handler.media.UriTemplateType;
import io.wcm.handler.media.spi.MediaHandlerConfig;
import io.wcm.handler.mediasource.dam.AssetRendition;
import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportService;
import io.wcm.handler.mediasource.dam.impl.weboptimized.WebOptimizedImageDeliveryService;
import io.wcm.wcm.commons.util.AemObjectReflectionToStringBuilder;

/**
 * {@link Asset} implementation for DAM assets.
 */
public final class DamAsset extends SlingAdaptable implements Asset {

  private final com.day.cq.dam.api.Asset asset;
  private final CropDimension cropDimension;
  private final Integer rotation;
  private final MediaArgs defaultMediaArgs;
  private final DamContext damContext;

  /**
   * @param media Media metadata
   * @param asset DAM asset
   * @param mediaHandlerConfig Media handler config
   * @param dynamicMediaSupportService Dynamic media support service
   * @param webOptimizedImageDeliveryService Web optimized image delivery service
   * @param adaptable Adaptable from current context
   */
  public DamAsset(Media media, com.day.cq.dam.api.Asset asset, MediaHandlerConfig mediaHandlerConfig,
      DynamicMediaSupportService dynamicMediaSupportService,
      WebOptimizedImageDeliveryService webOptimizedImageDeliveryService,
      Adaptable adaptable) {
    this.asset = asset;
    this.cropDimension = rescaleCropDimension(asset, media.getCropDimension());
    this.rotation = media.getRotation();
    this.defaultMediaArgs = media.getMediaRequest().getMediaArgs();
    this.damContext = new DamContext(asset, defaultMediaArgs, mediaHandlerConfig,
        dynamicMediaSupportService, webOptimizedImageDeliveryService, adaptable);
  }

  /**
   * Crop dimension stored in repository is always calucated against the web-enabled rendition of an asset.
   * Rescale the crop-dimension here once to calculate it against the original image, which will be used for the actual
   * cropping.
   * @param asset Asset
   * @param cropDimension Crop dimension from repository/input parameters
   * @return Rescaled crop dimension
   */
  private static @Nullable CropDimension rescaleCropDimension(@NotNull com.day.cq.dam.api.Asset asset, @Nullable CropDimension cropDimension) {
    if (cropDimension == null) {
      return null;
    }
    return WebEnabledRenditionCropping.getCropDimensionForOriginal(asset, cropDimension);
  }

  @Override
  public String getTitle() {
    String title = getPropertyAwareOfArray(DamConstants.DC_TITLE);
    // fallback to asset name if title is empty
    return StringUtils.defaultString(title, damContext.getAsset().getName());
  }

  /**
   * Get string value from properties. If value is an array, get first item of array.
   * It might happen that the adobe xmp lib creates an array, e.g. if the asset file already has a title attribute.
   * @param propertyName Property name
   * @return Single value
   */
  private @Nullable String getPropertyAwareOfArray(@NotNull String propertyName) {
    Object valueObject = asset.getMetadataValueFromJcr(propertyName);
    String value = null;
    if (valueObject != null) {
      if (valueObject instanceof Object[]) {
        Object[] valueArray = (Object[])valueObject;
        if (valueArray.length > 0) {
          value = valueArray[0].toString();
        }
      }
      else {
        value = valueObject.toString();
      }
    }
    return StringUtils.defaultIfBlank(value, null);
  }

  @Override
  public String getAltText() {
    if (defaultMediaArgs.isDecorative()) {
      return "";
    }
    if (!defaultMediaArgs.isForceAltValueFromAsset() && StringUtils.isNotEmpty(defaultMediaArgs.getAltText())) {
      return defaultMediaArgs.getAltText();
    }
    return StringUtils.defaultString(getDescription(), getTitle());
  }

  @Override
  public String getDescription() {
    return getPropertyAwareOfArray(DamConstants.DC_DESCRIPTION);
  }

  @Override
  public @NotNull String getPath() {
    return this.damContext.getAsset().getPath();
  }

  @Override
  public @NotNull ValueMap getProperties() {
    return new ValueMapDecorator(asset.getMetadata());
  }

  @Override
  public Rendition getDefaultRendition() {
    return getRendition(this.defaultMediaArgs);
  }

  @Override
  public Rendition getRendition(@NotNull MediaArgs mediaArgs) {
    Rendition rendition = getDamRendition(mediaArgs);

    // check if rendition is valid - otherwise return null
    if (StringUtils.isEmpty(rendition.getUrl())) {
      rendition = null;
    }

    return rendition;
  }

  @Override
  public Rendition getImageRendition(@NotNull MediaArgs mediaArgs) {
    Rendition rendition = getRendition(mediaArgs);
    if (rendition != null && rendition.isImage()) {
      return rendition;
    }
    else {
      return null;
    }
  }

  @Override
  public Rendition getDownloadRendition(@NotNull MediaArgs mediaArgs) {
    Rendition rendition = getRendition(mediaArgs);
    if (rendition != null && rendition.isDownload()) {
      return rendition;
    }
    else {
      return null;
    }
  }

  /**
   * Get DAM rendition instance.
   * @param mediaArgs Media args
   * @return DAM rendition instance (may be invalid rendition)
   */
  protected Rendition getDamRendition(MediaArgs mediaArgs) {
    return new DamRendition(this.cropDimension, this.rotation, mediaArgs, damContext);
  }

  @Override
  @SuppressWarnings({ "unchecked", "null" })
  public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
    if (type == com.day.cq.dam.api.Asset.class) {
      return (AdapterType)this.damContext.getAsset();
    }
    if (type == Resource.class) {
      return (AdapterType)this.damContext.getAsset().adaptTo(Resource.class);
    }
    return super.adaptTo(type);
  }

  @Override
  public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) {
    String extension = FilenameUtils.getExtension(damContext.getAsset().getName());
    if (!MediaFileType.isImage(extension) || MediaFileType.isVectorImage(extension)) {
      throw new UnsupportedOperationException("Unable to build URI template for this asset type: " + getPath());
    }
    com.day.cq.dam.api.Rendition original = damContext.getAsset().getOriginal();
    Dimension dimension = AssetRendition.getDimension(original);
    if (dimension == null) {
      throw new IllegalArgumentException("Unable to get dimension for original rendition of asset: " + getPath());
    }
    return new DamUriTemplate(type, dimension, original, null, null, null, damContext);
  }

  @Override
  public String toString() {
    return new AemObjectReflectionToStringBuilder(this,
        io.wcm.wcm.commons.util.ToStringStyle.SHORT_PREFIX_OMIT_NULL_STYLE).build();
  }

}