io.wcm.handler.mediasource.dam.impl.DefaultRenditionHandler.java Source code

Java tutorial

Introduction

Here is the source code for io.wcm.handler.mediasource.dam.impl.DefaultRenditionHandler.java

Source

/*
 * #%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 io.wcm.handler.media.MediaArgs;
import io.wcm.handler.media.format.MediaFormat;
import io.wcm.handler.media.format.MediaFormatHandler;
import io.wcm.wcm.commons.contenttype.FileExtension;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.lang3.StringUtils;

import com.day.cq.dam.api.Asset;
import com.day.cq.dam.api.DamConstants;
import com.day.cq.dam.api.Rendition;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Handles resolving DAM renditions and resizing for media handler.
 */
class DefaultRenditionHandler implements RenditionHandler {

    private Set<RenditionMetadata> renditions;
    private final RenditionMetadata originalRendition;
    private final Asset asset;

    /**
     * @param asset DAM asset
     */
    public DefaultRenditionHandler(Asset asset) {
        this.asset = asset;

        Rendition damOriginalRendition = asset.getOriginal();
        originalRendition = damOriginalRendition != null ? new RenditionMetadata(damOriginalRendition) : null;
    }

    /**
     * @return All renditions that are available for this asset
     */
    Set<RenditionMetadata> getAvailableRenditions() {
        if (this.renditions == null) {
            // gather rendition infos of all renditions and sort them by size (smallest or virtual crop rendition first)
            Set<RenditionMetadata> candidates = new TreeSet<RenditionMetadata>();
            for (Rendition rendition : asset.getRenditions()) {
                addRendition(candidates, rendition);
            }
            candidates = postProcessCandidates(candidates);
            this.renditions = ImmutableSet.<RenditionMetadata>copyOf(candidates);
        }
        return this.renditions;
    }

    /**
     * Provides an option to post process the list of candidates. Can be overridden in subclasses
     * @param candidates
     * @return {@link Set} of {@link RenditionMetadata}
     */
    protected Set<RenditionMetadata> postProcessCandidates(Set<RenditionMetadata> candidates) {
        return candidates;
    }

    /**
     * adds rendition to the list of candidates, if it should be available for resolving
     * @param candidates
     * @param rendition
     */
    private void addRendition(Set<RenditionMetadata> candidates, Rendition rendition) {
        // ignore CQ thumbnail renditions
        if (!StringUtils.startsWith(rendition.getName(), DamConstants.PREFIX_ASSET_THUMBNAIL + ".")) {
            RenditionMetadata renditionMetadata = createRenditionMetadata(rendition);
            candidates.add(renditionMetadata);
        }
    }

    /**
     * Create rendition metadata for given rendition. May be overridden by subclasses.
     * @param rendition Rendition
     * @return Rendition metadata
     */
    protected RenditionMetadata createRenditionMetadata(Rendition rendition) {
        return new RenditionMetadata(rendition);
    }

    /**
     * Get all renditions that match the requested list of file extension.
     * @param fileExtensions List of file extensions
     * @return Matching renditions
     */
    private Set<RenditionMetadata> getRendtionsMatchingFileExtensions(String[] fileExtensions) {

        // if no file extension restriction get all renditions
        Set<RenditionMetadata> allRenditions = getAvailableRenditions();
        if (fileExtensions == null || fileExtensions.length == 0) {
            return allRenditions;
        }

        // otherwise return those with matching extensions
        Set<RenditionMetadata> matchingRenditions = new TreeSet<RenditionMetadata>();
        for (RenditionMetadata rendition : allRenditions) {
            for (String fileExtension : fileExtensions) {
                if (StringUtils.equalsIgnoreCase(fileExtension, rendition.getFileExtension())) {
                    matchingRenditions.add(rendition);
                    break;
                }
            }
        }
        return matchingRenditions;
    }

    /**
     * Get rendition (probably virtual) for given media arguments.
     * @param mediaArgs Media arguments
     * @return Rendition or null if none is matching
     */
    @Override
    public RenditionMetadata getRendition(MediaArgs mediaArgs) {

        // get list of file extensions requested
        String[] requestedFileExtensions = getRequestedFileExtensions(mediaArgs);

        // if the array is null file extensions constraints are applied, but do not match to each other
        // - no rendition can fulfill these constraints
        if (requestedFileExtensions == null) {
            return null;
        }

        // check if a specific media size is requested
        boolean isSizeMatchingRequest = isSizeMatchingRequest(mediaArgs, requestedFileExtensions);

        // get rendition candidates matching for file extensions
        Set<RenditionMetadata> candidates = getRendtionsMatchingFileExtensions(requestedFileExtensions);

        // if request does not contain any size restrictions return original image or first by filename matching rendition
        if (!isSizeMatchingRequest) {
            return getOriginalOrFirstRendition(candidates);
        }

        // original rendition is a image - check for matching rendition or build virtual one
        RenditionMetadata exactMatchRendition = getExactMatchRendition(candidates, mediaArgs);
        if (exactMatchRendition != null) {
            return exactMatchRendition;
        }

        // get rendition virtual rendition downscaled from existing one
        RenditionMetadata virtualRendition = getVirtualRendition(candidates, mediaArgs);
        if (virtualRendition != null) {
            return virtualRendition;
        }

        // no match found
        return null;
    }

    /**
     * Get merged list of file extensions from both media formats and media args.
     * @param mediaArgs Media args
     * @return Array of file extensions.
     *         Returns empty array if all file extensions are allowed.
     *         Returns null if different file extensions are requested in media formats and media args
     *         and the file extension filtering is not fulfillable.
     */
    private String[] getRequestedFileExtensions(MediaArgs mediaArgs) {
        // get file extension defined in media args
        Set<String> mediaArgsFileExtensions = new HashSet<String>();
        if (mediaArgs.getFileExtensions() != null && mediaArgs.getFileExtensions().length > 0) {
            mediaArgsFileExtensions.addAll(ImmutableList.copyOf(mediaArgs.getFileExtensions()));
        }

        // get file extensions from media formats
        final Set<String> mediaFormatFileExtensions = new HashSet<String>();
        visitMediaFormats(mediaArgs, new MediaFormatVisitor<Object>() {
            @Override
            public Object visit(MediaFormat mediaFormat) {
                if (mediaFormat.getExtensions() != null && mediaFormat.getExtensions().length > 0) {
                    mediaFormatFileExtensions.addAll(ImmutableList.copyOf(mediaFormat.getExtensions()));
                }
                return null;
            }
        });

        // if extensions are defined both in mediaargs and media formats use intersection of both
        final String[] fileExtensions;
        if (!mediaArgsFileExtensions.isEmpty() && !mediaFormatFileExtensions.isEmpty()) {
            Collection<String> intersection = Sets.intersection(mediaArgsFileExtensions, mediaFormatFileExtensions);
            if (intersection.isEmpty()) {
                // not intersected file extensions - return null to singal no valid file extension request
                return null;
            } else {
                fileExtensions = intersection.toArray(new String[intersection.size()]);
            }
        } else if (!mediaArgsFileExtensions.isEmpty()) {
            fileExtensions = mediaArgsFileExtensions.toArray(new String[mediaArgsFileExtensions.size()]);
        } else {
            fileExtensions = mediaFormatFileExtensions.toArray(new String[mediaFormatFileExtensions.size()]);
        }

        return fileExtensions;
    }

    /**
     * Checks if the media args contain any with/height restriction, that means a rendition matching
     * the given size constraints is requested. Additionally it is checked that at least one image file
     * extension is requested.
     * @param mediaArgs Media arguments
     * @return true if any size restriction was defined.
     */
    private boolean isSizeMatchingRequest(MediaArgs mediaArgs, String[] requestedFileExtensions) {

        // check that at least one image file extension is in the list of requested extensions
        boolean anyImageFileExtension = false;
        for (String fileExtension : requestedFileExtensions) {
            if (FileExtension.isImage(fileExtension)) {
                anyImageFileExtension = true;
            }
        }
        if (!anyImageFileExtension && mediaArgs.getFixedWidth() == 0 && mediaArgs.getFixedHeight() == 0) {
            return false;
        }

        // check for size restriction
        if (mediaArgs.getFixedWidth() > 0 || mediaArgs.getFixedHeight() > 0) {
            return true;
        }
        Boolean isSizeMatchingMediaFormat = visitMediaFormats(mediaArgs, new MediaFormatVisitor<Boolean>() {
            @Override
            @SuppressFBWarnings("NP_BOOLEAN_RETURN_NULL")
            public Boolean visit(MediaFormat mediaFormat) {
                if (mediaFormat.getEffectiveMinWidth() > 0 || mediaFormat.getEffectiveMaxWidth() > 0
                        || mediaFormat.getEffectiveMinHeight() > 0 || mediaFormat.getEffectiveMaxHeight() > 0
                        || mediaFormat.getRatio() > 0) {
                    return true;
                }
                return null;
            }
        });
        return isSizeMatchingMediaFormat != null && isSizeMatchingMediaFormat.booleanValue();
    }

    /**
     * Get rendition that matches exactly with the given media args requirements.
     * @param candidates Rendition candidates
     * @param mediaArgs Media args
     * @return Rendition or null if none found
     */
    private RenditionMetadata getExactMatchRendition(final Set<RenditionMetadata> candidates, MediaArgs mediaArgs) {
        // check for fixed width and/or height request
        if (mediaArgs.getFixedWidth() > 0 || mediaArgs.getFixedHeight() > 0) {
            for (RenditionMetadata candidate : candidates) {
                if (candidate.matches(mediaArgs.getFixedWidth(), mediaArgs.getFixedHeight())) {
                    return candidate;
                }
            }
        }

        // otherwise check for media format restriction
        else if (mediaArgs.getMediaFormats() != null && mediaArgs.getMediaFormats().length > 0) {
            return visitMediaFormats(mediaArgs, new MediaFormatVisitor<RenditionMetadata>() {
                @Override
                public RenditionMetadata visit(MediaFormat mediaFormat) {
                    for (RenditionMetadata candidate : candidates) {
                        if (candidate.matches((int) mediaFormat.getEffectiveMinWidth(),
                                (int) mediaFormat.getEffectiveMinHeight(), (int) mediaFormat.getEffectiveMaxWidth(),
                                (int) mediaFormat.getEffectiveMaxHeight(), mediaFormat.getRatio())) {
                            candidate.setMediaFormat(mediaFormat);
                            return candidate;
                        }
                    }
                    return null;
                }
            });
        }

        // no restriction - return original or first rendition
        else {
            return getOriginalOrFirstRendition(candidates);
        }

        // none found
        return null;
    }

    /**
     * Returns original rendition - if it is contained in the candidate set. Otherwise first candidate is returned.
     * @param candidates Candidates
     * @return Original or first rendition of candidates or null
     */
    private RenditionMetadata getOriginalOrFirstRendition(Set<RenditionMetadata> candidates) {
        if (this.originalRendition != null && candidates.contains(this.originalRendition)) {
            return this.originalRendition;
        } else if (!candidates.isEmpty()) {
            return candidates.iterator().next();
        } else {
            return null;
        }
    }

    /**
     * Check if a rendition is available from which the required format can be downscaled from and returns
     * a virtual rendition in this case.
     * @param candidates Candidates
     * @param mediaArgs Media args
     * @return Rendition or null
     */
    private RenditionMetadata getVirtualRendition(final Set<RenditionMetadata> candidates, MediaArgs mediaArgs) {

        // get from fixed with/height
        if (mediaArgs.getFixedWidth() > 0 || mediaArgs.getFixedHeight() > 0) {
            long destWidth = mediaArgs.getFixedWidth();
            long destHeight = mediaArgs.getFixedHeight();
            double destRatio = 0;
            if (destWidth > 0 && destHeight > 0) {
                destRatio = (double) destWidth / (double) destHeight;
            }
            return getVirtualRendition(candidates, destWidth, destHeight, destRatio);
        }

        // or from any media format
        return visitMediaFormats(mediaArgs, new MediaFormatVisitor<RenditionMetadata>() {
            @Override
            public RenditionMetadata visit(MediaFormat mediaFormat) {
                int destWidth = (int) mediaFormat.getEffectiveMinWidth();
                int destHeight = (int) mediaFormat.getEffectiveMinHeight();
                double destRatio = mediaFormat.getRatio();
                // try to find matching rendition, otherwise check for next media format
                RenditionMetadata rendition = getVirtualRendition(candidates, destWidth, destHeight, destRatio);
                if (rendition != null) {
                    rendition.setMediaFormat(mediaFormat);
                }
                return rendition;
            }
        });
    }

    /**
     * Check if a rendition is available from which the required format can be downscaled from and returns
     * a virtual rendition in this case.
     * @param candidates Candidates
     * @param destWidth Destination width
     * @param destHeight Destination height
     * @param destRatio Destination ratio
     * @return Rendition or null
     */
    private RenditionMetadata getVirtualRendition(Set<RenditionMetadata> candidates, long destWidth,
            long destHeight, double destRatio) {

        // if ratio is defined get first rendition with matching ratio and same or bigger size
        if (destRatio > 0) {
            for (RenditionMetadata candidate : candidates) {
                if (candidate.matches(destWidth, destHeight, 0, 0, destRatio)) {
                    return getVirtualRendition(candidate, destWidth, destHeight, destRatio);
                }
            }
        }
        // otherwise get first rendition which is same or bigger in width and height
        else {
            for (RenditionMetadata candidate : candidates) {
                if (candidate.matches(destWidth, destHeight, 0, 0, 0d)) {
                    return getVirtualRendition(candidate, destWidth, destHeight, 0d);
                }
            }
        }

        // none found
        return null;
    }

    /**
     * Get virtual rendition for given width/height/ratio.
     * @param rendition Rendition
     * @param widthValue Width
     * @param heightValue Height
     * @param ratioValue Ratio
     * @return Rendition or null
     */
    private RenditionMetadata getVirtualRendition(RenditionMetadata rendition, long widthValue, long heightValue,
            double ratioValue) {

        long width = widthValue;
        long height = heightValue;
        double ratio = ratioValue;

        // if ratio is missing: calculate from given rendition
        if (ratio < MediaFormatHandler.RATIO_TOLERANCE) {
            ratio = (double) rendition.getWidth() / (double) rendition.getHeight();
        }

        // if height is missing - calculate from width
        if (height == 0 && width > 0) {
            height = (int) Math.round(width * ratio);
        }

        // if width is missing - calculate from height
        if (width == 0 && height > 0) {
            width = (int) Math.round(height / ratio);
        }

        // return virtual rendition
        if (widthValue > 0 && heightValue > 0) {
            if (rendition instanceof VirtualCropRenditionMetadata) {
                VirtualCropRenditionMetadata cropRendition = (VirtualCropRenditionMetadata) rendition;
                return new VirtualCropRenditionMetadata(cropRendition.getRendition(), widthValue, heightValue,
                        cropRendition.getCropDimension());
            } else {
                return new VirtualRenditionMetadata(rendition.getRendition(), widthValue, heightValue);
            }
        } else {
            return null;
        }
    }

    /**
     * Iterate over all media formats defined in media args. Ignores invalid media formats.
     * If the media format visitor returns a value that is not null, iteration is stopped and the value is returned from
     * this method.
     * @param mediaArgs Media args
     * @param mediaFormatVisitor Media format visitor
     * @return Return value form media format visitor, if any returned a value that is not null
     */
    private <T> T visitMediaFormats(MediaArgs mediaArgs, MediaFormatVisitor<T> mediaFormatVisitor) {
        MediaFormat[] mediaFormats = mediaArgs.getMediaFormats();
        if (mediaFormats != null) {
            for (MediaFormat mediaFormat : mediaFormats) {
                T returnValue = mediaFormatVisitor.visit(mediaFormat);
                if (returnValue != null) {
                    return returnValue;
                }
            }
        }
        return null;
    }

}