diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml index bd99c91a..fc4f02da 100644 --- a/.github/workflows/maven-build.yml +++ b/.github/workflows/maven-build.yml @@ -20,7 +20,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - java: [8, 11, 17] + java: [11, 17, 21] os: [ubuntu-latest] distribution: [temurin] @@ -32,6 +32,6 @@ jobs: java-version: ${{ matrix.java }} maven-executable: ./mvnw sonar-run-on-os: ubuntu-latest - sonar-run-on-java-version: 11 + sonar-run-on-java-version: 17 sonar-token: ${{ secrets.SONAR_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/maven-deploy.yml b/.github/workflows/maven-deploy.yml index 96d6b65c..c709b1cc 100644 --- a/.github/workflows/maven-deploy.yml +++ b/.github/workflows/maven-deploy.yml @@ -1,7 +1,9 @@ -# Deploy snapshots to Sonatpe OSS repository and deploy site to GitHub Pages +# Deploy snapshots to Sonatype OSS repository and deploy site to GitHub Pages name: Deploy +concurrency: ${{ github.workflow }} + on: push: branches: @@ -15,7 +17,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Configure GIT run: | @@ -23,11 +25,11 @@ jobs: git config --global user.name "${{ secrets.GH_SITE_DEPLOY_NAME }}" - name: Setup JDK - uses: actions/setup-java@v2 + uses: actions/setup-java@v3 with: distribution: temurin - java-version: 8 - cache: 'maven' + java-version: 11 + cache: maven - name: Build, verify, deploy, generate site env: diff --git a/.github/workflows/release-from-tag.yml b/.github/workflows/release-from-tag.yml index 93e00ed1..c4aab046 100644 --- a/.github/workflows/release-from-tag.yml +++ b/.github/workflows/release-from-tag.yml @@ -12,7 +12,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ncipollo/release-action@v1 with: body: 'Changes: https://wcm.io/handler/media/changes-report.html' diff --git a/.gitignore b/.gitignore index cb0de5f4..e6146058 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ npm-debug.log .vlt .vlt-sync* .brackets.json +dependency-reduced-pom.xml diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index c1dd12f1..cb28b0e3 100644 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 8c79a83a..ac184013 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -5,14 +5,14 @@ # to you 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. -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.4/apache-maven-3.9.4-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/README.md b/README.md index 8647c88d..1093177c 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Media resolving, processing and markup generation. Documentation: https://wcm.io/handler/media/
-Issues: https://wcm-io.atlassian.net/projects/WHAN
+Issues: https://github.com/wcm-io/io.wcm.handler.media/issues
Wiki: https://wcm-io.atlassian.net/wiki/
Continuous Integration: https://github.com/wcm-io/io.wcm.handler.media/actions
Commercial support: https://wcm.io/commercial-support.html diff --git a/changes.xml b/changes.xml index 6f6678a0..9e6e6587 100644 --- a/changes.xml +++ b/changes.xml @@ -23,12 +23,123 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> - + + + Remove deprecated functionality. + + Switch to AEM 6.5.17 as minimum version. + + MediaFileServlet: Force Content-Disposition=attachment header for SVG files served by MediaFileServlet. Also ensure URLs to SVG images are always handled by MediaFileServlet. + + + MediaHandlerConfig: Make list of allowed IPE editor types configurable (defaults to "image"). + + + DefaultMediaFormatListProvider, MediaFormatValidateServlet: Eliminate usage of org.apache.sling.commons.json. + + + + + + DAM Renditions: Read width/height of rendition lazy, as this can be expensive when not configured properly or Asset metadata is missing for other reasons. + + + MediaComponentPropertyResolver: Add option to resolve properties directly from a value map. + + + + + + Eliminate dependency to Guava. Embed Caffeine as replacement for Guava Cache. + + + + + + Switch to Java 11 as minimum version. + + + Reduce dependency to Guava. + + + Sling-Initial-Content: Register non-standard JCR namespaces for conversion with cp2fm into enhanced DocView files. + + + + + + MediaHandler: Build HTML element markup on-demand based on the current status of media when the element is requested. This allows to react on results of media post processors, and avoids building the element if not required. + + + File Upload Granite UI component: Fix "Clear Transformation" button when namePrefix with sub-node is used. + + + + + + Add Rendition.getUriTemplate method to build URI templates for getting scaled or auto-cropped renditions of an asset with the restrictions of the rendition e.g. aspect ratio. + + + File Upload Granite UI Widget: Allow video/mpeg and video/quicktime by default and support updating thumbnails. + + + Dynamic Media Support: Fix smart-cropping rendition validation in case unconstrained media formats are used (without exact size, e.g. only minimum width) and the original image ratio excaclty matches the requested ratio. + + + + + + Dynamic Media Support: Make Smart Crop rendition validation configurable (default: enabled). + + + Dynamic Media Support: Apply fail-safe approach when normalized width/height provided by Dynamic Media for smart cropping are not matching the defined aspect ratio. + + + + + + DAM Media Source: Use cropping dimension based on original rendition internally. Re-calculate webenabled rendition-based cropping coordinates when loading them from repository before starting the media processing, and not only when doing the actual cropping. + + + Dynamic Media Support: Fix smart-cropping rendition validation in case unconstrained media formats are used (without exact size, e.g. only minimum width). + + + + + + Dynamic Media Support: Ensure smart-cropped renditions fulfill minimum size requirements. + + + + + + Dynamic Media Support: Do not rely on Dynamic Media feature flag to detect DM capability on publish instances. In "AUTO" mode only the availability of DM metadata on a given asset is checked. + + + + + + Dynamic Media Support: Introduce OSGi configuration parameter dmCapabilityDetection to switch from auto-detection to enable or disable Dynamic Media capability via configuration. + + + + + + Dynamic Media Support: Make use of smart-cropped image rendition also in the case if the original image has same ratio as the requested ratio. + + + + + <auto> when building Dynamic Media URLs in author instance. + ]]> + + Switch to AEM 6.5.7 as minimum version. @@ -103,7 +214,7 @@ Add support for properties "altValueFromDAM"/"mediaForceAltValueFromAsset". If set to true, it is forced to use the alt. text from asset description (otherwise it's the fallback behavior). Change default behavior of alt text lookup from DAM: Use asset description if avalailable as alt. text, fallback to asset title if not description is set. + Change default behavior of alt text lookup from DAM: Use asset description if available, fallback to asset title if no description is set. ]]> diff --git a/mvnw b/mvnw index 5643201c..8d937f4c 100755 --- a/mvnw +++ b/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.2.0 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -54,7 +53,7 @@ fi cygwin=false; darwin=false; mingw=false -case "`uname`" in +case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true @@ -62,9 +61,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME else - export JAVA_HOME="/Library/Java/Home" + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; @@ -72,68 +71,38 @@ esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "\"$javaExecutable\"")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi @@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`\\unset -f command; \\command -v java`" + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi fi @@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -184,96 +150,99 @@ find_maven_basedir() { fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$(cd "$wdir/.." || exit 1; pwd) fi # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir" || exit 1; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1; fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi + log "Couldn't find $wrapperJarPath, downloading it ..." + if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" else - jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi @@ -282,35 +251,58 @@ fi # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/mvnw.cmd b/mvnw.cmd index 8a15b7f3..c4586b56 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -18,13 +18,12 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.2.0 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% + echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ @@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% ( "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% @@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% ( ) @REM End of extension +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* diff --git a/pom.xml b/pom.xml index 4ba37dcb..f03da51a 100644 --- a/pom.xml +++ b/pom.xml @@ -25,13 +25,13 @@ io.wcm io.wcm.parent_toplevel - 2.1.0 + 2.3.0-SNAPSHOT io.wcm io.wcm.handler.media - 1.14.5-SNAPSHOT + 2.0.0-SNAPSHOT jar Media Handler @@ -49,7 +49,7 @@ handler/media - 2022-06-16T17:48:16Z + 2023-12-18T11:21:09Z @@ -63,13 +63,13 @@ io.wcm io.wcm.handler.commons - 1.4.4 + 2.0.0-SNAPSHOT compile io.wcm io.wcm.handler.url - 1.5.0 + 2.0.0-SNAPSHOT compile @@ -82,19 +82,26 @@ io.wcm io.wcm.wcm.commons - 1.9.0 + 1.10.0 compile io.wcm io.wcm.sling.commons - 1.4.0 + 1.6.4 compile io.wcm io.wcm.wcm.ui.granite - 1.8.0 + 1.10.0 + compile + + + + com.github.ben-manes.caffeine + caffeine + 3.1.8 compile @@ -131,7 +138,7 @@ io.wcm io.wcm.testing.aem-mock.junit5 - 5.0.0 + 5.4.4 test @@ -143,13 +150,13 @@ org.apache.sling org.apache.sling.testing.caconfig-mock-plugin - 1.4.0 + 1.5.4 test io.wcm io.wcm.testing.wcm-io-mock.sling - 1.1.0 + 1.2.0 test @@ -161,7 +168,7 @@ io.wcm io.wcm.testing.wcm-io-mock.caconfig - 1.1.0 + 1.2.0 test @@ -180,7 +187,24 @@ org.skyscreamer jsonassert - 1.5.0 + 1.5.1 + test + + + com.fasterxml.jackson.core + jackson-databind + test + + + com.jayway.jsonpath + json-path + 2.8.0 + test + + + com.jayway.jsonpath + json-path-assert + 2.8.0 test @@ -188,20 +212,20 @@ com.twelvemonkeys.imageio imageio-tiff - 3.4.1 + 3.10.1 test com.twelvemonkeys.imageio imageio-batik - 3.4.1 + 3.10.1 test org.apache.xmlgraphics batik-transcoder - 1.9.1 + 1.17 test @@ -225,13 +249,19 @@ Sling-Initial-Content: \ SLING-INF/app-root;overwrite:=true;ignoreImportProviders:=xml;path:=/apps/wcm-io/handler/media - Sling-Namespaces: wcmio=http://wcm.io/ns + Sling-Namespaces: \ + wcmio=http://wcm.io/ns,\ + cq=http://www.day.com/jcr/cq/1.0,\ + granite=http://www.adobe.com/jcr/granite/1.0,\ + sling=http://sling.apache.org/jcr/sling/1.0 Import-Package: \ \ javax.annotation;version="[0.0,2)",\ \ com.day.cq.dam.api.handler;version="[1.0,3)",\ + \ + !com.github.benmanes.caffeine.*,\ * @@ -250,6 +280,42 @@ + + + org.apache.maven.plugins + maven-shade-plugin + + + + com.github.ben-manes.caffeine:caffeine + + + + + com.github.benmanes.caffeine + io.wcm.handler.media.shaded.com.github.benmanes.caffeine + + + + + com.github.ben-manes.caffeine:caffeine + + module-info.class + META-INF/** + + + + + + + package + + shade + + + + + io.wcm.maven.plugins i18n-maven-plugin diff --git a/src/main/java/io/wcm/handler/media/Asset.java b/src/main/java/io/wcm/handler/media/Asset.java index 4be051f9..041945db 100644 --- a/src/main/java/io/wcm/handler/media/Asset.java +++ b/src/main/java/io/wcm/handler/media/Asset.java @@ -93,16 +93,6 @@ public interface Asset extends Adaptable { @Nullable Rendition getImageRendition(@NotNull MediaArgs mediaArgs); - /** - * Get the first flash rendition that matches the given media args. - * @param mediaArgs Media args to filter specific media formats or extensions. - * @return {@link Rendition} for the first matching rendition or null if no match found. - * @deprecated Flash support is deprecated - */ - @Deprecated - @Nullable - Rendition getFlashRendition(@NotNull MediaArgs mediaArgs); - /** * Get the first download rendition that matches the given media args. * @param mediaArgs Media args to filter specific media formats or extensions. @@ -117,7 +107,7 @@ public interface Asset extends Adaptable { * asset and the max. width/height of it's original rendition. * @param type URI template type * @return URI template - * @throws UnsupportedOperationException if the original rendition is not an image or a vector image. + * @throws UnsupportedOperationException if the original rendition is not an image or it is a vector image. */ @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type); diff --git a/src/main/java/io/wcm/handler/media/Media.java b/src/main/java/io/wcm/handler/media/Media.java index 9a00a248..7fe640a5 100644 --- a/src/main/java/io/wcm/handler/media/Media.java +++ b/src/main/java/io/wcm/handler/media/Media.java @@ -20,7 +20,9 @@ package io.wcm.handler.media; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.function.Function; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -32,7 +34,6 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import com.google.common.collect.ImmutableList; import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.commons.dom.Span; @@ -49,6 +50,7 @@ public final class Media { private final @NotNull MediaSource mediaSource; private @NotNull MediaRequest mediaRequest; private HtmlElement element; + private Function> elementBuilder; private String url; private Asset asset; private Collection renditions; @@ -96,6 +98,10 @@ public void setMediaRequest(@NotNull MediaRequest mediaRequest) { */ @JsonIgnore public HtmlElement getElement() { + if (this.element == null && this.elementBuilder != null) { + this.element = this.elementBuilder.apply(this); + this.elementBuilder = null; + } return this.element; } @@ -104,27 +110,28 @@ public HtmlElement getElement() { */ @JsonIgnore public String getMarkup() { - if (markup == null && this.element != null) { - if (this.element instanceof Span) { + HtmlElement el = getElement(); + if (markup == null && el != null) { + if (el instanceof Span) { // in case of span get inner HTML markup, do not include span element itself StringBuilder result = new StringBuilder(); - for (Element child : this.element.getChildren()) { + for (Element child : el.getChildren()) { result.append(child.toString()); } markup = result.toString(); } else { - markup = this.element.toString(); + markup = el.toString(); } } return markup; } /** - * @param value Html element + * @param value Function that builds the HTML element representation on demand */ - public void setElement(HtmlElement value) { - this.element = value; + public void setElementBuilder(Function> value) { + this.elementBuilder = value; this.markup = null; } @@ -176,7 +183,7 @@ public Rendition getRendition() { */ public Collection getRenditions() { if (this.renditions == null) { - return ImmutableList.of(); + return Collections.emptyList(); } else { return this.renditions; diff --git a/src/main/java/io/wcm/handler/media/MediaArgs.java b/src/main/java/io/wcm/handler/media/MediaArgs.java index 1f726872..6d8723be 100644 --- a/src/main/java/io/wcm/handler/media/MediaArgs.java +++ b/src/main/java/io/wcm/handler/media/MediaArgs.java @@ -36,8 +36,6 @@ import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; -import com.google.common.collect.ImmutableSet; - import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.markup.DragDropSupport; import io.wcm.handler.media.markup.IPERatioCustomize; @@ -73,7 +71,7 @@ public final class MediaArgs implements Cloneable { private boolean dynamicMediaDisabled; private ValueMap properties; - private static final Set ALLOWED_FORCED_FILE_EXTENSIONS = ImmutableSet.of( + private static final Set ALLOWED_FORCED_FILE_EXTENSIONS = Set.of( FileExtension.JPEG, FileExtension.PNG); /** @@ -99,13 +97,10 @@ public MediaArgs(@NotNull String @NotNull... mediaFormatNames) { } /** - * Returns list of media formats to resolve to. If {@link #isMediaFormatsMandatory()} is false, - * the first rendition that matches any of the given media format is returned. If it is set to true, - * for each media format given a rendition has to be resolved and returned. If not all renditions - * could be resolved the media is marked as invalid (but the partial resolved renditions are returned anyway). + * Returns list of media formats to resolve to. * @return Media formats */ - public MediaFormat[] getMediaFormats() { + public MediaFormat @Nullable [] getMediaFormats() { if (this.mediaFormatOptions != null) { MediaFormat[] result = Arrays.stream(this.mediaFormatOptions) .filter(option -> option.getMediaFormatName() == null) @@ -137,7 +132,6 @@ public MediaFormat[] getMediaFormats() { /** * Sets list of media formats to resolve to. - * Additionally {@link #isMediaFormatsMandatory()} is set to true. * @param values Media formats * @return this */ @@ -170,21 +164,6 @@ public MediaFormat[] getMediaFormats() { return this; } - /** - * Checks if all media format options have the "mandatory" flag set. - * If none or not all of them have the flag set, false is returned. - * @return true if all media format options have the "mandatory" flag set. - * @deprecated Please check the mandatory flag for each media format individually via {@link #getMediaFormatOptions()} - */ - @Deprecated - public boolean isMediaFormatsMandatory() { - if (this.mediaFormatOptions == null) { - return false; - } - return !Arrays.stream(this.mediaFormatOptions) - .anyMatch(option -> !option.isMandatory()); - } - /** * The "mandatory" flag of all media format options is set to to the given value. * @param value Resolving of all media formats is mandatory. @@ -235,7 +214,6 @@ public String[] getMediaFormatNames() { /** * Sets list of media formats to resolve to. - * Additionally {@link #isMediaFormatsMandatory()} is set to true. * @param names Media format names. * @return this */ @@ -967,17 +945,6 @@ public ImageSizes(@NotNull String sizes, @NotNull WidthOption @NotNull... widthO return this.sizes; } - /** - * @return Widths for the renditions in the srcset attribute. - * @deprecated Use {@link #getWidthOptions()} - */ - @Deprecated - public long @Nullable [] getWidths() { - return Arrays.stream(this.widthOptions) - .mapToLong(WidthOption::getWidth) - .toArray(); - } - /** * @return Widths for the renditions in the srcset attribute. */ @@ -1034,36 +1001,6 @@ public PictureSource(@Nullable String mediaFormatName) { this.mediaFormatName = mediaFormatName; } - /** - * @param mediaFormat Media format - * @param media A valid media query - * list - * @param widths Widths for the renditions in the srcset attribute (all mandatory). - * @deprecated Use constructor with {@link MediaFormat} and {@link #media} and {@link #widths(long...)}. - */ - @Deprecated - public PictureSource(@NotNull MediaFormat mediaFormat, @Nullable String media, - long @NotNull... widths) { - this.mediaFormat = mediaFormat; - this.media = media; - this.widthOptions = toWidthOptions(widths); - } - - /** - * @param mediaFormat Media format - * @param media A valid media query - * list - * @param widthOptions Widths for the renditions in the srcset attribute. - * @deprecated Use constructor with {@link MediaFormat} and {@link #media} and {@link #widths(long...)}. - */ - @Deprecated - public PictureSource(@Nullable MediaFormat mediaFormat, @Nullable String media, - @NotNull WidthOption @NotNull... widthOptions) { - this.mediaFormat = mediaFormat; - this.media = media; - this.widthOptions = widthOptions; - } - private static @NotNull WidthOption @NotNull [] toWidthOptions(long @NotNull... widths) { return Arrays.stream(widths) .mapToObj(width -> new WidthOption(width, true)) @@ -1109,17 +1046,6 @@ public PictureSource widths(long @NotNull... value) { return this; } - /** - * @return Widths for the renditions in the srcset attribute. - * @deprecated Use {@link #getWidthOptions()} - */ - @Deprecated - public long @Nullable [] getWidths() { - return Arrays.stream(this.widthOptions) - .mapToLong(WidthOption::getWidth) - .toArray(); - } - /** * @param value A valid * source size list. diff --git a/src/main/java/io/wcm/handler/media/MediaBuilder.java b/src/main/java/io/wcm/handler/media/MediaBuilder.java index 6a9a1158..17e630eb 100644 --- a/src/main/java/io/wcm/handler/media/MediaBuilder.java +++ b/src/main/java/io/wcm/handler/media/MediaBuilder.java @@ -304,36 +304,6 @@ public interface MediaBuilder { @NotNull MediaBuilder pictureSource(@NotNull PictureSource pictureSource); - /** - * Apply responsive image handling using picture and source elements. - * This will add one source element with an media attribute set to the given media - * string, and a srcset attribute with renditions for each width given based on the given media format. - * @param mediaFormat Media format with ratio for the renditions of the source element - * @param media A valid media query - * list - * @param widths Widths for the renditions in the srcset attribute. - * All renditions will use the ratio of the given media format. - * @return this - * @deprecated Use {@link #pictureSource(io.wcm.handler.media.MediaArgs.PictureSource)} - */ - @Deprecated - @NotNull - MediaBuilder pictureSource(@NotNull MediaFormat mediaFormat, @NotNull String media, long @NotNull... widths); - - /** - * Apply responsive image handling using picture and source elements. - * This will add one source element without an media attribute, and a srcset - * attribute with renditions for each width given based on the given media format. - * @param mediaFormat Media format with ratio for the renditions of the source element - * @param widths Widths for the renditions in the srcset attribute. - * All renditions will use the ratio of the given media format. - * @return this - * @deprecated Use {@link #pictureSource(io.wcm.handler.media.MediaArgs.PictureSource)} - */ - @Deprecated - @NotNull - MediaBuilder pictureSource(@NotNull MediaFormat mediaFormat, long @NotNull... widths); - /** * Disable dynamic media support. * @param value If set to true, dynamic media support is disabled even when enabled on the instance. diff --git a/src/main/java/io/wcm/handler/media/MediaComponentPropertyResolver.java b/src/main/java/io/wcm/handler/media/MediaComponentPropertyResolver.java index e3d0a308..9690430c 100644 --- a/src/main/java/io/wcm/handler/media/MediaComponentPropertyResolver.java +++ b/src/main/java/io/wcm/handler/media/MediaComponentPropertyResolver.java @@ -21,8 +21,6 @@ import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES; import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVEPICTURE_SOURCES; -import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES; -import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES; import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP; import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS; import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS_MANDATORY; @@ -58,6 +56,10 @@ * Resolves Media Handler component properties for the component associated * with the given resource from content policies and properties defined in the component resource. * Please make sure to {@link #close()} instances of this class after usage. + *

+ * Alternatively, it's possible to use the resolver on a ValueMap. In this case, the properties + * are directly read from the provided value map. Picture Sources are not supported for that option. + *

*/ @ProviderType @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION") @@ -74,9 +76,11 @@ public final class MediaComponentPropertyResolver implements AutoCloseable { static final String PN_PICTURE_SOURCES_SIZES = "sizes"; static final String PN_PICTURE_SOURCES_WIDTHS = "widths"; - private final ComponentPropertyResolver resolver; + private final @Nullable ComponentPropertyResolver resolver; + private final PropertyAccessor propertyAccessor; /** + * Resolves * @param resource Resource * @param componentPropertyResolverFactory Component property resolver factory */ @@ -86,26 +90,22 @@ public MediaComponentPropertyResolver(@NotNull Resource resource, resolver = componentPropertyResolverFactory.get(resource, true) .contentPolicyResolution(ComponentPropertyResolution.RESOLVE) .componentPropertiesResolution(ComponentPropertyResolution.RESOLVE_INHERIT); + propertyAccessor = new ComponentPropertyResolverPropertyAccessor(resolver); } /** - * @param resource Resource - * @deprecated Please use {@link #MediaComponentPropertyResolver(Resource, ComponentPropertyResolverFactory)} + * @param valueMap Value map to read properties directly from */ - @Deprecated - @SuppressWarnings("resource") - public MediaComponentPropertyResolver(@NotNull Resource resource) { - // resolve media component properties 1. from policies and 2. from component definition - resolver = new ComponentPropertyResolver(resource, true) - .contentPolicyResolution(ComponentPropertyResolution.RESOLVE) - .componentPropertiesResolution(ComponentPropertyResolution.RESOLVE_INHERIT); + public MediaComponentPropertyResolver(@NotNull ValueMap valueMap) { + resolver = null; + propertyAccessor = new ValueMapPropertyAccessor(valueMap); } /** * @return AutoCrop state */ public boolean isAutoCrop() { - return resolver.get(PN_COMPONENT_MEDIA_AUTOCROP, false); + return propertyAccessor.get(PN_COMPONENT_MEDIA_AUTOCROP, false); } /** @@ -115,8 +115,8 @@ public boolean isAutoCrop() { Map mediaFormatOptions = new LinkedHashMap<>(); // media formats with optional mandatory boolean flag(s) - String[] mediaFormatNames = resolver.get(PN_COMPONENT_MEDIA_FORMATS, String[].class); - Boolean[] mediaFormatsMandatory = resolver.get(PN_COMPONENT_MEDIA_FORMATS_MANDATORY, Boolean[].class); + String[] mediaFormatNames = propertyAccessor.get(PN_COMPONENT_MEDIA_FORMATS, String[].class); + Boolean[] mediaFormatsMandatory = propertyAccessor.get(PN_COMPONENT_MEDIA_FORMATS_MANDATORY, Boolean[].class); if (mediaFormatNames != null) { for (int i = 0; i < mediaFormatNames.length; i++) { boolean mandatory = false; @@ -136,7 +136,7 @@ else if (mediaFormatsMandatory.length > i) { } // support additional property with list of media format names that are all rated as mandatory - String[] mediaFormatsMandatoryNames = resolver.get(PN_COMPONENT_MEDIA_FORMATS_MANDATORY_NAMES, String[].class); + String[] mediaFormatsMandatoryNames = propertyAccessor.get(PN_COMPONENT_MEDIA_FORMATS_MANDATORY_NAMES, String[].class); if (mediaFormatsMandatoryNames != null) { for (String mediaFormatName : mediaFormatsMandatoryNames) { if (StringUtils.isNotBlank(mediaFormatName)) { @@ -193,22 +193,15 @@ else if (mediaFormatsMandatory.length > i) { /** * @return Image sizes */ - @SuppressWarnings({ "deprecation", "null" }) + @SuppressWarnings("null") public @Nullable ImageSizes getImageSizes() { String responsiveType = getResponsiveType(); if (responsiveType != null && !StringUtils.equals(responsiveType, RESPONSIVE_TYPE_IMAGE_SIZES)) { return null; } - String sizes = StringUtils.trimToNull(resolver.get(NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES + "/" + PN_IMAGES_SIZES_SIZES, String.class)); - WidthOption[] widths = WidthUtils.parseWidths(resolver.get(NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES + "/" + PN_IMAGES_SIZES_WIDTHS, String.class)); - if (sizes != null && widths != null) { - return new ImageSizes(sizes, widths); - } - - // try to fallback to deprecated constant with node names with typo (backward compatibility) - sizes = StringUtils.trimToNull(resolver.get(NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES + "/" + PN_IMAGES_SIZES_SIZES, String.class)); - widths = WidthUtils.parseWidths(resolver.get(NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES + "/" + PN_IMAGES_SIZES_WIDTHS, String.class)); + String sizes = StringUtils.trimToNull(propertyAccessor.get(NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES + "/" + PN_IMAGES_SIZES_SIZES, String.class)); + WidthOption[] widths = WidthUtils.parseWidths(propertyAccessor.get(NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES + "/" + PN_IMAGES_SIZES_WIDTHS, String.class)); if (sizes != null && widths != null) { return new ImageSizes(sizes, widths); } @@ -219,20 +212,16 @@ else if (mediaFormatsMandatory.length > i) { /** * @return List of picture sources */ - @SuppressWarnings({ "deprecation", "null" }) + @SuppressWarnings("null") public @NotNull PictureSource @Nullable [] getPictureSources() { String responsiveType = getResponsiveType(); - if (responsiveType != null && !StringUtils.equals(responsiveType, RESPONSIVE_TYPE_PICTURE_SOURCES)) { + if (resolver == null || responsiveType != null && !StringUtils.equals(responsiveType, RESPONSIVE_TYPE_PICTURE_SOURCES)) { return null; } Collection sourceResources = resolver.getResources(NN_COMPONENT_MEDIA_RESPONSIVEPICTURE_SOURCES); if (sourceResources == null) { - // try to fallback to deprecated constant with node names with typo (backward compatibility) - sourceResources = resolver.getResources(NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES); - if (sourceResources == null) { - return null; - } + return null; } List sources = new ArrayList<>(); @@ -259,12 +248,52 @@ else if (mediaFormatsMandatory.length > i) { } private String getResponsiveType() { - return resolver.get(PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, String.class); + return propertyAccessor.get(PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, String.class); } @Override - public void close() throws Exception { - resolver.close(); + @SuppressWarnings("null") + public void close() { + if (resolver != null) { + resolver.close(); + } + } + + private interface PropertyAccessor { + @Nullable + T get(@NotNull String name, @NotNull Class type); + + T get(@NotNull String name, @NotNull T defaultValue); + } + + private static class ComponentPropertyResolverPropertyAccessor implements PropertyAccessor { + private final ComponentPropertyResolver componentPropertyResolver; + ComponentPropertyResolverPropertyAccessor(ComponentPropertyResolver componentPropertyResolver) { + this.componentPropertyResolver = componentPropertyResolver; + } + @Override + public @Nullable T get(@NotNull String name, @NotNull Class type) { + return componentPropertyResolver.get(name, type); + } + @Override + public T get(@NotNull String name, @NotNull T defaultValue) { + return componentPropertyResolver.get(name, defaultValue); + } + } + + private static class ValueMapPropertyAccessor implements PropertyAccessor { + private final ValueMap valueMap; + ValueMapPropertyAccessor(ValueMap valueMap) { + this.valueMap = valueMap; + } + @Override + public @Nullable T get(@NotNull String name, @NotNull Class type) { + return valueMap.get(name, type); + } + @Override + public T get(@NotNull String name, @NotNull T defaultValue) { + return valueMap.get(name, defaultValue); + } } } diff --git a/src/main/java/io/wcm/handler/media/MediaFileType.java b/src/main/java/io/wcm/handler/media/MediaFileType.java index 07e8d501..c1d2e6bd 100644 --- a/src/main/java/io/wcm/handler/media/MediaFileType.java +++ b/src/main/java/io/wcm/handler/media/MediaFileType.java @@ -28,8 +28,6 @@ import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ProviderType; -import com.google.common.collect.ImmutableSet; - import io.wcm.wcm.commons.contenttype.ContentType; import io.wcm.wcm.commons.contenttype.FileExtension; @@ -62,21 +60,16 @@ public enum MediaFileType { /** * SVG */ - SVG(new String[] { ContentType.SVG }, new String[] { FileExtension.SVG }), + SVG(new String[] { ContentType.SVG }, new String[] { FileExtension.SVG }); - /** - * Flash - * @deprecated Flash support is deprecated - */ - @Deprecated - SWF(new String[] { ContentType.SWF }, new String[] { FileExtension.SWF }); private final Set contentTypes; private final Set extensions; + @SuppressWarnings("null") MediaFileType(@NotNull String @NotNull [] contentTypes, @NotNull String @NotNull [] extensions) { - this.contentTypes = ImmutableSet.copyOf(contentTypes); - this.extensions = ImmutableSet.copyOf(extensions); + this.contentTypes = Set.of(contentTypes); + this.extensions = Set.of(extensions); } /** @@ -118,12 +111,6 @@ public Set getExtensions() { private static final EnumSet VECTOR_IMAGE_FILE_TYPES = EnumSet.of( SVG); - /** - * All file types that will be displayed as Flash. - */ - private static final EnumSet FLASH_FILE_TYPES = EnumSet.of( - SWF); - /** * Check if the given file extension is supported by the Media Handler for rendering as image. * @param fileExtension File extension @@ -193,35 +180,6 @@ public static boolean isVectorImage(@Nullable String fileExtension) { return getContentTypes(VECTOR_IMAGE_FILE_TYPES); } - /** - * Check if the given file extension is an flash. - * @param fileExtension File extension - * @return true if flash - * @deprecated Flash support is deprecated - */ - @Deprecated - public static boolean isFlash(@Nullable String fileExtension) { - return isExtension(FLASH_FILE_TYPES, fileExtension); - } - - /** - * @return Flash file extensions - * @deprecated Flash support is deprecated - */ - @Deprecated - public static @NotNull Set getFlashFileExtensions() { - return getFileExtensions(FLASH_FILE_TYPES); - } - - /** - * @return Flash content types - * @deprecated Flash support is deprecated - */ - @Deprecated - public static @NotNull Set getFlashContentTypes() { - return getContentTypes(FLASH_FILE_TYPES); - } - private static Set getContentTypes(@NotNull EnumSet fileTypes) { return fileTypes.stream() .flatMap(type -> type.getContentTypes().stream()) diff --git a/src/main/java/io/wcm/handler/media/MediaInvalidReason.java b/src/main/java/io/wcm/handler/media/MediaInvalidReason.java index 5687c909..ea2c450f 100644 --- a/src/main/java/io/wcm/handler/media/MediaInvalidReason.java +++ b/src/main/java/io/wcm/handler/media/MediaInvalidReason.java @@ -45,13 +45,6 @@ public enum MediaInvalidReason { */ NOT_ENOUGH_MATCHING_RENDITIONS, - /** - * No media source found for handling the given (or empty) media request - * @deprecated No longer in use, first media source defined is used as fallback if no match is found. - */ - @Deprecated - NO_MEDIA_SOURCE, - /** * One or all of the given media format names are invalid. */ diff --git a/src/main/java/io/wcm/handler/media/MediaNameConstants.java b/src/main/java/io/wcm/handler/media/MediaNameConstants.java index 007e6ca9..4ff421b7 100644 --- a/src/main/java/io/wcm/handler/media/MediaNameConstants.java +++ b/src/main/java/io/wcm/handler/media/MediaNameConstants.java @@ -133,13 +133,6 @@ private MediaNameConstants() { */ public static final @NotNull String NN_MEDIA_INLINE_STANDARD = "file"; - /** - * Default property name for flash variables - * @deprecated Flash support is deprecated - */ - @Deprecated - public static final @NotNull String PN_FLASH_VARS = "flashVars"; - /** * CSS class for dummy image */ @@ -160,13 +153,6 @@ private MediaNameConstants() { */ public static final @NotNull String CSS_DIFF_REMOVED = "wcmio_mediahandler_wcm_diff_removed"; - /** - * Property name for responsive breakpoint (mq) - * @deprecated This is used only for the "legacy mode" of responsive image handling. - */ - @Deprecated - public static final @NotNull String PROP_BREAKPOINT = "mq"; - /** * Property name for setting additional CSS classes */ @@ -235,29 +221,6 @@ private MediaNameConstants() { */ public static final @NotNull String NN_COMPONENT_MEDIA_RESPONSIVEPICTURE_SOURCES = "wcmio:mediaResponsivePictureSources"; - /** - * Defines "image sizes" responsive image setting. - * Contains properties sizes, widths. - *

- * Child node is to be defined on component or in policy. - *

- * @deprecated Please use {@link #NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES} - */ - @Deprecated - public static final @NotNull String NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES = "wcmio:mediaRepsonsiveImageSizes"; - - /** - * Defines "picture sources" responsive image setting. - * Contains child nodes for each source definition with properties mediaFormat, media, - * widths. - *

- * Child node is to be defined on component or in policy. - *

- * @deprecated Please use {@link #NN_COMPONENT_MEDIA_RESPONSIVEPICTURE_SOURCES} - */ - @Deprecated - public static final @NotNull String NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES = "wcmio:mediaRepsonsivePictureSources"; - /** * Media format property name for the parent media format. Parent media format is the original media format that * is used to generate a width-based sub-media-format for responsive images. @@ -274,11 +237,4 @@ private MediaNameConstants() { */ public static final String URI_TEMPLATE_PLACEHOLDER_HEIGHT = "{height}"; - /** - * URI template placeholder for width. - * @deprecated Please use {@link #URI_TEMPLATE_PLACEHOLDER_WIDTH} - */ - @Deprecated - public static final String URI_TEMPLATE_PLACEHOLDER_WITH = URI_TEMPLATE_PLACEHOLDER_WIDTH; - } diff --git a/src/main/java/io/wcm/handler/media/MediaRequest.java b/src/main/java/io/wcm/handler/media/MediaRequest.java index cb765ddf..acaf7ab6 100644 --- a/src/main/java/io/wcm/handler/media/MediaRequest.java +++ b/src/main/java/io/wcm/handler/media/MediaRequest.java @@ -49,7 +49,7 @@ public final class MediaRequest { * @param mediaArgs Additional arguments affection media resolving */ public MediaRequest(@NotNull Resource resource, @Nullable MediaArgs mediaArgs) { - this(resource, null, mediaArgs, null, null, null); + this(resource, null, mediaArgs, null); } /** @@ -57,41 +57,7 @@ public MediaRequest(@NotNull Resource resource, @Nullable MediaArgs mediaArgs) { * @param mediaArgs Additional arguments affection media resolving */ public MediaRequest(@NotNull String mediaRef, @Nullable MediaArgs mediaArgs) { - this(null, mediaRef, mediaArgs, null, null, null); - } - - /** - * @param resource Resource containing reference to media asset - * @param mediaRef Reference to media item - * @param mediaArgs Additional arguments affection media resolving - * @param refProperty Name of the property from which the media reference is read - * @param cropProperty Name of the property which contains the cropping parameters - * @deprecated Use {@link #MediaRequest(Resource, String, MediaArgs, MediaPropertyNames)} - */ - @Deprecated - public MediaRequest(@Nullable Resource resource, @Nullable String mediaRef, @Nullable MediaArgs mediaArgs, - @Nullable String refProperty, @Nullable String cropProperty) { - this(resource, mediaRef, mediaArgs, new MediaPropertyNames() - .refProperty(refProperty) - .cropProperty(cropProperty)); - } - - /** - * @param resource Resource containing reference to media asset - * @param mediaRef Reference to media item - * @param mediaArgs Additional arguments affection media resolving - * @param refProperty Name of the property from which the media reference is read - * @param cropProperty Name of the property which contains the cropping parameters - * @param rotationProperty Name of the property which contains the rotation parameter - * @deprecated Use {@link #MediaRequest(Resource, String, MediaArgs, MediaPropertyNames)} - */ - @Deprecated - public MediaRequest(@Nullable Resource resource, @Nullable String mediaRef, @Nullable MediaArgs mediaArgs, - @Nullable String refProperty, @Nullable String cropProperty, @Nullable String rotationProperty) { - this(resource, mediaRef, mediaArgs, new MediaPropertyNames() - .refProperty(refProperty) - .cropProperty(cropProperty) - .rotationProperty(rotationProperty)); + this(null, mediaRef, mediaArgs, null); } /** @@ -136,33 +102,6 @@ public MediaRequest(@Nullable Resource resource, @Nullable String mediaRef, @Nul return this.mediaPropertyNames; } - /** - * @return Name of the property from which the media reference is read - * @deprecated Please use {@link #getMediaPropertyNames()}. - */ - @Deprecated - public @Nullable String getRefProperty() { - return this.mediaPropertyNames.getRefProperty(); - } - - /** - * @return Name of the property which contains the cropping parameters - * @deprecated Please use {@link #getMediaPropertyNames()}. - */ - @Deprecated - public @Nullable String getCropProperty() { - return this.mediaPropertyNames.getCropProperty(); - } - - /** - * @return Name of the property which contains the rotation parameter - * @deprecated Please use {@link #getMediaPropertyNames()}. - */ - @Deprecated - public @Nullable String getRotationProperty() { - return this.mediaPropertyNames.getRotationProperty(); - } - /** * @return Properties from resource containing target link. The value map is a copy * of the original map so it is safe to change the property values contained in the map. diff --git a/src/main/java/io/wcm/handler/media/Rendition.java b/src/main/java/io/wcm/handler/media/Rendition.java index 4ee87a68..6f6d665d 100644 --- a/src/main/java/io/wcm/handler/media/Rendition.java +++ b/src/main/java/io/wcm/handler/media/Rendition.java @@ -115,14 +115,6 @@ public interface Rendition extends Adaptable, ModificationDateProvider { @JsonIgnore boolean isVectorImage(); - /** - * @return true if the rendition has a flash movie. - * @deprecated Flash support is deprecated - */ - @Deprecated - @JsonIgnore - boolean isFlash(); - /** * @return true if the rendition is not and image nor a flash movie. */ @@ -168,4 +160,18 @@ default double getRatio() { * rendition is returned that fulfills all other media format restrictions, this flag is set to true. */ boolean isFallback(); + + /** + * Generate an URI template for the rendition. + * The URI template ignores the actual resolution of this rendition and allows to scale the rendition + * to any size within the maximum range of width/height, keeping the aspect ratio and respecting + * both the original image and probably configured cropping parameters. + * @param type URI template type. It is not supported to use {@link UriTemplateType#CROP_CENTER}. + * @return URI template + * @throws IllegalArgumentException if {@link UriTemplateType#CROP_CENTER} is used + * @throws UnsupportedOperationException if the original rendition is not an image or it is a vector image. + */ + @NotNull + UriTemplate getUriTemplate(@NotNull UriTemplateType type); + } diff --git a/src/main/java/io/wcm/handler/media/format/MediaFormat.java b/src/main/java/io/wcm/handler/media/format/MediaFormat.java index 30200375..58949b42 100644 --- a/src/main/java/io/wcm/handler/media/format/MediaFormat.java +++ b/src/main/java/io/wcm/handler/media/format/MediaFormat.java @@ -175,26 +175,6 @@ public long getMinWidthHeight() { return this.minWidthHeight; } - /** - * @return Ration width (px) - * @deprecated Use {@link #getRatioWidthAsDouble()} - */ - @Deprecated - @JsonIgnore - public long getRatioWidth() { - return Math.round(this.ratioWidth); - } - - /** - * @return Ration height (px) - * @deprecated Use {@link #getRatioHeightAsDouble()} - */ - @Deprecated - @JsonIgnore - public long getRatioHeight() { - return Math.round(this.ratioHeight); - } - /** * @return Ration width (px) */ @@ -213,7 +193,7 @@ public double getRatioHeightAsDouble() { /** * Returns the ratio defined in the media format definition. - * If no ratio is defined an the media format has a fixed with/height it is calculated automatically. + * If no ratio is defined an the media format has a fixed width/height it is calculated automatically. * Otherwise 0 is returned. * @return Ratio */ @@ -505,7 +485,7 @@ String getCombinedTitle() { List extParts = new ArrayList<>(); - // with/height restrictions + // width/height restrictions if (minWidthHeight != 0) { extParts.add("min. " + minWidthHeight + "px width/height"); } diff --git a/src/main/java/io/wcm/handler/media/format/ResponsiveMediaFormatsBuilder.java b/src/main/java/io/wcm/handler/media/format/ResponsiveMediaFormatsBuilder.java deleted file mode 100644 index 4683aa31..00000000 --- a/src/main/java/io/wcm/handler/media/format/ResponsiveMediaFormatsBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * #%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.media.format; - -import java.util.ArrayList; -import java.util.List; - -import org.jetbrains.annotations.NotNull; -import org.osgi.annotation.versioning.ProviderType; - -import io.wcm.handler.media.MediaNameConstants; - -/** - * Special builder that supports generating a on-the-fly list of media formats derived from a main - * media format with same ratio but different sizes for different breakpoints. - *

- * The main media format should not have a fixed dimension defined, but only a ratio and probably min. width and height. - * The resulting media formats are only generated on-the-fly for the media resolution process. On each format a - * {@link MediaNameConstants#PROP_BREAKPOINT} breakpoint is set that is used by the - * {@link io.wcm.handler.media.markup.ResponsiveImageMediaMarkupBuilder}. - *

- * @deprecated Use responsive image handling methods for image sizes and picture sources from MediaBuilder - * together with {@link io.wcm.handler.media.markup.SimpleImageMediaMarkupBuilder}. - */ -@ProviderType -@Deprecated -public final class ResponsiveMediaFormatsBuilder { - - private final MediaFormat mainMediaFormat; - private final List mediaFormats = new ArrayList<>(); - - /** - * @param mainMediaFormat Main media format from which the reponsive "on-the-fly" formats are derived from. - */ - public ResponsiveMediaFormatsBuilder(@NotNull MediaFormat mainMediaFormat) { - this.mainMediaFormat = mainMediaFormat; - } - - /** - * Defines one breakpoint for each "on-the-fly" format required. - * @param breakpoint Breakpoint name which is set in the {@link MediaNameConstants#PROP_BREAKPOINT} property. - * @param width Width for the breakpoint - * @param height Height for the breakpoint - * @return this - */ - public ResponsiveMediaFormatsBuilder breakpoint(@NotNull String breakpoint, int width, int height) { - mediaFormats.add(MediaFormatBuilder.create(buildCombinedName(mainMediaFormat, breakpoint, width, height)) - .label(mainMediaFormat.getLabel()) - .extensions(mainMediaFormat.getExtensions()) - .ratio(mainMediaFormat.getRatio()) - .fixedDimension(width, height) - .property(MediaNameConstants.PROP_BREAKPOINT, breakpoint) - .build()); - return this; - } - - /** - * Builds an array of media formats that can be used as for - * {@link io.wcm.handler.media.MediaBuilder#mandatoryMediaFormats(MediaFormat...)}. - * @return Array of on-the-fly media formats - */ - public MediaFormat[] build() { - return mediaFormats.toArray(new MediaFormat[0]); - } - - static @NotNull String buildCombinedName(MediaFormat mediaFormat, String breakpoint, int width, int height) { - return mediaFormat.getName() + "_" + breakpoint + "_" + width + "_" + height; - } - -} diff --git a/src/main/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProvider.java b/src/main/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProvider.java index 70404412..066e1568 100644 --- a/src/main/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProvider.java +++ b/src/main/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProvider.java @@ -20,6 +20,9 @@ package io.wcm.handler.media.format.impl; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Set; import javax.servlet.Servlet; @@ -29,13 +32,13 @@ import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.HttpConstants; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; -import org.apache.sling.commons.json.JSONArray; -import org.apache.sling.commons.json.JSONException; -import org.apache.sling.commons.json.JSONObject; import org.jetbrains.annotations.NotNull; import org.osgi.service.component.annotations.Component; -import com.google.common.collect.ImmutableSet; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.format.MediaFormatHandler; @@ -51,47 +54,41 @@ "sling.servlet.resourceTypes=sling/servlet/default", "sling.servlet.methods=" + HttpConstants.METHOD_GET }) -@SuppressWarnings("deprecation") public final class DefaultMediaFormatListProvider extends SlingSafeMethodsServlet { private static final long serialVersionUID = 1L; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + @Override protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHttpServletResponse response) throws ServletException, IOException { - try { - - // get list of media formats for current medialib path - Set mediaFormats = getMediaFormats(request); - - response.setContentType(ContentType.JSON); - - JSONArray mediaFormatList = new JSONArray(); - - if (mediaFormats != null) { - for (MediaFormat mediaFormat : mediaFormats) { - if (!mediaFormat.isInternal()) { - JSONObject mediaFormatItem = new JSONObject(); - mediaFormatItem.put("name", mediaFormat.getName()); - mediaFormatItem.put("text", mediaFormat.toString()); - mediaFormatItem.put("width", mediaFormat.getWidth()); - mediaFormatItem.put("height", mediaFormat.getHeight()); - mediaFormatItem.put("widthMin", mediaFormat.getMinWidth()); - mediaFormatItem.put("heightMin", mediaFormat.getMinHeight()); - mediaFormatItem.put("widthHeightMin", mediaFormat.getMinWidthHeight()); - mediaFormatItem.put("isImage", mediaFormat.isImage()); - mediaFormatItem.put("ratio", mediaFormat.getRatio()); - mediaFormatItem.put("ratioWidth", mediaFormat.getRatioWidthAsDouble()); - mediaFormatItem.put("ratioHeight", mediaFormat.getRatioHeightAsDouble()); - mediaFormatItem.put("ratioDisplayString", mediaFormat.getRatioDisplayString()); - mediaFormatList.put(mediaFormatItem); - } + // get list of media formats for current medialib path + Set mediaFormats = getMediaFormats(request); + + List mediaFormatList = new ArrayList<>(); + if (mediaFormats != null) { + for (MediaFormat mediaFormat : mediaFormats) { + if (!mediaFormat.isInternal()) { + MediaFormatItem mediaFormatItem = new MediaFormatItem(); + mediaFormatItem.name = mediaFormat.getName(); + mediaFormatItem.text = mediaFormat.toString(); + mediaFormatItem.width = mediaFormat.getWidth(); + mediaFormatItem.height = mediaFormat.getHeight(); + mediaFormatItem.widthMin = mediaFormat.getMinWidth(); + mediaFormatItem.heightMin = mediaFormat.getMinHeight(); + mediaFormatItem.widthHeightMin = mediaFormat.getMinWidthHeight(); + mediaFormatItem.isImage = mediaFormat.isImage(); + mediaFormatItem.ratio = mediaFormat.getRatio(); + mediaFormatItem.ratioWidth = mediaFormat.getRatioWidthAsDouble(); + mediaFormatItem.ratioHeight = mediaFormat.getRatioHeightAsDouble(); + mediaFormatItem.ratioDisplayString = mediaFormat.getRatioDisplayString(); + mediaFormatList.add(mediaFormatItem); } } - - response.getWriter().write(mediaFormatList.toString()); - } - catch (JSONException ex) { - throw new ServletException(ex); } + + // serialize to JSON using Jackson + response.setContentType(ContentType.JSON); + response.getWriter().write(OBJECT_MAPPER.writeValueAsString(mediaFormatList)); } protected Set getMediaFormats(SlingHttpServletRequest request) { @@ -100,8 +97,74 @@ protected Set getMediaFormats(SlingHttpServletRequest request) { return mediaFormatHandler.getMediaFormats(); } else { - return ImmutableSet.of(); + return Collections.emptySet(); + } + } + + @JsonInclude(Include.NON_NULL) + static class MediaFormatItem { + private String name; + private String text; + private long width; + private long height; + private long widthMin; + private long heightMin; + private long widthHeightMin; + private boolean isImage; + private double ratio; + private double ratioWidth; + private double ratioHeight; + private String ratioDisplayString; + + public String getName() { + return this.name; + } + + public String getText() { + return this.text; + } + + public long getWidth() { + return this.width; + } + + public long getHeight() { + return this.height; + } + + public long getWidthMin() { + return this.widthMin; + } + + public long getHeightMin() { + return this.heightMin; + } + + public long getWidthHeightMin() { + return this.widthHeightMin; + } + + @JsonProperty("isImage") + public boolean isImage() { + return this.isImage; + } + + public double getRatio() { + return this.ratio; + } + + public double getRatioWidth() { + return this.ratioWidth; + } + + public double getRatioHeight() { + return this.ratioHeight; + } + + public String getRatioDisplayString() { + return this.ratioDisplayString; } + } } diff --git a/src/main/java/io/wcm/handler/media/format/impl/MediaFormatHandlerImpl.java b/src/main/java/io/wcm/handler/media/format/impl/MediaFormatHandlerImpl.java index 2dcbebbd..380e1d62 100644 --- a/src/main/java/io/wcm/handler/media/format/impl/MediaFormatHandlerImpl.java +++ b/src/main/java/io/wcm/handler/media/format/impl/MediaFormatHandlerImpl.java @@ -19,6 +19,7 @@ */ package io.wcm.handler.media.format.impl; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; @@ -34,8 +35,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.google.common.collect.ImmutableSortedSet; - import io.wcm.handler.media.MediaFileType; import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.format.MediaFormatHandler; @@ -106,7 +105,7 @@ public MediaFormat getMediaFormat(@NotNull String mediaFormatName) { public @NotNull SortedSet getMediaFormats(@NotNull Comparator comparator) { SortedSet set = new TreeSet<>(comparator); set.addAll(getMediaFormatsForCurrentResource()); - return ImmutableSortedSet.copyOf(set); + return Collections.unmodifiableSortedSet(set); } /** diff --git a/src/main/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImpl.java b/src/main/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImpl.java index 8bde2446..8aea53e7 100644 --- a/src/main/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImpl.java +++ b/src/main/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImpl.java @@ -24,7 +24,6 @@ import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -41,8 +40,8 @@ import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.format.MediaFormatProviderManager; @@ -67,7 +66,7 @@ public final class MediaFormatProviderManagerImpl implements MediaFormatProvider private BundleContext bundleContext; // cache resolving of media formats per combined cache key of context-aware services - private final Cache> cache = CacheBuilder.newBuilder() + private final Cache> cache = Caffeine.newBuilder() .expireAfterWrite(1, TimeUnit.HOURS) .build(); @@ -80,14 +79,9 @@ private void activate(BundleContext bc) { public SortedSet getMediaFormats(Resource contextResource) { ResolveAllResult result = serviceResolver.resolveAll(MediaFormatProvider.class, contextResource); String key = result.getCombinedKey(); - try { - return cache.get(key, () -> result.getServices() - .flatMap(provider -> provider.getMediaFormats().stream()) - .collect(Collectors.toCollection(TreeSet::new))); - } - catch (ExecutionException ex) { - throw new RuntimeException("Error accessing media format provider result cache.", ex); - } + return cache.get(key, theKey -> result.getServices() + .flatMap(provider -> provider.getMediaFormats().stream()) + .collect(Collectors.toCollection(TreeSet::new))); } @Override diff --git a/src/main/java/io/wcm/handler/media/format/impl/MediaFormatSupport.java b/src/main/java/io/wcm/handler/media/format/impl/MediaFormatSupport.java index c60a5be0..5cadfb58 100644 --- a/src/main/java/io/wcm/handler/media/format/impl/MediaFormatSupport.java +++ b/src/main/java/io/wcm/handler/media/format/impl/MediaFormatSupport.java @@ -21,14 +21,13 @@ import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; +import org.apache.commons.collections4.SetUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; - import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.format.MediaFormat; @@ -53,7 +52,7 @@ private MediaFormatSupport() { // get file extension defined in media args Set mediaArgsFileExtensions = new HashSet<>(); if (mediaArgs.getFileExtensions() != null && mediaArgs.getFileExtensions().length > 0) { - mediaArgsFileExtensions.addAll(ImmutableList.copyOf(mediaArgs.getFileExtensions())); + mediaArgsFileExtensions.addAll(List.of(mediaArgs.getFileExtensions())); } // get file extensions from media formats @@ -62,7 +61,7 @@ private MediaFormatSupport() { @Override public @Nullable Object visit(@NotNull MediaFormat mediaFormat) { if (mediaFormat.getExtensions() != null && mediaFormat.getExtensions().length > 0) { - mediaFormatFileExtensions.addAll(ImmutableList.copyOf(mediaFormat.getExtensions())); + mediaFormatFileExtensions.addAll(List.of(mediaFormat.getExtensions())); } return null; } @@ -71,9 +70,9 @@ private MediaFormatSupport() { // if extensions are defined both in mediaargs and media formats use intersection of both final String[] fileExtensions; if (!mediaArgsFileExtensions.isEmpty() && !mediaFormatFileExtensions.isEmpty()) { - Collection intersection = Sets.intersection(mediaArgsFileExtensions, mediaFormatFileExtensions); + Collection intersection = SetUtils.intersection(mediaArgsFileExtensions, mediaFormatFileExtensions); if (intersection.isEmpty()) { - // not intersected file extensions - return null to singal no valid file extension request + // not intersected file extensions - return null to signal no valid file extension request fileExtensions = null; } else { diff --git a/src/main/java/io/wcm/handler/media/format/package-info.java b/src/main/java/io/wcm/handler/media/format/package-info.java index 6da24b5f..4b55d3b4 100644 --- a/src/main/java/io/wcm/handler/media/format/package-info.java +++ b/src/main/java/io/wcm/handler/media/format/package-info.java @@ -20,5 +20,5 @@ /** * Media format management. */ -@org.osgi.annotation.versioning.Version("1.4.0") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.media.format; diff --git a/src/main/java/io/wcm/handler/media/impl/AbstractMediaFileServlet.java b/src/main/java/io/wcm/handler/media/impl/AbstractMediaFileServlet.java index 725370e9..0c8e7535 100644 --- a/src/main/java/io/wcm/handler/media/impl/AbstractMediaFileServlet.java +++ b/src/main/java/io/wcm/handler/media/impl/AbstractMediaFileServlet.java @@ -101,7 +101,7 @@ protected Resource getBinaryDataResource(SlingHttpServletRequest request) { * @return true if the resource is not modified and should not be delivered anew */ protected boolean isNotModified(Resource resource, SlingHttpServletRequest request, - SlingHttpServletResponse response) throws IOException { + SlingHttpServletResponse response) { // check resource's modification date against the If-Modified-Since header and send 304 if resource wasn't modified // never send expires header on author or publish instance (performance optimization - if medialib items changes // users have to refresh browsers cache) diff --git a/src/main/java/io/wcm/handler/media/impl/DummyImageServlet.java b/src/main/java/io/wcm/handler/media/impl/DummyImageServlet.java index 11cd9238..b9bc22f5 100644 --- a/src/main/java/io/wcm/handler/media/impl/DummyImageServlet.java +++ b/src/main/java/io/wcm/handler/media/impl/DummyImageServlet.java @@ -80,7 +80,7 @@ protected Layer createLayer(ImageContext ctx) throws RepositoryException, IOExce int height = parser.get(SUFFIX_HEIGHT, 0); String name = parser.get(SUFFIX_MEDIA_FORMAT_NAME, String.class); - // validate with/height + // validate width/height if (width < 1 || height < 1) { return new Layer(1, 1, null); } diff --git a/src/main/java/io/wcm/handler/media/impl/ImageFileServlet.java b/src/main/java/io/wcm/handler/media/impl/ImageFileServlet.java index 56da9e6a..972e220d 100644 --- a/src/main/java/io/wcm/handler/media/impl/ImageFileServlet.java +++ b/src/main/java/io/wcm/handler/media/impl/ImageFileServlet.java @@ -32,13 +32,13 @@ import org.apache.sling.api.resource.Resource; import org.apache.sling.api.servlets.HttpConstants; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import com.day.cq.commons.jcr.JcrConstants; import com.day.cq.dam.api.handler.store.AssetStore; import com.day.image.Layer; -import com.drew.lang.annotations.Nullable; import io.wcm.handler.media.CropDimension; import io.wcm.handler.media.format.Ratio; @@ -116,12 +116,18 @@ protected byte[] getBinaryData(Resource resource, SlingHttpServletRequest reques } // if only width or only height is given - derive other value from ratio - double layerRatio = Ratio.get(layer.getWidth(), layer.getHeight()); + double originalRatio; + if (cropDimension != null) { + originalRatio = Ratio.get(cropDimension); + } + else { + originalRatio = Ratio.get(layer.getWidth(), layer.getHeight()); + } if (width == 0) { - width = (int)Math.round(height * layerRatio); + width = (int)Math.round(height * originalRatio); } else if (height == 0) { - height = (int)Math.round(width / layerRatio); + height = (int)Math.round(width / originalRatio); } // if required: crop image @@ -131,7 +137,7 @@ else if (height == 0) { else { // if image ratio that is requested does not match with the given ratio apply a center-crop here double requestedRatio = Ratio.get(width, height); - if (!Ratio.matches(layerRatio, requestedRatio)) { + if (!Ratio.matches(originalRatio, requestedRatio)) { cropDimension = ImageTransformation.calculateAutoCropDimension(layer.getWidth(), layer.getHeight(), requestedRatio); layer.crop(cropDimension.getRectangle()); } diff --git a/src/main/java/io/wcm/handler/media/impl/ImageTransformation.java b/src/main/java/io/wcm/handler/media/impl/ImageTransformation.java index 9a8bad49..5e1ae153 100644 --- a/src/main/java/io/wcm/handler/media/impl/ImageTransformation.java +++ b/src/main/java/io/wcm/handler/media/impl/ImageTransformation.java @@ -20,8 +20,7 @@ package io.wcm.handler.media.impl; import org.jetbrains.annotations.NotNull; - -import com.drew.lang.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import io.wcm.handler.media.CropDimension; import io.wcm.handler.media.Dimension; diff --git a/src/main/java/io/wcm/handler/media/impl/MediaBuilderImpl.java b/src/main/java/io/wcm/handler/media/impl/MediaBuilderImpl.java index 8aafc328..a3146344 100644 --- a/src/main/java/io/wcm/handler/media/impl/MediaBuilderImpl.java +++ b/src/main/java/io/wcm/handler/media/impl/MediaBuilderImpl.java @@ -61,7 +61,7 @@ final class MediaBuilderImpl implements MediaBuilder { private static final Logger log = LoggerFactory.getLogger(MediaBuilderImpl.class); MediaBuilderImpl(@Nullable Resource resource, @NotNull MediaHandlerImpl mediaHandler, - @Nullable ComponentPropertyResolverFactory componentPropertyResolverFactory) { + @NotNull ComponentPropertyResolverFactory componentPropertyResolverFactory) { this.resource = resource; this.mediaRef = null; this.mediaHandler = mediaHandler; @@ -76,8 +76,9 @@ final class MediaBuilderImpl implements MediaBuilder { * @param contextResource context resource * @param componentPropertyResolverFactory factory to create a component property resolver */ - private void resolveDefaultSettingsFromPolicyAndComponent(Resource contextResource, ComponentPropertyResolverFactory componentPropertyResolverFactory) { - try (MediaComponentPropertyResolver resolver = getMediaComponentPropertyResolver(contextResource, componentPropertyResolverFactory)) { + private void resolveDefaultSettingsFromPolicyAndComponent(@NotNull Resource contextResource, + @NotNull ComponentPropertyResolverFactory componentPropertyResolverFactory) { + try (MediaComponentPropertyResolver resolver = new MediaComponentPropertyResolver(contextResource, componentPropertyResolverFactory)) { mediaArgs.mediaFormatOptions(resolver.getMediaFormatOptions()); mediaArgs.autoCrop(resolver.isAutoCrop()); mediaArgs.imageSizes(resolver.getImageSizes()); @@ -88,20 +89,8 @@ private void resolveDefaultSettingsFromPolicyAndComponent(Resource contextResour } } - @SuppressWarnings("deprecation") - private static MediaComponentPropertyResolver getMediaComponentPropertyResolver(@NotNull Resource resource, - @Nullable ComponentPropertyResolverFactory componentPropertyResolverFactory) { - if (componentPropertyResolverFactory != null) { - return new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory); - } - else { - // fallback mode if ComponentPropertyResolverFactory is not available - return new MediaComponentPropertyResolver(resource); - } - } - MediaBuilderImpl(String mediaRef, Resource contextResource, MediaHandlerImpl mediaHandler, - @Nullable ComponentPropertyResolverFactory componentPropertyResolverFactory) { + @NotNull ComponentPropertyResolverFactory componentPropertyResolverFactory) { this.resource = contextResource; this.mediaRef = mediaRef; this.mediaHandler = mediaHandler; @@ -111,10 +100,6 @@ private static MediaComponentPropertyResolver getMediaComponentPropertyResolver( } } - MediaBuilderImpl(String mediaRef, MediaHandlerImpl mediaHandler) { - this(mediaRef, null, mediaHandler, null); - } - MediaBuilderImpl(MediaRequest mediaRequest, MediaHandlerImpl mediaHandler) { if (mediaRequest == null) { throw new IllegalArgumentException("Media request is null."); @@ -313,18 +298,6 @@ private static MediaComponentPropertyResolver getMediaComponentPropertyResolver( return this; } - @Override - public @NotNull MediaBuilder pictureSource(@NotNull MediaFormat mediaFormat, @NotNull String media, long @NotNull... widths) { - this.pictureSourceSets.add(new PictureSource(mediaFormat).media(media).widths(widths)); - return this; - } - - @Override - public @NotNull MediaBuilder pictureSource(@NotNull MediaFormat mediaFormat, long @NotNull... widths) { - this.pictureSourceSets.add(new PictureSource(mediaFormat).widths(widths)); - return this; - } - @Override public @NotNull MediaBuilder dynamicMediaDisabled(boolean value) { this.mediaArgs.dynamicMediaDisabled(value); @@ -356,6 +329,7 @@ private static MediaComponentPropertyResolver getMediaComponentPropertyResolver( } @Override + @SuppressWarnings("PMD.OptimizableToArrayCall") public @NotNull Media build() { if (!pictureSourceSets.isEmpty()) { this.mediaArgs.pictureSources(pictureSourceSets.toArray(new PictureSource[pictureSourceSets.size()])); diff --git a/src/main/java/io/wcm/handler/media/impl/MediaFormatValidateServlet.java b/src/main/java/io/wcm/handler/media/impl/MediaFormatValidateServlet.java index 1dc727eb..340e698b 100644 --- a/src/main/java/io/wcm/handler/media/impl/MediaFormatValidateServlet.java +++ b/src/main/java/io/wcm/handler/media/impl/MediaFormatValidateServlet.java @@ -34,8 +34,6 @@ import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.servlets.HttpConstants; import org.apache.sling.api.servlets.SlingSafeMethodsServlet; -import org.apache.sling.commons.json.JSONException; -import org.apache.sling.commons.json.JSONObject; import org.apache.sling.servlets.annotations.SlingServletResourceTypes; import org.jetbrains.annotations.NotNull; import org.osgi.service.component.annotations.Component; @@ -43,6 +41,9 @@ import com.day.cq.i18n.I18n; import com.day.cq.wcm.api.Page; import com.day.cq.wcm.api.PageManager; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaArgs.MediaFormatOption; @@ -63,7 +64,6 @@ selectors = MediaFormatValidateServlet.SELECTOR, resourceTypes = "sling/servlet/default", methods = HttpConstants.METHOD_GET) -@SuppressWarnings("deprecation") public final class MediaFormatValidateServlet extends SlingSafeMethodsServlet { private static final long serialVersionUID = 1L; @@ -74,6 +74,8 @@ public final class MediaFormatValidateServlet extends SlingSafeMethodsServlet { static final String RP_MEDIA_CROPAUTO = "mediaCropAuto"; static final String RP_MEDIA_REF = "mediaRef"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + /** * Prefix for i18n keys to generated messages for media invalid reasons. */ @@ -112,20 +114,15 @@ protected void doGet(@NotNull SlingHttpServletRequest request, @NotNull SlingHtt .build(); // response - try { - JSONObject result = new JSONObject(); - result.put("valid", media.isValid()); - if (!media.isValid()) { - I18n i18n = getI18n(request); - result.put("reason", getI18nText(i18n, getMediaInvalidReasonI18nKeyOrMessage(media))); - result.put("reasonTitle", getI18nText(i18n, ASSET_INVALID_I18N_KEY)); - } - response.setContentType(ContentType.JSON); - response.getWriter().write(result.toString()); - } - catch (JSONException ex) { - throw new ServletException(ex); + ResultResponse result = new ResultResponse(); + result.valid = media.isValid(); + if (!media.isValid()) { + I18n i18n = getI18n(request); + result.reason = getI18nText(i18n, getMediaInvalidReasonI18nKeyOrMessage(media)); + result.reasonTitle = getI18nText(i18n, ASSET_INVALID_I18N_KEY); } + response.setContentType(ContentType.JSON); + response.getWriter().write(OBJECT_MAPPER.writeValueAsString(result)); } private String getMediaInvalidReasonI18nKeyOrMessage(@NotNull Media media) { @@ -157,4 +154,25 @@ private I18n getI18n(SlingHttpServletRequest request) { return new I18n(request); } + @JsonInclude(Include.NON_NULL) + static class ResultResponse { + + private boolean valid; + private String reason; + private String reasonTitle; + + public boolean isValid() { + return this.valid; + } + + public String getReason() { + return this.reason; + } + + public String getReasonTitle() { + return this.reasonTitle; + } + + } + } diff --git a/src/main/java/io/wcm/handler/media/impl/MediaHandlerConfigAdapterFactory.java b/src/main/java/io/wcm/handler/media/impl/MediaHandlerAdapterFactory.java similarity index 73% rename from src/main/java/io/wcm/handler/media/impl/MediaHandlerConfigAdapterFactory.java rename to src/main/java/io/wcm/handler/media/impl/MediaHandlerAdapterFactory.java index 3a3b681e..fd38bd8a 100644 --- a/src/main/java/io/wcm/handler/media/impl/MediaHandlerConfigAdapterFactory.java +++ b/src/main/java/io/wcm/handler/media/impl/MediaHandlerAdapterFactory.java @@ -21,11 +21,14 @@ import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.adapter.AdapterFactory; +import org.apache.sling.api.resource.Resource; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; +import io.wcm.handler.media.MediaComponentPropertyResolver; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.sling.commons.caservice.ContextAwareServiceResolver; +import io.wcm.wcm.commons.component.ComponentPropertyResolverFactory; /** * Adapts resources or requests to {@link MediaHandlerConfig} via {@link ContextAwareServiceResolver}. @@ -34,12 +37,15 @@ property = { AdapterFactory.ADAPTABLE_CLASSES + "=org.apache.sling.api.resource.Resource", AdapterFactory.ADAPTABLE_CLASSES + "=org.apache.sling.api.SlingHttpServletRequest", - AdapterFactory.ADAPTER_CLASSES + "=io.wcm.handler.media.spi.MediaHandlerConfig" + AdapterFactory.ADAPTER_CLASSES + "=io.wcm.handler.media.spi.MediaHandlerConfig", + AdapterFactory.ADAPTER_CLASSES + "=io.wcm.handler.media.MediaComponentPropertyResolver" }) -public class MediaHandlerConfigAdapterFactory implements AdapterFactory { +public class MediaHandlerAdapterFactory implements AdapterFactory { @Reference private ContextAwareServiceResolver serviceResolver; + @Reference + private ComponentPropertyResolverFactory componentPropertyResolverFactory; @SuppressWarnings({ "unchecked", "null" }) @Override @@ -47,6 +53,9 @@ public AdapterType getAdapter(Object adaptable, Class if (type == MediaHandlerConfig.class) { return (AdapterType)serviceResolver.resolve(MediaHandlerConfig.class, (Adaptable)adaptable); } + if (type == MediaComponentPropertyResolver.class && adaptable instanceof Resource) { + return (AdapterType)new MediaComponentPropertyResolver((Resource)adaptable, componentPropertyResolverFactory); + } return null; } diff --git a/src/main/java/io/wcm/handler/media/impl/MediaHandlerImpl.java b/src/main/java/io/wcm/handler/media/impl/MediaHandlerImpl.java index 2def9b60..e3060a4c 100644 --- a/src/main/java/io/wcm/handler/media/impl/MediaHandlerImpl.java +++ b/src/main/java/io/wcm/handler/media/impl/MediaHandlerImpl.java @@ -26,7 +26,6 @@ import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.resource.Resource; import org.apache.sling.models.annotations.Model; -import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; import org.apache.sling.models.annotations.injectorspecific.OSGiService; import org.apache.sling.models.annotations.injectorspecific.Self; import org.jetbrains.annotations.NotNull; @@ -34,8 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableList; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.media.Media; @@ -67,7 +64,7 @@ public final class MediaHandlerImpl implements MediaHandler { private MediaHandlerConfig mediaHandlerConfig; @Self private MediaFormatHandler mediaFormatHandler; - @OSGiService(injectionStrategy = InjectionStrategy.OPTIONAL) + @OSGiService private ComponentPropertyResolverFactory componentPropertyResolverFactory; private static final Logger log = LoggerFactory.getLogger(MediaHandlerImpl.class); @@ -89,7 +86,7 @@ public final class MediaHandlerImpl implements MediaHandler { @Override public @NotNull MediaBuilder get(String mediaRef) { - return new MediaBuilderImpl(mediaRef, this); + return new MediaBuilderImpl(mediaRef, null, this, componentPropertyResolverFactory); } @Override @@ -191,14 +188,16 @@ else if (firstMediaSource == null) { // generate markup (if markup builder is available) - first accepting wins List> mediaMarkupBuilders = mediaHandlerConfig.getMarkupBuilders(); if (mediaMarkupBuilders != null) { - for (Class mediaMarkupBuilderClass : mediaMarkupBuilders) { - MediaMarkupBuilder mediaMarkupBuilder = AdaptTo.notNull(adaptable, mediaMarkupBuilderClass); - if (mediaMarkupBuilder.accepts(media)) { - log.trace("Apply media markup builder ({}): {}", mediaMarkupBuilderClass, mediaRequest); - media.setElement(mediaMarkupBuilder.build(media)); - break; + media.setElementBuilder(m -> { + for (Class mediaMarkupBuilderClass : mediaMarkupBuilders) { + MediaMarkupBuilder mediaMarkupBuilder = AdaptTo.notNull(adaptable, mediaMarkupBuilderClass); + if (mediaMarkupBuilder.accepts(m)) { + log.trace("Apply media markup builder ({}): {}", mediaMarkupBuilderClass, mediaRequest); + return mediaMarkupBuilder.build(m); + } } - } + return null; + }); } // postprocess media request after resolving @@ -266,7 +265,7 @@ private boolean resolveDownloadMediaFormats(MediaArgs mediaArgs) { List candidates = new ArrayList<>(); boolean fallbackToAllMediaFormats = false; if (mediaArgs.getMediaFormats() != null) { - candidates.addAll(ImmutableList.copyOf(mediaArgs.getMediaFormats())); + candidates.addAll(List.of(mediaArgs.getMediaFormats())); } else { candidates.addAll(mediaFormatHandler.getMediaFormats()); diff --git a/src/main/java/io/wcm/handler/media/impl/ResourceLayerUtil.java b/src/main/java/io/wcm/handler/media/impl/ResourceLayerUtil.java index 0002079c..551a3fa9 100644 --- a/src/main/java/io/wcm/handler/media/impl/ResourceLayerUtil.java +++ b/src/main/java/io/wcm/handler/media/impl/ResourceLayerUtil.java @@ -24,12 +24,12 @@ import org.apache.sling.api.resource.Resource; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.day.cq.dam.api.Rendition; import com.day.cq.dam.api.handler.AssetHandler; import com.day.cq.dam.api.handler.store.AssetStore; import com.day.image.Layer; -import com.drew.lang.annotations.Nullable; /** * Gets layer from rendition resource. diff --git a/src/main/java/io/wcm/handler/media/impl/ipeconfig/AspectRatioResource.java b/src/main/java/io/wcm/handler/media/impl/ipeconfig/AspectRatioResource.java index 67264883..5707cb2d 100644 --- a/src/main/java/io/wcm/handler/media/impl/ipeconfig/AspectRatioResource.java +++ b/src/main/java/io/wcm/handler/media/impl/ipeconfig/AspectRatioResource.java @@ -24,13 +24,11 @@ import org.apache.sling.api.resource.ResourceMetadata; import org.apache.sling.api.resource.ResourceResolver; 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.google.common.collect.ImmutableMap; - import io.wcm.handler.media.format.MediaFormat; +import io.wcm.sling.commons.resource.ImmutableValueMap; /** * Virtual resource returning name and ratio of media format. @@ -54,9 +52,9 @@ class AspectRatioResource extends AbstractResource { ratio = 1d / mediaFormat.getRatio(); } - this.properties = new ValueMapDecorator(ImmutableMap.of( + this.properties = ImmutableValueMap.of( "name", getDisplayString(mediaFormat), - "ratio", ratio)); + "ratio", ratio); } private static String getDisplayString(MediaFormat mf) { diff --git a/src/main/java/io/wcm/handler/media/markup/DummyResponsiveImageMediaMarkupBuilder.java b/src/main/java/io/wcm/handler/media/markup/DummyResponsiveImageMediaMarkupBuilder.java deleted file mode 100644 index b65cdb78..00000000 --- a/src/main/java/io/wcm/handler/media/markup/DummyResponsiveImageMediaMarkupBuilder.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * #%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.media.markup; - -import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.adapter.Adaptable; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.commons.json.JSONArray; -import org.apache.sling.commons.json.JSONException; -import org.apache.sling.commons.json.JSONObject; -import org.apache.sling.models.annotations.Model; -import org.apache.sling.models.annotations.injectorspecific.Self; -import org.jetbrains.annotations.NotNull; -import org.osgi.annotation.versioning.ConsumerType; - -import com.day.cq.wcm.api.WCMMode; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.wcm.handler.commons.dom.HtmlElement; -import io.wcm.handler.commons.dom.Image; -import io.wcm.handler.media.Media; -import io.wcm.handler.media.MediaArgs; -import io.wcm.handler.media.MediaNameConstants; -import io.wcm.handler.media.format.MediaFormat; -import io.wcm.handler.media.impl.DummyImageServlet; -import io.wcm.handler.media.spi.MediaHandlerConfig; -import io.wcm.handler.media.spi.MediaSource; -import io.wcm.handler.url.UrlHandler; -import io.wcm.handler.url.suffix.SuffixBuilder; -import io.wcm.sling.commons.adapter.AdaptTo; -import io.wcm.wcm.commons.contenttype.FileExtension; - -/** - * Generates a rendered dummy image as edit placeholder in WCM edit mode with information about image sizes - * and media format name. - * @deprecated Use responsive image handling methods for image sizes and picture sources from MediaBuilder. - */ -@Model(adaptables = { - SlingHttpServletRequest.class, Resource.class -}) -@ConsumerType -@Deprecated -public class DummyResponsiveImageMediaMarkupBuilder extends AbstractImageMediaMarkupBuilder { - - @Self - private Adaptable adaptable; - @Self - private UrlHandler urlHandler; - @Self - private MediaHandlerConfig mediaHandlerConfig; - - @Override - public final boolean accepts(@NotNull Media media) { - // accept if not rendition was found and in edit mode - // and multiple media formats are mandatory, and dummy image is not suppressed - MediaArgs mediaArgs = media.getMediaRequest().getMediaArgs(); - MediaFormat[] mediaFormats = mediaArgs.getMediaFormats(); - return (!media.isValid() || media.getRendition() == null) - && getWcmMode() != null - && getWcmMode() != WCMMode.DISABLED - && (mediaFormats != null && mediaFormats.length > 1) - && mediaArgs.isDummyImage() && mediaArgs.isMediaFormatsMandatory(); - } - - @SuppressWarnings({ "null", "unused" }) - @SuppressFBWarnings("STYLE") - @Override - public final HtmlElement build(@NotNull Media media) { - HtmlElement mediaElement = getImageElement(media); - - // set responsive image sources - JSONArray sources = getResponsiveImageSources(media); - setResponsiveImageSource(mediaElement, sources, media); - - // set additional attributes - setAdditionalAttributes(mediaElement, media); - - // enable drag&drop for media source - if none is specified use first one defined in config - MediaSource mediaSource = media.getMediaSource(); - if (mediaSource == null && !mediaHandlerConfig.getSources().isEmpty()) { - Class mediaSourceClass = mediaHandlerConfig.getSources().iterator().next(); - mediaSource = AdaptTo.notNull(adaptable, mediaSourceClass); - } - if (mediaSource != null) { - mediaSource.enableMediaDrop(mediaElement, media.getMediaRequest()); - } - - return mediaElement; - } - - /** - * Create an IMG element. - * @param media Media metadata - * @return IMG element with properties - */ - protected HtmlElement getImageElement(Media media) { - Image img = new Image().addCssClass(MediaNameConstants.CSS_DUMMYIMAGE); - return img; - } - - /** - * Collect responsive JSON metadata for all renditions as image sources. - * @param media Media - * @return JSON metadata - */ - protected JSONArray getResponsiveImageSources(Media media) { - MediaFormat[] mediaFormats = media.getMediaRequest().getMediaArgs().getMediaFormats(); - JSONArray sources = new JSONArray(); - for (MediaFormat mediaFormat : mediaFormats) { - sources.put(toReponsiveImageSource(media, mediaFormat)); - } - return sources; - } - - /** - * Build JSON metadata for one rendition as image source. - * @param media Media - * @param mediaFormat Media format - * @return JSON metadata - */ - protected JSONObject toReponsiveImageSource(Media media, MediaFormat mediaFormat) { - String url = buildDummyImageUrl(mediaFormat); - try { - JSONObject source = new JSONObject(); - source.put(MediaNameConstants.PROP_BREAKPOINT, mediaFormat.getProperties().get(MediaNameConstants.PROP_BREAKPOINT)); - source.put(ResponsiveImageMediaMarkupBuilder.PROP_SRC, url); - return source; - } - catch (JSONException ex) { - throw new RuntimeException("Error building JSON source.", ex); - } - } - - /** - * Build Dummy/Placeholder image URL - * @param format Media format - * @return Dummy image URL - */ - protected final String buildDummyImageUrl(MediaFormat format) { - String suffix = new SuffixBuilder() - .put(DummyImageServlet.SUFFIX_WIDTH, format.getWidth()) - .put(DummyImageServlet.SUFFIX_HEIGHT, format.getHeight()) - .put(DummyImageServlet.SUFFIX_MEDIA_FORMAT_NAME, format.getLabel()) - .build(); - return urlHandler.get(DummyImageServlet.PATH) - .extension(FileExtension.PNG) - .suffix(suffix) - .build(); - } - - /** - * Set attribute on media element for responsive image sources - * @param mediaElement Media element - * @param responsiveImageSources Responsive image sources JSON metadata - * @param media Media - */ - protected void setResponsiveImageSource(HtmlElement mediaElement, JSONArray responsiveImageSources, Media media) { - mediaElement.setData(ResponsiveImageMediaMarkupBuilder.PROP_RESPONSIVE_SOURCES, responsiveImageSources.toString()); - } - - @Override - public final boolean isValidMedia(@NotNull HtmlElement element) { - return false; - } - -} diff --git a/src/main/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtil.java b/src/main/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtil.java index 520702a5..5031ded1 100644 --- a/src/main/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtil.java +++ b/src/main/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtil.java @@ -19,6 +19,8 @@ */ package io.wcm.handler.media.markup; +import java.util.Set; + import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; @@ -54,24 +56,13 @@ @ProviderType public final class MediaMarkupBuilderUtil { - private MediaMarkupBuilderUtil() { - // static methods only - } - /** - * Adds CSS classes that denote the changes to the media element when compared to a different version. - * If no diff has been requested by the WCM UI, there won't be any changes to the element. - * @param mediaElement Element to be decorated - * @param resource Resource pointing to JCR node - * @param refProperty Name of property for media library item reference. If null, default name is used. - * @param request Servlet request - * @deprecated Use - * {@link #addDiffDecoration(HtmlElement, Resource, String, SlingHttpServletRequest, MediaHandlerConfig)} + * List of OOTB IPE editor types for images. */ - @Deprecated - public static void addDiffDecoration(@NotNull HtmlElement mediaElement, @NotNull Resource resource, - @NotNull String refProperty, @NotNull SlingHttpServletRequest request) { - addDiffDecoration(mediaElement, resource, refProperty, request, null); + public static final Set DEFAULT_ALLOWED_IPE_EDITOR_TYPES = Set.of("image"); + + private MediaMarkupBuilderUtil() { + // static methods only } /** @@ -159,7 +150,7 @@ else if (StringUtils.isEmpty(oldMediaRef)) { long width = mediaArgs.getFixedWidth(); long height = mediaArgs.getFixedHeight(); if ((width == 0 || height == 0) && mediaFormats != null && mediaFormats.length > 0) { - MediaFormat firstMediaFormat = mediaArgs.getMediaFormats()[0]; + MediaFormat firstMediaFormat = mediaFormats[0]; Dimension dimension = firstMediaFormat.getMinDimension(); if (dimension != null) { width = dimension.getWidth(); @@ -217,6 +208,18 @@ public static boolean canApplyDragDropSupport(@NotNull MediaRequest mediaRequest */ public static boolean canSetCustomIPECropRatios(@NotNull MediaRequest mediaRequest, @Nullable ComponentContext wcmComponentContext) { + return canSetCustomIPECropRatios(mediaRequest, wcmComponentContext, DEFAULT_ALLOWED_IPE_EDITOR_TYPES); + } + + /** + * Implements check whether to set customized IPE cropping ratios as described in {@link IPERatioCustomize}. + * @param mediaRequest Media request + * @param wcmComponentContext WCM component context + * @param allowedIpeEditorTypes Allowed editor types for image IPE (in-place editor). + * @return true if customized IP cropping ratios can be set + */ + public static boolean canSetCustomIPECropRatios(@NotNull MediaRequest mediaRequest, + @Nullable ComponentContext wcmComponentContext, @NotNull Set allowedIpeEditorTypes) { EditConfig editConfig = null; InplaceEditingConfig ipeConfig = null; @@ -227,7 +230,7 @@ public static boolean canSetCustomIPECropRatios(@NotNull MediaRequest mediaReque ipeConfig = editConfig.getInplaceEditingConfig(); } if (editConfig == null || ipeConfig == null - || !StringUtils.equals(ipeConfig.getEditorType(), "image")) { + || !allowedIpeEditorTypes.contains(ipeConfig.getEditorType())) { // no image IPE activated - never customize crop ratios return false; } diff --git a/src/main/java/io/wcm/handler/media/markup/ResponsiveImageMediaMarkupBuilder.java b/src/main/java/io/wcm/handler/media/markup/ResponsiveImageMediaMarkupBuilder.java deleted file mode 100644 index 5e00f11d..00000000 --- a/src/main/java/io/wcm/handler/media/markup/ResponsiveImageMediaMarkupBuilder.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * #%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.media.markup; - -import java.util.Collection; - -import org.apache.commons.lang3.StringUtils; -import org.apache.sling.api.SlingHttpServletRequest; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.commons.json.JSONArray; -import org.apache.sling.commons.json.JSONException; -import org.apache.sling.commons.json.JSONObject; -import org.apache.sling.models.annotations.Model; -import org.jetbrains.annotations.NotNull; -import org.osgi.annotation.versioning.ConsumerType; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.wcm.handler.commons.dom.HtmlElement; -import io.wcm.handler.commons.dom.Image; -import io.wcm.handler.media.Asset; -import io.wcm.handler.media.Media; -import io.wcm.handler.media.MediaNameConstants; -import io.wcm.handler.media.Rendition; -import io.wcm.handler.media.format.MediaFormat; - -/** - * Builds image element with data attribute containing sources for responsive image. - * This builder uses non-HTML5 standard markup and is therefore deprecated. - * @deprecated Use responsive image handling methods for image sizes and picture sources from MediaBuilder - * together with {@link SimpleImageMediaMarkupBuilder}. - */ -@Model(adaptables = { - SlingHttpServletRequest.class, Resource.class -}) -@ConsumerType -@Deprecated -public class ResponsiveImageMediaMarkupBuilder extends AbstractImageMediaMarkupBuilder { - - /** - * Data attribute name for responsive image sources. - */ - static final String PROP_RESPONSIVE_SOURCES = "resp-src"; - - /** - * Property name for image URL - */ - static final String PROP_SRC = "src"; - - @Override - public final boolean accepts(@NotNull Media media) { - return media.getMediaRequest().getMediaArgs().isMediaFormatsMandatory() - && media.getRendition() != null - && media.getRenditions().size() > 1 - && media.isValid(); - } - - @Override - public final HtmlElement build(@NotNull Media media) { - HtmlElement mediaElement = getImageElement(media); - - // set responsive image sources - JSONArray sources = getResponsiveImageSources(media); - setResponsiveImageSource(mediaElement, sources, media); - - // set additional attributes - setAdditionalAttributes(mediaElement, media); - - // further processing in edit or preview mode - applyWcmMarkup(mediaElement, media); - - return mediaElement; - } - - /** - * Create an IMG element with alt text. - * @param media Media metadata - * @return IMG element with properties - */ - protected HtmlElement getImageElement(Media media) { - Image img = new Image(); - - // Alternative text - Asset asset = media.getAsset(); - String altText = null; - if (asset != null) { - altText = asset.getAltText(); - } - if (StringUtils.isNotEmpty(altText)) { - img.setAlt(altText); - } - - return img; - } - - /** - * Collect responsive JSON metadata for all renditions as image sources. - * @param media Media - * @return JSON metadata - */ - protected JSONArray getResponsiveImageSources(Media media) { - Collection renditions = media.getRenditions(); - JSONArray sources = new JSONArray(); - for (Rendition rendition : renditions) { - sources.put(toReponsiveImageSource(media, rendition)); - } - return sources; - } - - /** - * Build JSON metadata for one rendition as image source. - * @param media Media - * @param rendition Rendition - * @return JSON metadata - */ - @SuppressWarnings("null") - @SuppressFBWarnings("STYLE") - protected JSONObject toReponsiveImageSource(Media media, Rendition rendition) { - try { - JSONObject source = new JSONObject(); - MediaFormat mediaFormat = rendition.getMediaFormat(); - source.put(MediaNameConstants.PROP_BREAKPOINT, mediaFormat.getProperties().get(MediaNameConstants.PROP_BREAKPOINT)); - source.put(PROP_SRC, rendition.getUrl()); - return source; - } - catch (JSONException ex) { - throw new RuntimeException("Error building JSON source.", ex); - } - } - - /** - * Set attribute on media element for responsive image sources - * @param mediaElement Media element - * @param responsiveImageSources Responsive image sources JSON metadata - * @param media Media - */ - protected void setResponsiveImageSource(HtmlElement mediaElement, JSONArray responsiveImageSources, Media media) { - mediaElement.setData(PROP_RESPONSIVE_SOURCES, responsiveImageSources.toString()); - } - - @Override - public final boolean isValidMedia(@NotNull HtmlElement element) { - if (element instanceof Image) { - Image img = (Image)element; - // if it's a responsive image, we don't need the src attribute set - return imageSourceIsNotEmpty(img) - && !StringUtils.contains(img.getCssClass(), MediaNameConstants.CSS_DUMMYIMAGE); - } - return false; - } - - private boolean imageSourceIsNotEmpty(Image img) { - String imageSources = img.getData(PROP_RESPONSIVE_SOURCES); - return StringUtils.isNotBlank(imageSources); - } - -} diff --git a/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java b/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java index 2435842e..4b047c06 100644 --- a/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java +++ b/src/main/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilder.java @@ -34,8 +34,6 @@ import org.jetbrains.annotations.Nullable; import org.osgi.annotation.versioning.ConsumerType; -import com.google.common.collect.ImmutableList; - import io.wcm.handler.commons.dom.Area; import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.commons.dom.Image; @@ -144,7 +142,7 @@ public final HtmlElement build(@NotNull Media media) { if (foundAnySource) { if (image instanceof Span) { // if image was wrapped in span, add content of span element, not the span itself - for (Element element : ImmutableList.copyOf(image.getChildren())) { + for (Element element : List.copyOf(image.getChildren())) { element.detach(); picture.addContent(element); } diff --git a/src/main/java/io/wcm/handler/media/markup/package-info.java b/src/main/java/io/wcm/handler/media/markup/package-info.java index 18d04e27..cd21ed56 100644 --- a/src/main/java/io/wcm/handler/media/markup/package-info.java +++ b/src/main/java/io/wcm/handler/media/markup/package-info.java @@ -20,5 +20,5 @@ /** * Default media markup builder implementations. */ -@org.osgi.annotation.versioning.Version("1.5.1") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.media.markup; diff --git a/src/main/java/io/wcm/handler/media/package-info.java b/src/main/java/io/wcm/handler/media/package-info.java index 6a14732d..3dc7b07a 100644 --- a/src/main/java/io/wcm/handler/media/package-info.java +++ b/src/main/java/io/wcm/handler/media/package-info.java @@ -20,5 +20,5 @@ /** * Media Handler API. */ -@org.osgi.annotation.versioning.Version("1.17") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.media; diff --git a/src/main/java/io/wcm/handler/media/spi/MediaFormatProvider.java b/src/main/java/io/wcm/handler/media/spi/MediaFormatProvider.java index bd382e42..62832617 100644 --- a/src/main/java/io/wcm/handler/media/spi/MediaFormatProvider.java +++ b/src/main/java/io/wcm/handler/media/spi/MediaFormatProvider.java @@ -20,13 +20,12 @@ package io.wcm.handler.media.spi; import java.lang.reflect.Field; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.osgi.annotation.versioning.ConsumerType; -import com.google.common.collect.ImmutableSet; - import io.wcm.handler.media.format.MediaFormat; import io.wcm.sling.commons.caservice.ContextAwareService; @@ -79,7 +78,7 @@ private static Set getMediaFormatsFromPublicFields(Class type) { catch (IllegalArgumentException | IllegalAccessException ex) { throw new RuntimeException("Unable to access fields of " + type.getName(), ex); } - return ImmutableSet.copyOf(params); + return Collections.unmodifiableSet(params); } } diff --git a/src/main/java/io/wcm/handler/media/spi/MediaHandlerConfig.java b/src/main/java/io/wcm/handler/media/spi/MediaHandlerConfig.java index 21b278bf..22393d0d 100644 --- a/src/main/java/io/wcm/handler/media/spi/MediaHandlerConfig.java +++ b/src/main/java/io/wcm/handler/media/spi/MediaHandlerConfig.java @@ -19,17 +19,19 @@ */ package io.wcm.handler.media.spi; +import java.util.Collections; import java.util.List; +import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.osgi.annotation.versioning.ConsumerType; import com.day.cq.wcm.api.Page; -import com.google.common.collect.ImmutableList; import io.wcm.handler.media.MediaNameConstants; import io.wcm.handler.media.markup.DummyImageMediaMarkupBuilder; +import io.wcm.handler.media.markup.MediaMarkupBuilderUtil; import io.wcm.handler.media.markup.SimpleImageMediaMarkupBuilder; import io.wcm.handler.mediasource.dam.DamMediaSource; import io.wcm.sling.commons.caservice.ContextAwareService; @@ -47,10 +49,10 @@ public abstract class MediaHandlerConfig implements ContextAwareService { */ public static final double DEFAULT_JPEG_QUALITY = 0.98d; - private static final List> DEFAULT_MEDIA_SOURCES = ImmutableList.>of( + private static final List> DEFAULT_MEDIA_SOURCES = List.of( DamMediaSource.class); - private static final List> DEFAULT_MEDIA_MARKUP_BUILDERS = ImmutableList.>of( + private static final List> DEFAULT_MEDIA_MARKUP_BUILDERS = List.of( SimpleImageMediaMarkupBuilder.class, DummyImageMediaMarkupBuilder.class); @@ -73,7 +75,7 @@ public abstract class MediaHandlerConfig implements ContextAwareService { */ public @NotNull List> getPreProcessors() { // no processors - return ImmutableList.of(); + return Collections.emptyList(); } /** @@ -81,7 +83,7 @@ public abstract class MediaHandlerConfig implements ContextAwareService { */ public @NotNull List> getPostProcessors() { // no processors - return ImmutableList.of(); + return Collections.emptyList(); } /** @@ -247,6 +249,14 @@ public boolean enforceVirtualRenditions() { return false; } + /** + * @return Allowed editor types for image IPE (in-place editor). + * By default, only the OOTB "image" editor type is supported. + */ + public @NotNull Set allowedIpeEditorTypes() { + return MediaMarkupBuilderUtil.DEFAULT_ALLOWED_IPE_EDITOR_TYPES; + } + /** * Get root path for picking assets using path field widgets. * @param page Context page diff --git a/src/main/java/io/wcm/handler/media/spi/MediaSource.java b/src/main/java/io/wcm/handler/media/spi/MediaSource.java index 135f6fa0..37b8353a 100644 --- a/src/main/java/io/wcm/handler/media/spi/MediaSource.java +++ b/src/main/java/io/wcm/handler/media/spi/MediaSource.java @@ -38,8 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.collect.ImmutableList; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.media.Asset; @@ -135,17 +133,6 @@ public void setCustomIPECropRatios(@NotNull HtmlElement element, @NotNull Med // can be implemented by subclasses } - /** - * Get media request path to media library - * @param mediaRequest Media request - * @return Path or null if not present - * @deprecated Use {@link #getMediaRef(MediaRequest, MediaHandlerConfig)} - */ - @Deprecated - protected final @Nullable String getMediaRef(@NotNull MediaRequest mediaRequest) { - return getMediaRef(mediaRequest, null); - } - /** * Get media request path to media library * @param mediaRequest Media request @@ -168,17 +155,6 @@ else if (mediaRequest.getResource() != null) { } } - /** - * Get property name containing the media request path - * @param mediaRequest Media request - * @return Property name - * @deprecated Use {@link #getMediaRefProperty(MediaRequest, MediaHandlerConfig)} - */ - @Deprecated - protected final @Nullable String getMediaRefProperty(@NotNull MediaRequest mediaRequest) { - return getMediaRefProperty(mediaRequest, null); - } - /** * Get property name containing the media request path * @param mediaRequest Media request @@ -200,17 +176,6 @@ else if (mediaRequest.getResource() != null) { return refProperty; } - /** - * Get (optional) crop dimensions from resource - * @param mediaRequest Media request - * @return Crop dimension or null if not set or invalid - * @deprecated Use {@link #getMediaCropDimension(MediaRequest, MediaHandlerConfig)} - */ - @Deprecated - protected final @Nullable CropDimension getMediaCropDimension(@NotNull MediaRequest mediaRequest) { - return getMediaCropDimension(mediaRequest, null); - } - /** * Get (optional) crop dimensions from resource * @param mediaRequest Media request @@ -236,17 +201,6 @@ else if (mediaRequest.getResource() != null) { return null; } - /** - * Get property name containing the cropping parameters - * @param mediaRequest Media request - * @return Property name - * @deprecated Use {@link #getMediaCropProperty(MediaRequest, MediaHandlerConfig)} - */ - @Deprecated - protected final @NotNull String getMediaCropProperty(@NotNull MediaRequest mediaRequest) { - return getMediaCropProperty(mediaRequest, null); - } - /** * Get property name containing the cropping parameters * @param mediaRequest Media request @@ -382,7 +336,8 @@ protected final boolean resolveRenditions(Media media, Asset asset, MediaArgs me boolean anyMandatory = mediaArgs.getMediaFormatOptions() != null && Arrays.stream(mediaArgs.getMediaFormatOptions()) .anyMatch(MediaFormatOption::isMandatory); - if (mediaArgs.getMediaFormats() != null && mediaArgs.getMediaFormats().length > 1 + MediaFormat[] mediaFormats = mediaArgs.getMediaFormats(); + if (mediaFormats != null && mediaFormats.length > 1 && (anyMandatory || mediaArgs.getImageSizes() != null || mediaArgs.getPictureSources() != null)) { return resolveAllRenditions(media, asset, mediaArgs); } @@ -403,7 +358,7 @@ private boolean resolveFirstMatchRenditions(Media media, Asset asset, MediaArgs Rendition rendition = asset.getRendition(mediaArgs); boolean renditionFound = false; if (rendition != null) { - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); media.setUrl(rendition.getUrl()); renditionFound = true; } diff --git a/src/main/java/io/wcm/handler/media/spi/package-info.java b/src/main/java/io/wcm/handler/media/spi/package-info.java index 1891b307..8bcd15de 100644 --- a/src/main/java/io/wcm/handler/media/spi/package-info.java +++ b/src/main/java/io/wcm/handler/media/spi/package-info.java @@ -20,5 +20,5 @@ /** * SPI for configuring and tailoring media handler processing. */ -@org.osgi.annotation.versioning.Version("1.7") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.media.spi; diff --git a/src/main/java/io/wcm/handler/media/ui/package-info.java b/src/main/java/io/wcm/handler/media/ui/package-info.java index 3cab9852..40c7b11b 100644 --- a/src/main/java/io/wcm/handler/media/ui/package-info.java +++ b/src/main/java/io/wcm/handler/media/ui/package-info.java @@ -20,5 +20,5 @@ /** * Sling model classes for UI views. */ -@org.osgi.annotation.versioning.Version("1.1.0") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.media.ui; diff --git a/src/main/java/io/wcm/handler/mediasource/dam/AssetRendition.java b/src/main/java/io/wcm/handler/mediasource/dam/AssetRendition.java index 17fadec6..ddbeb91d 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/AssetRendition.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/AssetRendition.java @@ -94,7 +94,7 @@ private AssetRendition() { *

* @param rendition Rendition * @param suppressLogWarningNoRenditionsMetadata If set to true, no log warnings is generated when - * renditions metadata containing the with/height of the rendition does not exist (yet). + * renditions metadata containing the width/height of the rendition does not exist (yet). * @return Dimension or null if dimension could not be detected, not even in fallback mode */ public static @Nullable Dimension getDimension(@NotNull Rendition rendition, @@ -172,7 +172,7 @@ private static long getAssetMetadataValueAsLong(Asset asset, String... propertyN * Fallback: Read dimension by loading image binary into memory. * @param rendition Rendition * @param suppressLogWarningNoRenditionsMetadata If set to true, no log warnings is generated when - * renditions metadata containing the with/height of the rendition does not exist (yet). + * renditions metadata containing the width/height of the rendition does not exist (yet). * @return Dimension or null */ @SuppressWarnings("PMD.GuardLogStatement") @@ -202,7 +202,7 @@ private static long getAssetMetadataValueAsLong(Asset asset, String... propertyN } /** - * Convert with/height to dimension + * Convert width/height to dimension * @param width Width * @param height Height * @return Dimension or null if width or height are not valid diff --git a/src/main/java/io/wcm/handler/mediasource/dam/DamMediaSource.java b/src/main/java/io/wcm/handler/mediasource/dam/DamMediaSource.java index bfd68d90..e386d60e 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/DamMediaSource.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/DamMediaSource.java @@ -221,7 +221,7 @@ public void setCustomIPECropRatios(@NotNull HtmlElement element, @NotNull Med } if (componentContext != null - && MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)) { + && MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext, mediaHandlerConfig.allowedIpeEditorTypes())) { // overlay IPE config with cropping ratios for each media format with a valid ratio CroppingRatios croppingRatios = new CroppingRatios(mediaFormatHandler); Set mediaFormatNames = croppingRatios.getMediaFormatsForCropping(mediaRequest); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java index 1b56a6b3..b604565b 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAsset.java @@ -67,13 +67,28 @@ public final class DamAsset extends SlingAdaptable implements Asset { public DamAsset(Media media, com.day.cq.dam.api.Asset damAsset, MediaHandlerConfig mediaHandlerConfig, DynamicMediaSupportService dynamicMediaSupportService, Adaptable adaptable) { this.damAsset = damAsset; - this.cropDimension = media.getCropDimension(); + this.cropDimension = rescaleCropDimension(damAsset, media.getCropDimension()); this.rotation = media.getRotation(); this.defaultMediaArgs = media.getMediaRequest().getMediaArgs(); - this.damContext = new DamContext(damAsset, defaultMediaArgs.getUrlMode(), mediaHandlerConfig, + this.damContext = new DamContext(damAsset, defaultMediaArgs, mediaHandlerConfig, dynamicMediaSupportService, 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); @@ -158,18 +173,6 @@ public Rendition getImageRendition(@NotNull MediaArgs mediaArgs) { } } - @SuppressWarnings("deprecation") - @Override - public Rendition getFlashRendition(@NotNull MediaArgs mediaArgs) { - Rendition rendition = getRendition(mediaArgs); - if (rendition != null && rendition.isFlash()) { - return rendition; - } - else { - return null; - } - } - @Override public Rendition getDownloadRendition(@NotNull MediaArgs mediaArgs) { Rendition rendition = getRendition(mediaArgs); @@ -213,7 +216,7 @@ public AdapterType adaptTo(Class type) { if (dimension == null) { throw new IllegalArgumentException("Unable to get dimension for original rendition of asset: " + getPath()); } - return new DamUriTemplate(type, dimension, damContext, defaultMediaArgs); + return new DamUriTemplate(type, dimension, original, null, null, null, damContext); } @Override diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAutoCropping.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAutoCropping.java index cc3aeb27..e7e5bcb0 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAutoCropping.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamAutoCropping.java @@ -29,27 +29,32 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import com.day.cq.dam.api.Asset; - import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.Dimension; import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.impl.ImageTransformation; import io.wcm.handler.mediasource.dam.AssetRendition; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop; /** * Helper class for calculating crop dimensions for auto-cropping. */ class DamAutoCropping { - private final Asset asset; + private final DamContext damContext; private final MediaArgs mediaArgs; - DamAutoCropping(@NotNull Asset asset, @NotNull MediaArgs mediaArgs) { - this.asset = asset; + DamAutoCropping(@NotNull DamContext damContext, @NotNull MediaArgs mediaArgs) { + this.damContext = damContext; this.mediaArgs = mediaArgs; } + /** + * Get possible cropping dimension for all given media formats. + * @return List of matching cropping definitions + */ public List calculateAutoCropDimensions() { Stream mediaFormats = Arrays.stream( ObjectUtils.defaultIfNull(mediaArgs.getMediaFormats(), new MediaFormat[0])); @@ -59,31 +64,45 @@ public List calculateAutoCropDimensions() { .collect(Collectors.toList()); } - private CropDimension calculateAutoCropDimension(@NotNull MediaFormat mediaFormat) { + /** + * Get or calculate cropping dimension for given media format (if it has an actual ratio defined). + * @param mediaFormat Media format + * @return Cropping dimension or null if not found + */ + private @Nullable CropDimension calculateAutoCropDimension(@NotNull MediaFormat mediaFormat) { + CropDimension result = null; + double ratio = mediaFormat.getRatio(); if (ratio > 0) { - RenditionMetadata rendition = DamAutoCropping.getWebRenditionForCropping(asset); - if (rendition != null && rendition.getWidth() > 0 && rendition.getHeight() > 0) { - return ImageTransformation.calculateAutoCropDimension(rendition.getWidth(), rendition.getHeight(), ratio); + // first check is DM is enabled, and a fitting smart crop rendition for this aspect ratio is defined + result = getDynamicMediaCropDimension(ratio); + + // otherwise calculate auto-cropping dimension based on original image + if (result == null) { + Dimension dimension = AssetRendition.getDimension(damContext.getAsset().getOriginal()); + if (dimension != null && dimension.getWidth() > 0 && dimension.getHeight() > 0) { + result = ImageTransformation.calculateAutoCropDimension(dimension.getWidth(), dimension.getHeight(), ratio); + } } } - return null; + + return result; } /** - * Get web first rendition for asset. - * This is the same logic as implemented in - * /libs/cq/gui/components/authoring/editors/clientlibs/core/inlineediting/js/ImageEditor.js. - * @param asset Asset - * @return Web rendition or null if none found + * Try to get actual smart crop dimension for the requested ratio for the current asset to be used for auto-cropping. + * @param requestedRatio Requested ratio + * @return Cropping dimension or null if not found */ - @SuppressWarnings("null") - public static @Nullable RenditionMetadata getWebRenditionForCropping(@NotNull Asset asset) { - return asset.getRenditions().stream() - .filter(AssetRendition::isWebRendition) - .findFirst() - .map(rendition -> new RenditionMetadata(rendition)) - .orElse(null); + private @Nullable CropDimension getDynamicMediaCropDimension(double requestedRatio) { + if (damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset() + && damContext.isDynamicMediaValidateSmartCropRenditionSizes()) { + NamedDimension smartCropDef = SmartCrop.getDimensionForRatio(damContext.getImageProfile(), requestedRatio); + if (smartCropDef != null) { + return SmartCrop.getCropDimensionForAsset(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef); + } + } + return null; } } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamContext.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamContext.java index 6b421103..e9ac08bb 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamContext.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamContext.java @@ -23,7 +23,10 @@ import java.util.List; import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.adapter.Adaptable; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,11 +34,11 @@ import com.day.cq.dam.scene7.api.constants.Scene7Constants; import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportService; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfile; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension; -import io.wcm.handler.url.UrlMode; /** * Context objects require in DAM support implementation. @@ -43,7 +46,7 @@ public final class DamContext implements Adaptable { private final Asset asset; - private final UrlMode urlMode; + private final MediaArgs mediaArgs; private final MediaHandlerConfig mediaHandlerConfig; private final DynamicMediaSupportService dynamicMediaSupportService; private final Adaptable adaptable; @@ -62,15 +65,15 @@ public final class DamContext implements Adaptable { /** * @param asset DAM asset - * @param urlMode urlMode + * @param mediaArgs Media Args from media request * @param mediaHandlerConfig Media handler config * @param dynamicMediaSupportService Dynamic media support service * @param adaptable Adaptable from current context */ - public DamContext(@NotNull Asset asset, @Nullable UrlMode urlMode, @NotNull MediaHandlerConfig mediaHandlerConfig, + public DamContext(@NotNull Asset asset, @NotNull MediaArgs mediaArgs, @NotNull MediaHandlerConfig mediaHandlerConfig, @NotNull DynamicMediaSupportService dynamicMediaSupportService, @NotNull Adaptable adaptable) { this.asset = asset; - this.urlMode = urlMode; + this.mediaArgs = mediaArgs; this.mediaHandlerConfig = mediaHandlerConfig; this.dynamicMediaSupportService = dynamicMediaSupportService; this.adaptable = adaptable; @@ -83,6 +86,13 @@ public Asset getAsset() { return asset; } + /** + * @return Media Args from media request + */ + public MediaArgs getMediaArgs() { + return mediaArgs; + } + /** * @return Media handler config */ @@ -94,7 +104,12 @@ public MediaHandlerConfig getMediaHandlerConfig() { * @return Whether dynamic media is enabled on this AEM instance */ public boolean isDynamicMediaEnabled() { - return dynamicMediaSupportService.isDynamicMediaEnabled(); + // check that DM is not disabled globally + return dynamicMediaSupportService.isDynamicMediaEnabled() + // check that DM capability is enabled for the given asset + && dynamicMediaSupportService.isDynamicMediaCapabilityEnabled(isDynamicMediaAsset()) + // ensure DM is not disabled within MediaArgs for this media request + && !mediaArgs.isDynamicMediaDisabled(); } /** @@ -127,11 +142,19 @@ public boolean isDynamicMediaAsset() { */ public @Nullable String getDynamicMediaServerUrl() { if (dynamicMediaServerUrl == null) { - dynamicMediaServerUrl = dynamicMediaSupportService.getDynamicMediaServerUrl(asset, urlMode); + dynamicMediaServerUrl = dynamicMediaSupportService.getDynamicMediaServerUrl(asset, mediaArgs.getUrlMode(), adaptable); } return dynamicMediaServerUrl; } + /** + * @return Whether to validate that the renditions defined via smart cropping fulfill the requested image width/height + * to avoid upscaling or white borders. + */ + public boolean isDynamicMediaValidateSmartCropRenditionSizes() { + return dynamicMediaSupportService.isValidateSmartCropRenditionSizes(); + } + /** * @return Dynamic media reply image size limit */ @@ -161,6 +184,21 @@ public boolean isDynamicMediaAsset() { } } + /** + * @return Resource resolver from current context + */ + public @NotNull ResourceResolver getResourceResolver() { + if (adaptable instanceof Resource) { + return ((Resource)adaptable).getResourceResolver(); + } + else if (adaptable instanceof SlingHttpServletRequest) { + return ((SlingHttpServletRequest)adaptable).getResourceResolver(); + } + else { + throw new IllegalStateException("Adaptable is neither Resoucre nor SlingHttpServletRequest"); + } + } + @Override public @Nullable AdapterType adaptTo(@NotNull Class type) { return adaptable.adaptTo(type); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRendition.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRendition.java index a28c049b..bea9569d 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRendition.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRendition.java @@ -27,6 +27,7 @@ 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; @@ -35,6 +36,8 @@ 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.format.MediaFormat; import io.wcm.handler.url.UrlHandler; import io.wcm.sling.commons.adapter.AdaptTo; @@ -92,7 +95,7 @@ class DamRendition extends SlingAdaptable implements Rendition { // if no match was found and auto-cropping is enabled, try to build a transformed rendition // with automatically devised cropping parameters if (resolvedRendition == null && mediaArgs.isAutoCrop()) { - DamAutoCropping autoCropping = new DamAutoCropping(damContext.getAsset(), mediaArgs); + DamAutoCropping autoCropping = new DamAutoCropping(damContext, mediaArgs); List autoCropDimensions = autoCropping.calculateAutoCropDimensions(); for (CropDimension autoCropDimension : autoCropDimensions) { RenditionHandler renditionHandler = new TransformedRenditionHandler(autoCropDimension, rotation, damContext); @@ -113,42 +116,55 @@ class DamRendition extends SlingAdaptable implements Rendition { @Override public String getUrl() { - if (this.rendition == null) { + if (rendition == null) { return null; } String url = null; - boolean dynamicMediaEnabled = damContext.isDynamicMediaEnabled() && !mediaArgs.isDynamicMediaDisabled(); - if (dynamicMediaEnabled && damContext.isDynamicMediaAsset()) { - // if DM is enabled: try to get rendition URL from dynamic media - String dynamicMediaPath = this.rendition.getDynamicMediaPath(this.mediaArgs.isContentDispositionAttachment(), damContext); - String productionAssetUrl = damContext.getDynamicMediaServerUrl(); - if (productionAssetUrl != null) { - url = productionAssetUrl + dynamicMediaPath; + if (damContext.isDynamicMediaEnabled()) { + if (damContext.isDynamicMediaAsset()) { + url = buildDynamicMediaUrl(); + if (url == null) { + // asset is valid DM asset, but no valid rendition could be generated + // reason might be that the smart-cropped rendition was too small for the requested size + return null; + } } - } - if (url == null) { - if (dynamicMediaEnabled) { + else { + // DM is enabled, but given asset is not a DM asset if (damContext.isDynamicMediaAemFallbackDisabled()) { - if (log.isWarnEnabled()) { - log.warn("Asset is not a valid DM asset, fallback disabled, rendition invalid: {}", this.rendition.getRendition().getPath()); - } + log.warn("Asset is not a valid DM asset, fallback disabled, rendition invalid: {}", rendition.getRendition().getPath()); return null; } else { - if (log.isTraceEnabled()) { - log.trace("Asset is not a valid DM asset, fallback to AEM-rendered rendition: {}", this.rendition.getRendition().getPath()); - } + log.trace("Asset is not a valid DM asset, fallback to AEM-rendered rendition: {}", rendition.getRendition().getPath()); } } + } + if (url == null) { // Render renditions in AEM: build externalized URL UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class); - String mediaPath = this.rendition.getMediaPath(this.mediaArgs.isContentDispositionAttachment()); - url = urlHandler.get(mediaPath).urlMode(this.mediaArgs.getUrlMode()) - .buildExternalResourceUrl(this.rendition.adaptTo(Resource.class)); + String mediaPath = rendition.getMediaPath(mediaArgs.isContentDispositionAttachment()); + url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode()) + .buildExternalResourceUrl(rendition.adaptTo(Resource.class)); } return url; } + /** + * Build DM URL for this rendition based on the calculated DM path and the configured DM hostname. + * @return DM URL or null if either DM path or configured DM hostname is null + */ + private @Nullable String buildDynamicMediaUrl() { + String dynamicMediaPath = rendition.getDynamicMediaPath(mediaArgs.isContentDispositionAttachment(), damContext); + String productionAssetUrl = damContext.getDynamicMediaServerUrl(); + if (dynamicMediaPath != null && productionAssetUrl != null) { + return productionAssetUrl + dynamicMediaPath; + } + else { + return null; + } + } + @Override public String getPath() { if (this.rendition != null) { @@ -241,15 +257,9 @@ public boolean isVectorImage() { return MediaFileType.isVectorImage(getFileExtension()); } - @Override - @SuppressWarnings("deprecation") - public boolean isFlash() { - return MediaFileType.isFlash(getFileExtension()); - } - @Override public boolean isDownload() { - return !isImage() && !isFlash(); + return !isImage(); } @Override @@ -277,6 +287,17 @@ public boolean isFallback() { return fallback; } + @Override + public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) { + if (this.rendition == null) { + throw new IllegalStateException("Rendition is not valid."); + } + if (type == UriTemplateType.CROP_CENTER) { + throw new IllegalArgumentException("CROP_CENTER not supported for rendition URI templates."); + } + return this.rendition.getUriTemplate(type, damContext); + } + @Override @SuppressWarnings("null") public AdapterType adaptTo(Class type) { diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRenditionMetadataService.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRenditionMetadataService.java deleted file mode 100644 index 56d156b7..00000000 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamRenditionMetadataService.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * #%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.mediasource.dam.impl.metadata.RenditionMetadataListenerService; - -/** - * @deprecated This service is deprecated and replaced by @link {@link RenditionMetadataListenerService}. - * But: If you've referenced this class from your unit test to generate rendition metadata during - * the test runs, you can remove the reference completely and instead update to - * io.wcm.testing.wcm-io-mock.handler 1.2.0 or higher; - * the listener service is there registered automatically, and able to operate in all run modes. - */ -@Deprecated -public final class DamRenditionMetadataService { - - // keep this only as deprecation warning for unit test referencing the original class - -} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamUriTemplate.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamUriTemplate.java index d73d876f..9cd1e4b4 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DamUriTemplate.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DamUriTemplate.java @@ -25,7 +25,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.resource.Resource; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.day.cq.dam.api.Rendition; + +import io.wcm.handler.media.CropDimension; import io.wcm.handler.media.Dimension; import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.UriTemplate; @@ -33,83 +37,134 @@ import io.wcm.handler.media.impl.ImageFileServlet; import io.wcm.handler.media.impl.MediaFileServlet; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaPath; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop; import io.wcm.handler.url.UrlHandler; import io.wcm.sling.commons.adapter.AdaptTo; +/** + * Generates URI templates for asset renditions - with or without Dynamic Media. + */ class DamUriTemplate implements UriTemplate { - private final String uriTemplate; private final UriTemplateType type; + private final String uriTemplate; private final Dimension dimension; DamUriTemplate(@NotNull UriTemplateType type, @NotNull Dimension dimension, - @NotNull DamContext damContext, @NotNull MediaArgs mediaArgs) { - this.uriTemplate = buildUriTemplate(type, damContext, mediaArgs); + @NotNull Rendition rendition, @Nullable CropDimension cropDimension, @Nullable Integer rotation, + @Nullable Double ratio, @NotNull DamContext damContext) { this.type = type; - this.dimension = dimension; - } - private static String buildUriTemplate(@NotNull UriTemplateType type, @NotNull DamContext damContext, - @NotNull MediaArgs mediaArgs) { String url = null; - if (!mediaArgs.isDynamicMediaDisabled() && damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset()) { + Dimension validatedDimension = null; + if (damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset()) { // if DM is enabled: try to get rendition URL from dynamic media - String productionAssetUrl = damContext.getDynamicMediaServerUrl(); - if (productionAssetUrl != null) { - switch (type) { - case CROP_CENTER: - url = productionAssetUrl + DynamicMediaPath.buildImage(damContext) - + "?wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH + "&hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT + "&fit=crop"; - break; - case SCALE_WIDTH: - url = productionAssetUrl + DynamicMediaPath.buildImage(damContext) - + "?wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH; - break; - case SCALE_HEIGHT: - url = productionAssetUrl + DynamicMediaPath.buildImage(damContext) - + "?hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT; - break; - default: - throw new IllegalArgumentException("Unsupported type: " + type); - } + NamedDimension smartCropDef = getDynamicMediaSmartCropDef(cropDimension, rotation, ratio, damContext); + url = buildUriTemplateDynamicMedia(type, cropDimension, rotation, smartCropDef, damContext); + // get actual max. dimension from smart crop rendition + if (url != null && smartCropDef != null) { + validatedDimension = SmartCrop.getCropDimensionForAsset(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef); } } if (url == null && (!damContext.isDynamicMediaEnabled() || !damContext.isDynamicMediaAemFallbackDisabled())) { // Render renditions in AEM: build externalized URL - final long DUMMY_WIDTH = 999991; - final long DUMMY_HEIGHT = 999992; - - String mediaPath = RenditionMetadata.buildMediaPath(damContext.getAsset().getOriginal().getPath() + "." + ImageFileServlet.SELECTOR - + "." + DUMMY_WIDTH + "." + DUMMY_HEIGHT - + "." + MediaFileServlet.EXTENSION, - ImageFileServlet.getImageFileName(damContext.getAsset().getName(), mediaArgs.getEnforceOutputFileExtension())); - UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class); - url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode()) - .buildExternalResourceUrl(damContext.getAsset().adaptTo(Resource.class)); - - switch (type) { - case CROP_CENTER: - url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH); - url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT); - break; - case SCALE_WIDTH: - url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH); - url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), "0"); - break; - case SCALE_HEIGHT: - url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), "0"); - url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT); - break; - default: - throw new IllegalArgumentException("Unsupported type: " + type); - } + url = buildUriTemplateDam(type, rendition, cropDimension, rotation, damContext); + } + this.uriTemplate = url; + + if (validatedDimension == null) { + validatedDimension = dimension; + } + this.dimension = validatedDimension; + } + + private static String buildUriTemplateDam(@NotNull UriTemplateType type, @NotNull Rendition rendition, + @Nullable CropDimension cropDimension, @Nullable Integer rotation, + @NotNull DamContext damContext) { + final long DUMMY_WIDTH = 999991; + final long DUMMY_HEIGHT = 999992; + + // build rendition URL with dummy width/height parameters (otherwise externalization will fail) + MediaArgs mediaArgs = damContext.getMediaArgs(); + String mediaPath = RenditionMetadata.buildMediaPath(rendition.getPath() + + "." + ImageFileServlet.buildSelectorString(DUMMY_WIDTH, DUMMY_HEIGHT, cropDimension, rotation, false) + + "." + MediaFileServlet.EXTENSION, + ImageFileServlet.getImageFileName(damContext.getAsset().getName(), mediaArgs.getEnforceOutputFileExtension())); + UrlHandler urlHandler = AdaptTo.notNull(damContext, UrlHandler.class); + String url = urlHandler.get(mediaPath).urlMode(mediaArgs.getUrlMode()) + .buildExternalResourceUrl(damContext.getAsset().adaptTo(Resource.class)); + + // replace dummy width/height parameters with actual placeholders + switch (type) { + case CROP_CENTER: + url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH); + url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT); + break; + case SCALE_WIDTH: + url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), URI_TEMPLATE_PLACEHOLDER_WIDTH); + url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), "0"); + break; + case SCALE_HEIGHT: + url = StringUtils.replace(url, Long.toString(DUMMY_WIDTH), "0"); + url = StringUtils.replace(url, Long.toString(DUMMY_HEIGHT), URI_TEMPLATE_PLACEHOLDER_HEIGHT); + break; + default: + throw new IllegalArgumentException("Unsupported type: " + type); } return url; } - @Override - public String getUriTemplate() { - return uriTemplate; + private static @Nullable String buildUriTemplateDynamicMedia(@NotNull UriTemplateType type, + @Nullable CropDimension cropDimension, @Nullable Integer rotation, @Nullable NamedDimension smartCropDef, + @NotNull DamContext damContext) { + String productionAssetUrl = damContext.getDynamicMediaServerUrl(); + if (productionAssetUrl == null) { + return null; + } + StringBuilder result = new StringBuilder(); + result.append(productionAssetUrl).append(DynamicMediaPath.buildImage(damContext)); + + // build DM URL with smart cropping + if (smartCropDef != null) { + result.append("%3A").append(smartCropDef.getName()).append("?") + .append(getDynamicMediaWidthHeightParameters(type)) + .append("&fit=constrain"); + return result.toString(); + } + + // build DM URL without smart cropping + result.append("?"); + if (cropDimension != null) { + result.append("crop=").append(cropDimension.getCropStringWidthHeight()).append("&"); + } + if (rotation != null) { + result.append("rotate=").append(rotation).append("&"); + } + result.append(getDynamicMediaWidthHeightParameters(type)); + return result.toString(); + } + + private static String getDynamicMediaWidthHeightParameters(UriTemplateType type) { + switch (type) { + case CROP_CENTER: + return "wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH + "&hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT + "&fit=crop"; + case SCALE_WIDTH: + return "wid=" + URI_TEMPLATE_PLACEHOLDER_WIDTH; + case SCALE_HEIGHT: + return "hei=" + URI_TEMPLATE_PLACEHOLDER_HEIGHT; + default: + throw new IllegalArgumentException("Unsupported type: " + type); + } + } + + private static NamedDimension getDynamicMediaSmartCropDef(@Nullable CropDimension cropDimension, @Nullable Integer rotation, + @Nullable Double ratio, @NotNull DamContext damContext) { + if (SmartCrop.canApply(cropDimension, rotation) && ratio != null) { + // check for matching image profile and use predefined cropping preset if match found + return SmartCrop.getDimensionForRatio(damContext.getImageProfile(), ratio); + } + return null; } @Override @@ -117,6 +172,11 @@ public UriTemplateType getType() { return type; } + @Override + public String getUriTemplate() { + return uriTemplate; + } + @Override public long getMaxWidth() { return dimension.getWidth(); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandler.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandler.java index 4c004d0e..2c9a8268 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandler.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/DefaultRenditionHandler.java @@ -22,9 +22,14 @@ import static io.wcm.handler.media.format.impl.MediaFormatSupport.getRequestedFileExtensions; import static io.wcm.handler.media.format.impl.MediaFormatSupport.visitMediaFormats; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.stream.Collectors; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -33,15 +38,18 @@ import com.day.cq.dam.api.Asset; import com.day.cq.dam.api.Rendition; -import com.google.common.collect.ImmutableSet; +import io.wcm.handler.media.CropDimension; import io.wcm.handler.media.MediaArgs; +import io.wcm.handler.media.MediaArgs.MediaFormatOption; import io.wcm.handler.media.MediaFileType; import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.format.MediaFormatHandler; import io.wcm.handler.media.format.Ratio; import io.wcm.handler.media.format.impl.MediaFormatVisitor; import io.wcm.handler.mediasource.dam.AssetRendition; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.NamedDimension; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop; /** * Handles resolving DAM renditions and resizing for media handler. @@ -79,7 +87,7 @@ Set getAvailableRenditions(MediaArgs mediaArgs) { addRendition(candidates, rendition, mediaArgs); } candidates = postProcessCandidates(candidates, mediaArgs); - this.renditions = ImmutableSet.copyOf(candidates); + this.renditions = Collections.unmodifiableSet(candidates); } return this.renditions; } @@ -109,14 +117,59 @@ private void addRendition(Set candidates, Rendition rendition if (!isIncludeAssetWebRenditions && AssetRendition.isWebRendition(rendition)) { return; } - // skip all non-original rendition for dynamic media assets. dynamic media does not support them. - if (damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset() && !AssetRendition.isOriginal(rendition)) { - return; + + // special handling for dynamic media + if (damContext.isDynamicMediaEnabled() && damContext.isDynamicMediaAsset()) { + // skip all non-original renditions for dynamic media assets. dynamic media does not support them. + if (!AssetRendition.isOriginal(rendition)) { + return; + } + + // check if there are matching smart crop renditions for the requested media format(s) + // and return those instead of the original rendition for further processing + String fileExtension = FilenameUtils.getExtension(damContext.getAsset().getName()); + if (damContext.isDynamicMediaValidateSmartCropRenditionSizes() + && MediaFileType.isImage(fileExtension) && !MediaFileType.isVectorImage(fileExtension)) { + List cropDimensions = getDynamicMediaCropDimensions(mediaArgs); + if (!cropDimensions.isEmpty()) { + candidates.addAll(cropDimensions.stream() + .map(cropDimension -> new VirtualTransformedRenditionMetadata(originalRendition.getRendition(), + cropDimension.getWidth(), cropDimension.getHeight(), mediaArgs.getEnforceOutputFileExtension(), cropDimension, null)) + .collect(Collectors.toList())); + return; + } + } } + RenditionMetadata renditionMetadata = createRenditionMetadata(rendition); candidates.add(renditionMetadata); } + /** + * Try to get actual smart crop dimensions for the requested ratio(s) for the current asset. + * @param mediaArgs Media Args with requested media formats + * @return Cropping dimensions or empty list if not found + */ + private @NotNull List getDynamicMediaCropDimensions(MediaArgs mediaArgs) { + if (mediaArgs.getMediaFormatOptions() == null) { + return Collections.emptyList(); + } + List result = new ArrayList<>(); + for (MediaFormatOption mediaFormatOption : mediaArgs.getMediaFormatOptions()) { + MediaFormat mediaFormat = mediaFormatOption.getMediaFormat(); + if (mediaFormat != null && mediaFormat.hasRatio()) { + NamedDimension smartCropDef = SmartCrop.getDimensionForRatio(damContext.getImageProfile(), mediaFormat.getRatio()); + if (smartCropDef != null) { + CropDimension cropDimension = SmartCrop.getCropDimensionForAsset(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef); + if (cropDimension != null) { + result.add(cropDimension); + } + } + } + } + return result; + } + /** * Create rendition metadata for given rendition. May be overridden by subclasses. * @param rendition Rendition @@ -267,6 +320,7 @@ private boolean isSizeMatchingRequest(MediaArgs mediaArgs, String[] requestedFil * @return Rendition or null if none found */ private RenditionMetadata getExactMatchRendition(final Set candidates, MediaArgs mediaArgs) { + MediaFormat[] mediaFormats = mediaArgs.getMediaFormats(); // check for fixed width and/or height request if (mediaArgs.getFixedWidth() > 0 || mediaArgs.getFixedHeight() > 0) { for (RenditionMetadata candidate : candidates) { @@ -277,16 +331,16 @@ private RenditionMetadata getExactMatchRendition(final Set ca } // otherwise check for media format restriction - else if (mediaArgs.getMediaFormats() != null && mediaArgs.getMediaFormats().length > 0) { + else if (mediaFormats != null && mediaFormats.length > 0) { return visitMediaFormats(mediaArgs, new MediaFormatVisitor() { @Override public @Nullable RenditionMetadata visit(@NotNull MediaFormat mediaFormat) { for (RenditionMetadata candidate : candidates) { - if (candidate.matches((int)mediaFormat.getEffectiveMinWidth(), - (int)mediaFormat.getEffectiveMinHeight(), - (int)mediaFormat.getEffectiveMaxWidth(), - (int)mediaFormat.getEffectiveMaxHeight(), - (int)mediaFormat.getMinWidthHeight(), + if (candidate.matches(mediaFormat.getEffectiveMinWidth(), + mediaFormat.getEffectiveMinHeight(), + mediaFormat.getEffectiveMaxWidth(), + mediaFormat.getEffectiveMaxHeight(), + mediaFormat.getMinWidthHeight(), mediaFormat.getRatio())) { candidate.setMediaFormat(mediaFormat); return candidate; @@ -333,7 +387,7 @@ else if (!candidates.isEmpty()) { */ private RenditionMetadata getVirtualRendition(final Set candidates, MediaArgs mediaArgs) { - // get from fixed with/height + // get from fixed width/height if (mediaArgs.getFixedWidth() > 0 || mediaArgs.getFixedHeight() > 0) { long destWidth = mediaArgs.getFixedWidth(); long destHeight = mediaArgs.getFixedHeight(); @@ -349,9 +403,9 @@ private RenditionMetadata getVirtualRendition(final Set candi return visitMediaFormats(mediaArgs, new MediaFormatVisitor() { @Override public @Nullable RenditionMetadata visit(@NotNull MediaFormat mediaFormat) { - int destWidth = (int)mediaFormat.getEffectiveMinWidth(); - int destHeight = (int)mediaFormat.getEffectiveMinHeight(); - int minWidthHeight = (int)mediaFormat.getMinWidthHeight(); + long destWidth = mediaFormat.getEffectiveMinWidth(); + long destHeight = mediaFormat.getEffectiveMinHeight(); + long minWidthHeight = mediaFormat.getMinWidthHeight(); double destRatio = mediaFormat.getRatio(); // try to find matching rendition, otherwise check for next media format RenditionMetadata rendition = getVirtualRendition(candidates, destWidth, destHeight, minWidthHeight, destRatio, @@ -423,12 +477,12 @@ private RenditionMetadata getVirtualRendition(RenditionMetadata rendition, long // if height is missing - calculate from width if (height == 0 && width > 0) { - height = (int)Math.round(width / ratio); + height = Math.round(width / ratio); } // if width is missing - calculate from height if (width == 0 && height > 0) { - width = (int)Math.round(height * ratio); + width = Math.round(height * ratio); } // return virtual rendition diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java index d8624429..32407b98 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/RenditionMetadata.java @@ -25,9 +25,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.jackrabbit.oak.commons.LazyValue; import org.apache.sling.api.adapter.SlingAdaptable; import org.apache.sling.api.resource.Resource; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.day.cq.dam.api.Rendition; import com.day.image.Layer; @@ -35,6 +37,8 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.wcm.handler.media.Dimension; import io.wcm.handler.media.MediaFileType; +import io.wcm.handler.media.UriTemplate; +import io.wcm.handler.media.UriTemplateType; import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.format.Ratio; import io.wcm.handler.media.impl.ImageFileServlet; @@ -51,8 +55,7 @@ class RenditionMetadata extends SlingAdaptable implements Comparable dimensionLazyValue; private final boolean isImage; private final boolean isVectorImage; private MediaFormat mediaFormat; @@ -69,16 +72,18 @@ class RenditionMetadata extends SlingAdaptable implements Comparable() { + @Override + protected Dimension createValue() { + Dimension result = AssetRendition.getDimension(rendition); + if (result == null) { + result = new Dimension(0, 0); + } + return result; + } + }; } /** @@ -142,14 +147,14 @@ public String getMimeType() { * @return Image width */ public long getWidth() { - return this.width; + return dimensionLazyValue.get().getWidth(); } /** * @return Image height */ public long getHeight() { - return this.height; + return dimensionLazyValue.get().getHeight(); } /** @@ -200,7 +205,7 @@ else if (MediaFileType.isBrowserImage(getFileExtension()) || !MediaFileType.isIm * @param damContext DAM context * @return Dynamic media path part or null if dynamic media not supported for this rendition */ - public @NotNull String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) { + public @Nullable String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) { if (contentDispositionAttachment) { // serve static content from dynamic media for content disposition attachment return DynamicMediaPath.buildContent(damContext, true); @@ -320,7 +325,7 @@ else if (otherIsOriginalRendition && !thisIsOriginalRendition) { String thisPath = getRendition().getPath(); String otherPath = obj.getRendition().getPath(); if (!StringUtils.equals(thisPath, otherPath)) { - // same with/height - compare paths as last resort + // same width/height - compare paths as last resort return thisPath.compareTo(otherPath); } else { @@ -353,12 +358,23 @@ protected InputStream getInputStream() { return this.rendition.adaptTo(Resource.class).adaptTo(InputStream.class); } + public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type, @NotNull DamContext damContext) { + if (!isImage() || isVectorImage()) { + throw new UnsupportedOperationException("Unable to build URI template for rendition: " + getRendition().getPath()); + } + Dimension dimension = AssetRendition.getDimension(getRendition()); + if (dimension == null) { + throw new IllegalArgumentException("Unable to get dimension for rendition: " + getRendition().getPath()); + } + return new DamUriTemplate(type, dimension, rendition, null, null, Ratio.get(dimension), damContext); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.rendition.getPath()); - if (this.width > 0 || this.height > 0) { - sb.append(" (").append(Long.toString(this.width)).append("x").append(Long.toString(this.height)).append(")"); + if (getWidth() > 0 || getHeight() > 0) { + sb.append(" (").append(Long.toString(getWidth())).append("x").append(Long.toString(getHeight())).append(")"); } return sb.toString(); } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandler.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandler.java index 8eba06dc..f3f1fce6 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandler.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandler.java @@ -30,7 +30,6 @@ import io.wcm.handler.media.CropDimension; import io.wcm.handler.media.MediaArgs; -import io.wcm.handler.media.format.Ratio; /** * Extended rendition handler supporting cropping and rotating of images. @@ -102,36 +101,10 @@ private VirtualTransformedRenditionMetadata getCropRendition(MediaArgs mediaArgs if (original == null || original.isVectorImage()) { return null; } - Double scaleFactor = getCropScaleFactor(original); - long scaledLeft = Math.round(cropDimension.getLeft() * scaleFactor); - long scaledTop = Math.round(cropDimension.getTop() * scaleFactor); - long scaledWidth = Math.round(cropDimension.getWidth() * scaleFactor); - if (scaledWidth > original.getWidth()) { - scaledWidth = original.getWidth(); - } - long scaledHeight = Math.round(cropDimension.getHeight() * scaleFactor); - if (scaledHeight > original.getHeight()) { - scaledHeight = original.getHeight(); - } - CropDimension scaledCropDimension = new CropDimension(scaledLeft, scaledTop, scaledWidth, scaledHeight, - cropDimension.isAutoCrop()); return new VirtualTransformedRenditionMetadata(original.getRendition(), - rotateMapWidth(scaledCropDimension.getWidth(), scaledCropDimension.getHeight(), rotation), - rotateMapHeight(scaledCropDimension.getWidth(), scaledCropDimension.getHeight(), rotation), - mediaArgs.getEnforceOutputFileExtension(), scaledCropDimension, rotation); - } - - /** - * The cropping coordinates are stored with coordinates relating to the web-enabled rendition. But we want - * to crop the original image, so we have to scale those values to match the coordinates in the original image. - * @return Scale factor - */ - private double getCropScaleFactor(RenditionMetadata original) { - RenditionMetadata webEnabled = DamAutoCropping.getWebRenditionForCropping(getAsset()); - if (original == null || webEnabled == null || original.getWidth() == 0 || webEnabled.getWidth() == 0) { - return 1d; - } - return Ratio.get(original.getWidth(), webEnabled.getWidth()); + rotateMapWidth(cropDimension.getWidth(), cropDimension.getHeight(), rotation), + rotateMapHeight(cropDimension.getWidth(), cropDimension.getHeight(), rotation), + mediaArgs.getEnforceOutputFileExtension(), cropDimension, rotation); } } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java index 01786282..f184b20d 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualRenditionMetadata.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.day.cq.dam.api.Rendition; import com.day.image.Layer; @@ -61,7 +62,7 @@ public String getFileName(boolean contentDispositionAttachment) { @Override public long getFileSize() { - // no size for virutal renditions + // no size for virtual renditions return 0L; } @@ -88,7 +89,7 @@ public long getHeight() { } @Override - public @NotNull String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) { + public @Nullable String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) { if (contentDispositionAttachment) { // serve static content from dynamic media for content disposition attachment return DynamicMediaPath.buildContent(damContext, true); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java index 693b9867..8301efb5 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/VirtualTransformedRenditionMetadata.java @@ -24,13 +24,20 @@ import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import com.day.cq.dam.api.Rendition; import com.day.image.Layer; import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.UriTemplate; +import io.wcm.handler.media.UriTemplateType; +import io.wcm.handler.media.format.Ratio; import io.wcm.handler.media.impl.ImageFileServlet; +import io.wcm.handler.media.impl.ImageTransformation; import io.wcm.handler.media.impl.MediaFileServlet; +import io.wcm.handler.mediasource.dam.AssetRendition; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaPath; /** @@ -93,7 +100,7 @@ public Integer getRotation() { } @Override - public @NotNull String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) { + public @Nullable String getDynamicMediaPath(boolean contentDispositionAttachment, DamContext damContext) { // render virtual rendition with dynamic media (we ignore contentDispositionAttachment here) return DynamicMediaPath.buildImage(damContext, getWidth(), getHeight(), this.cropDimension, this.rotation); } @@ -121,6 +128,22 @@ protected InputStream getInputStream() { return null; } + @Override + public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type, @NotNull DamContext damContext) { + if (!isImage() || isVectorImage()) { + throw new UnsupportedOperationException("Unable to build URI template for rendition: " + getRendition().getPath()); + } + Dimension dimension = cropDimension; + if (dimension == null) { + dimension = AssetRendition.getDimension(getRendition()); + } + if (dimension == null) { + throw new IllegalArgumentException("Unable to get dimension for rendition: " + getRendition().getPath()); + } + dimension = ImageTransformation.rotateMapDimension(dimension, rotation); + return new DamUriTemplate(type, dimension, getRendition(), cropDimension, rotation, Ratio.get(dimension), damContext); + } + @Override public int hashCode() { return new HashCodeBuilder() diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/WebEnabledRenditionCropping.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/WebEnabledRenditionCropping.java new file mode 100644 index 00000000..d3288ed8 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/WebEnabledRenditionCropping.java @@ -0,0 +1,93 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.day.cq.dam.api.Asset; + +import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.format.Ratio; +import io.wcm.handler.mediasource.dam.AssetRendition; + +/** + * Helper class for calculating crop dimensions for auto-cropping. + */ +final class WebEnabledRenditionCropping { + + private WebEnabledRenditionCropping() { + // static methods only + } + + /** + * Rescales the a crop dimension that is based on the web-enabled rendition to apply to the original rendition + * of the asset (which is the actual base for the cropping). + * @param asset Asset + * @param cropDimensionForWebRendition Crop dimension calculated based on web rendition + * @return Rendition or null if no match found + */ + public static @NotNull CropDimension getCropDimensionForOriginal(@NotNull Asset asset, + @NotNull CropDimension cropDimensionForWebRendition) { + RenditionMetadata original = new RenditionMetadata(asset.getOriginal()); + Double scaleFactor = getCropScaleFactor(asset, original); + long scaledLeft = Math.round(cropDimensionForWebRendition.getLeft() * scaleFactor); + long scaledTop = Math.round(cropDimensionForWebRendition.getTop() * scaleFactor); + long scaledWidth = Math.round(cropDimensionForWebRendition.getWidth() * scaleFactor); + if (scaledWidth > original.getWidth()) { + scaledWidth = original.getWidth(); + } + long scaledHeight = Math.round(cropDimensionForWebRendition.getHeight() * scaleFactor); + if (scaledHeight > original.getHeight()) { + scaledHeight = original.getHeight(); + } + return new CropDimension(scaledLeft, scaledTop, scaledWidth, scaledHeight, + cropDimensionForWebRendition.isAutoCrop()); + } + + /** + * The cropping coordinates are stored with coordinates relating to the web-enabled rendition. But we want + * to crop the original image, so we have to scale those values to match the coordinates in the original image. + * @return Scale factor + */ + private static double getCropScaleFactor(@NotNull Asset asset, @NotNull RenditionMetadata original) { + RenditionMetadata webEnabled = getWebEnabledRendition(asset); + if (webEnabled == null || original.getWidth() == 0 || webEnabled.getWidth() == 0) { + return 1d; + } + return Ratio.get(original.getWidth(), webEnabled.getWidth()); + } + + /** + * Get web first rendition for asset. + * This is the same logic as implemented in + * /libs/cq/gui/components/authoring/editors/clientlibs/core/inlineediting/js/ImageEditor.js. + * @param asset Asset + * @return Web rendition or null if none found + */ + private static @Nullable RenditionMetadata getWebEnabledRendition(@NotNull Asset asset) { + return asset.getRenditions().stream() + .filter(AssetRendition::isWebRendition) + .findFirst() + .map(RenditionMetadata::new) + .orElse(null); + } + +} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaCapabilityDetection.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaCapabilityDetection.java new file mode 100644 index 00000000..0cb2697f --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaCapabilityDetection.java @@ -0,0 +1,46 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.dynamicmedia; + +/** + * Modes to detect if Dynamic Media is available for a given asset. + */ +enum DynamicMediaCapabilityDetection { + + /** + * Default: Auto-detect if Dynamic Media is available for a given asset. + * If a property dam:scene7File exists in the metadata of the asset, Dynamic Media is considered + * available. If the property does not exist, the asset is treated as non-DM asset and renditions + * are rendered within AEM. + */ + AUTO, + + /** + * Disables the detection of Dynamic Media. Dynamic Media is never used. + * All renditions are rendered within AEM. + */ + OFF, + + /** + * Configures that Dynamic Media is available for the environment and should be used for all assets. + */ + ON + +} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java index 2c328d1f..fb31dfad 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPath.java @@ -22,7 +22,6 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; @@ -94,7 +93,7 @@ private DynamicMediaPath() { * @param height Height * @return Media path */ - public static @NotNull String buildImage(@NotNull DamContext damContext, long width, long height) { + public static @Nullable String buildImage(@NotNull DamContext damContext, long width, long height) { return buildImage(damContext, width, height, null, null); } @@ -107,18 +106,25 @@ private DynamicMediaPath() { * @param rotation Rotation * @return Media path */ - public static @NotNull String buildImage(@NotNull DamContext damContext, long width, long height, + public static @Nullable String buildImage(@NotNull DamContext damContext, long width, long height, @Nullable CropDimension cropDimension, @Nullable Integer rotation) { Dimension dimension = calcWidthHeight(damContext, width, height); StringBuilder result = new StringBuilder(); result.append(IMAGE_SERVER_PATH).append(encodeDynamicMediaObject(damContext)); - if (cropDimension != null && cropDimension.isAutoCrop() && rotation == null) { - // auto-crop applied - check for matching image profile and use predefined cropping preset if match found - Optional smartCroppingDef = getSmartCropDimension(damContext, width, height); - if (smartCroppingDef.isPresent()) { - result.append("%3A").append(smartCroppingDef.get().getName()).append("?") + // check for smart cropping when no cropping was applied by default, or auto-crop is enabled + if (SmartCrop.canApply(cropDimension, rotation)) { + // check for matching image profile and use predefined cropping preset if match found + NamedDimension smartCropDef = SmartCrop.getDimensionForWidthHeight(damContext.getImageProfile(), width, height); + if (smartCropDef != null) { + if (damContext.isDynamicMediaValidateSmartCropRenditionSizes() + && !SmartCrop.isMatchingSize(damContext.getAsset(), damContext.getResourceResolver(), smartCropDef, width, height)) { + // smart crop should be applied, but selected area is too small, treat as invalid + logResult(damContext, ""); + return null; + } + result.append("%3A").append(smartCropDef.getName()).append("?") .append("wid=").append(dimension.getWidth()).append("&") .append("hei=").append(dimension.getHeight()).append("&") // cropping/width/height is pre-calculated to fit with original ratio, make sure there are no 1px background lines visible @@ -143,7 +149,7 @@ private DynamicMediaPath() { return result.toString(); } - private static void logResult(@NotNull DamContext damContext, @NotNull StringBuilder result) { + private static void logResult(@NotNull DamContext damContext, @NotNull CharSequence result) { if (log.isTraceEnabled()) { log.trace("Build dynamic media path for {}: {}", damContext.getAsset().getPath(), result); } @@ -173,18 +179,6 @@ private static Dimension calcWidthHeight(@NotNull DamContext damContext, long wi return new Dimension(width, height); } - private static Optional<@NotNull NamedDimension> getSmartCropDimension(@NotNull DamContext damContext, long width, long height) { - Double requestedRatio = Ratio.get(width, height); - ImageProfile imageProfile = damContext.getImageProfile(); - if (imageProfile == null) { - return Optional.empty(); - } - Optional matchingDimension = imageProfile.getSmartCropDefinitions().stream() - .filter(def -> Ratio.matches(Ratio.get(def), requestedRatio)) - .findFirst(); - return matchingDimension.map(def -> new NamedDimension(def.getName(), width, height)); - } - /** * Splits dynamic media folder and file name and URL-encodes them separately (may contain spaces or special chars). * @param damContext DAM context diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportService.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportService.java index 32df2c5b..78aa7729 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportService.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportService.java @@ -19,6 +19,7 @@ */ package io.wcm.handler.mediasource.dam.impl.dynamicmedia; +import org.apache.sling.api.adapter.Adaptable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,12 +39,24 @@ public interface DynamicMediaSupportService { */ boolean isDynamicMediaEnabled(); + /** + * @param isDynamicMediaAsset true if given asset has DM metadata properties available. + * @return Whether dynamic media capability is enabled for the given asset + */ + boolean isDynamicMediaCapabilityEnabled(boolean isDynamicMediaAsset); + /** * @return Whether a transparent fallback to Media Handler-based rendering of renditions is allowed * if the appropriate Dynamic Media metadata is not preset for an asset. */ boolean isAemFallbackDisabled(); + /** + * @return Whether to validate that the renditions defined via smart cropping fulfill the requested image width/height + * to avoid upscaling or white borders. + */ + boolean isValidateSmartCropRenditionSizes(); + /** * @return Reply image size limit as configured in dynamic media. */ @@ -69,10 +82,12 @@ public interface DynamicMediaSupportService { /** * Get scene7 host/URL prefix for publish environment. * @param asset DAM asset + * @param urlMode URL mode + * @param adaptable Adaptable * @return Protocol and hostname of scene7 host or null. * If author preview mode is enabled, returns empty string. */ @Nullable - String getDynamicMediaServerUrl(@NotNull Asset asset, @Nullable UrlMode urlMode); + String getDynamicMediaServerUrl(@NotNull Asset asset, @Nullable UrlMode urlMode, @NotNull Adaptable adaptable); } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImpl.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImpl.java index f0e66c56..23d2e693 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImpl.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImpl.java @@ -21,16 +21,17 @@ import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import java.util.Map; import java.util.regex.Pattern; import javax.jcr.RepositoryException; import org.apache.commons.lang3.StringUtils; +import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.resource.LoginException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; -import org.apache.sling.featureflags.Features; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.osgi.service.component.annotations.Activate; @@ -45,10 +46,10 @@ import com.day.cq.dam.api.Asset; import com.day.cq.dam.api.DamConstants; import com.day.cq.dam.api.s7dam.utils.PublishUtils; -import com.google.common.collect.ImmutableMap; import io.wcm.handler.media.Dimension; import io.wcm.handler.url.SiteConfig; +import io.wcm.handler.url.UrlHandler; import io.wcm.handler.url.UrlMode; import io.wcm.handler.url.UrlModes; import io.wcm.sling.commons.adapter.AdaptTo; @@ -68,9 +69,15 @@ public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportServic @AttributeDefinition( name = "Enabled", description = "Enable support for dynamic media. " - + "Only gets active when dynamic media is actually configured for the instance.") + + "Only gets active when dynamic media is actually enabled for the instance.") boolean enabled() default true; + @AttributeDefinition( + name = "Dynamic Media Capability", + description = "Whether to detect automatically if Dynamic Media is actually for a given asset by looking for existing DM metadata. " + + "Setting to ON disables the auto-detection and forces it to enabled for all asssets, setting to OFF forced it to disabled.") + DynamicMediaCapabilityDetection dmCapabilityDetection() default DynamicMediaCapabilityDetection.AUTO; + @AttributeDefinition( name = "Author Preview Mode", description = "Loads dynamic media images via author instance - to allow previewing unpublished images. " @@ -83,6 +90,11 @@ public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportServic + "if Dynamic Media is enabled, but the asset has not the appropriate Dynamic Media metadata.") boolean disableAemFallback() default false; + @AttributeDefinition( + name = "Validte Smart Crop Rendition Sizes", + description = "Validates that the renditions defined via smart cropping fulfill the requested image width/height to avoid upscaling or white borders.") + boolean validateSmartCropRenditionSizes() default true; + @AttributeDefinition( name = "Image width limit", description = "The configured width value for 'Reply Image Size Limit'.") @@ -95,21 +107,16 @@ public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportServic } - /** - * Feature flag signaling activation of DM on AEM instance. - */ - public static final String ASSETS_SCENE7_FEATURE_FLAG_PID = "com.adobe.dam.asset.scene7.feature.flag"; - - @Reference - private Features featureFlagService; @Reference private PublishUtils dynamicMediaPublishUtils; @Reference private ResourceResolverFactory resourceResolverFactory; private boolean enabled; + private DynamicMediaCapabilityDetection dmCapabilityDetection; private boolean authorPreviewMode; private boolean disableAemFallback; + private boolean validateSmartCropRenditionSizes; private Dimension imageSizeLimit; private static final String SERVICEUSER_SUBSERVICE = "dynamic-media-support"; @@ -120,14 +127,36 @@ public class DynamicMediaSupportServiceImpl implements DynamicMediaSupportServic @Activate private void activate(Config config) { this.enabled = config.enabled(); + this.dmCapabilityDetection = config.dmCapabilityDetection(); this.authorPreviewMode = config.authorPreviewMode(); this.disableAemFallback = config.disableAemFallback(); + this.validateSmartCropRenditionSizes = config.validateSmartCropRenditionSizes(); this.imageSizeLimit = new Dimension(config.imageSizeLimitWidth(), config.imageSizeLimitHeight()); + + if (this.enabled) { + log.info("DynamicMediaSupport: enabled={}, capabilityEnabled={}, capabilityDetection={}, " + + "authorPreviewMode={}, disableAemFallback={}, imageSizeLimit={}", + this.enabled, this.dmCapabilityDetection, this.dmCapabilityDetection, + this.authorPreviewMode, this.disableAemFallback, this.imageSizeLimit); + } } @Override public boolean isDynamicMediaEnabled() { - return this.enabled && featureFlagService.isEnabled(ASSETS_SCENE7_FEATURE_FLAG_PID); + return this.enabled; + } + + @Override + public boolean isDynamicMediaCapabilityEnabled(boolean isDynamicMediaAsset) { + switch (dmCapabilityDetection) { + case AUTO: + return isDynamicMediaAsset; + case ON: + return true; + case OFF: + default: + return false; + } } @Override @@ -135,6 +164,11 @@ public boolean isAemFallbackDisabled() { return disableAemFallback; } + @Override + public boolean isValidateSmartCropRenditionSizes() { + return validateSmartCropRenditionSizes; + } + @Override public @NotNull Dimension getImageSizeLimit() { return this.imageSizeLimit; @@ -143,7 +177,7 @@ public boolean isAemFallbackDisabled() { @Override public @Nullable ImageProfile getImageProfile(@NotNull String profilePath) { try (ResourceResolver resourceResolver = resourceResolverFactory - .getServiceResourceResolver(ImmutableMap.of(ResourceResolverFactory.SUBSERVICE, SERVICEUSER_SUBSERVICE))) { + .getServiceResourceResolver(Map.of(ResourceResolverFactory.SUBSERVICE, SERVICEUSER_SUBSERVICE))) { Resource profileResource = resourceResolver.getResource(profilePath); if (profileResource != null) { log.debug("Loaded image profile: {}", profilePath); @@ -188,13 +222,15 @@ public boolean isAemFallbackDisabled() { } @Override - public @Nullable String getDynamicMediaServerUrl(@NotNull Asset asset, @Nullable UrlMode urlMode) { + public @Nullable String getDynamicMediaServerUrl(@NotNull Asset asset, @Nullable UrlMode urlMode, @NotNull Adaptable adaptable) { Resource assetResource = AdaptTo.notNull(asset, Resource.class); if (authorPreviewMode && !forcePublishMode(urlMode)) { // route dynamic media requests through author instance for preview // return configured author URL, or empty string if none configured - SiteConfig siteConfig = AdaptTo.notNull(assetResource, SiteConfig.class); - return StringUtils.defaultString(siteConfig.siteUrlAuthor()); + SiteConfig siteConfig = AdaptTo.notNull(adaptable, SiteConfig.class); + String siteUrlAUthor = StringUtils.defaultString(siteConfig.siteUrlAuthor()); + UrlHandler urlHandler = AdaptTo.notNull(adaptable, UrlHandler.class); + return urlHandler.applySiteUrlAutoDetection(siteUrlAUthor); } try { String[] productionAssetUrls = dynamicMediaPublishUtils.externalizeImageDeliveryAsset(assetResource); @@ -215,10 +251,10 @@ public boolean isAemFallbackDisabled() { * @return true if publish mode should be forced */ private boolean forcePublishMode(@Nullable UrlMode urlMode) { - return (urlMode == UrlModes.FULL_URL_PUBLISH - || urlMode == UrlModes.FULL_URL_PUBLISH_FORCENONSECURE - || urlMode == UrlModes.FULL_URL_PUBLISH_FORCESECURE - || urlMode == UrlModes.FULL_URL_PUBLISH_PROTOCOLRELATIVE); + return urlMode != null && (urlMode.equals(UrlModes.FULL_URL_PUBLISH) + || urlMode.equals(UrlModes.FULL_URL_PUBLISH_FORCENONSECURE) + || urlMode.equals(UrlModes.FULL_URL_PUBLISH_FORCESECURE) + || urlMode.equals(UrlModes.FULL_URL_PUBLISH_PROTOCOLRELATIVE)); } } diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImpl.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImpl.java index af042967..df42fa1e 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImpl.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImpl.java @@ -30,11 +30,22 @@ /** * Wraps access to dynamic media image profile. */ -final class ImageProfileImpl implements ImageProfile { +public final class ImageProfileImpl implements ImageProfile { - static final String PN_CROP_TYPE = "crop_type"; - static final String CROP_TYPE_SMART = "crop_smart"; - static final String PN_BANNER = "banner"; + /** + * Crop type + */ + public static final String PN_CROP_TYPE = "crop_type"; + + /** + * Smart cropping crop type. + */ + public static final String CROP_TYPE_SMART = "crop_smart"; + + /** + * Banner property with string like: Crop-1,100,60|Crop-2,50,30 + */ + public static final String PN_BANNER = "banner"; private final List smartCropDefinitions; diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCrop.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCrop.java new file mode 100644 index 00000000..36f81a03 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCrop.java @@ -0,0 +1,219 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.dynamicmedia; + +import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; + +import java.util.Arrays; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +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.dam.api.Asset; + +import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.Dimension; +import io.wcm.handler.media.format.Ratio; +import io.wcm.handler.mediasource.dam.AssetRendition; + +/** + * Apply Dynamic Media Smart Cropping. + */ +public final class SmartCrop { + + /** + * Normalized width (double value 0..1 as percentage of original image). + */ + public static final String PN_NORMALIZED_WIDTH = "normalizedWidth"; + + /** + * Normalized height (double value 0..1 as percentage of original image). + */ + public static final String PN_NORMALIZED_HEIGHT = "normalizedHeight"; + + /** + * Left margin (double value 0..1 as percentage of original image). + */ + public static final String PN_LEFT = "left"; + + /** + * Top margin (double value 0..1 as percentage of original image). + */ + public static final String PN_TOP = "top"; + + private static final double MIN_NORMALIZED_WIDTH_HEIGHT = 0.0001; + private static final Logger log = LoggerFactory.getLogger(SmartCrop.class); + + private SmartCrop() { + // static methods only + } + + /** + * Smart cropping can be applied when no manual cropping was applied, or auto cropping is enabled. + * Additionally, combination with rotation is not allowed. + * @param cropDimension Manual crop definition + * @param rotation Rotation + * @return true if Smart Cropping can be applied + */ + public static boolean canApply(@Nullable CropDimension cropDimension, @Nullable Integer rotation) { + return (cropDimension == null || cropDimension.isAutoCrop()) && rotation == null; + } + + /** + * Checks DM image profile for a smart cropping definition matching the ratio of the requested ratio. + * @param imageProfile Image profile from DAM context (null if no is defined) + * @param requestedRatio Requested ratio + * @return Named dimension or null. The provided width/height can usually be ignored, because they + * are the width/height from the image profile which only describe the aspect ratio, but not + * any width/height values used in reality. + */ + public static @Nullable NamedDimension getDimensionForRatio(@Nullable ImageProfile imageProfile, double requestedRatio) { + if (imageProfile == null) { + return null; + } + return imageProfile.getSmartCropDefinitions().stream() + .filter(def -> Ratio.matches(Ratio.get(def), requestedRatio)) + .findFirst().orElse(null); + } + + /** + * Checks DM image profile for a smart cropping definition matching the ratio of the requested width/height. + * @param imageProfile Image profile from DAM context (null if no is defined) + * @param width Width + * @param height Height + * @return Smart cropping definition with requested width/height - or null if no match + */ + public static @Nullable NamedDimension getDimensionForWidthHeight(@Nullable ImageProfile imageProfile, long width, long height) { + Double requestedRatio = Ratio.get(width, height); + NamedDimension matchingDimension = getDimensionForRatio(imageProfile, requestedRatio); + if (matchingDimension != null) { + // create new named dimension with actual requested width/height + return new NamedDimension(matchingDimension.getName(), width, height); + } + else { + return null; + } + } + + /** + * Gets the actual smart-cropped dimension for the given asset and smart cropping definition (aspect ratio). + * @param asset Asset + * @param resourceResolver Resource resolver + * @param smartCropDef Smart cropping definition from image profile + * @return Actual dimension of the smart cropping area or null if not found + */ + @SuppressWarnings("java:S1075") // no filesystem paths + public static @Nullable CropDimension getCropDimensionForAsset(@NotNull Asset asset, + @NotNull ResourceResolver resourceResolver, @NotNull NamedDimension smartCropDef) { + // at this path smart cropping parameters may be stored for each ratio (esp. if manual cropping was applied) + String smartCropRenditionPath = asset.getPath() + + "/" + JCR_CONTENT + + "/" + RENDITIONS_FOLDER + + "/" + smartCropDef.getName() + + "/" + JCR_CONTENT; + Resource smartCropRendition = resourceResolver.getResource(smartCropRenditionPath); + if (smartCropRendition == null) { + // on AEMaaCS this path should always exist, in AEMaaCS SDK it seems to be created only when manual cropping + // is applied in the Assets UI + return null; + } + ValueMap props = smartCropRendition.getValueMap(); + double leftPercentage = props.get(PN_LEFT, 0d); + double topPercentage = props.get(PN_TOP, 0d); + double widthPercentage = props.get(PN_NORMALIZED_WIDTH, 0d); + double heightPercentage = props.get(PN_NORMALIZED_HEIGHT, 0d); + Dimension originalDimension = AssetRendition.getDimension(asset.getOriginal()); + if (originalDimension == null + || !isValidTopLeft(leftPercentage, topPercentage) + || !isValidWidthHeight(widthPercentage, heightPercentage)) { + // ignore smart cropping rendition with invalid dimension + return null; + } + + // calculate actual cropping dimension + long originalWidth = originalDimension.getWidth(); + long originalHeight = originalDimension.getHeight(); + long left = Math.round(originalWidth * leftPercentage); + long top = Math.round(originalHeight * topPercentage); + long width = Math.round(originalWidth * widthPercentage); + long height = Math.round(originalHeight * heightPercentage); + + // it may happen that DM provides inconsistent normalizedWidth/normalizedHeight values which results + // in renditions not matching the ratio of the cropping definition. In that case use only the one from the + // the two which results in the smaller rendition and calculate the missing value from the other + double expectedRatio = Ratio.get(smartCropDef.getWidth(), smartCropDef.getHeight()); + double actualRatio = Ratio.get(width, height); + if (!Ratio.matches(expectedRatio, actualRatio)) { + if (actualRatio > expectedRatio) { + width = Math.round(height * expectedRatio); + } + else { + height = Math.round(width / expectedRatio); + } + } + + return new CropDimension(left, top, width, height, true); + } + + /** + * Verifies that the actual image area picked in smart cropping (either automatic or manual) results in + * a rendition size that fulfills at least the requested width/height. + * @param asset DAM asset + * @param resourceResolver Resource resolve + * @param smartCropDef Smart cropping dimension + * @param width Requested width + * @param height Requested height + * @return true if size is matching, or no width/height information for the cropped area is available + */ + public static boolean isMatchingSize(@NotNull Asset asset, @NotNull ResourceResolver resourceResolver, + @NotNull NamedDimension smartCropDef, long width, long height) { + CropDimension cropDimension = getCropDimensionForAsset(asset, resourceResolver, smartCropDef); + if (cropDimension == null) { + // smart cropping rendition is not found in repository or it contains invalid values, + // we assume the size should be fine and skip further checking + return true; + } + + // check if smart cropping area is large enough + long croppedWidth = cropDimension.getWidth(); + long croppedHeight = cropDimension.getHeight(); + boolean isMatchingSize = (cropDimension.getWidth() >= width && croppedHeight >= height); + if (!isMatchingSize) { + log.debug("Smart cropping area '{}' for asset {} is too small ({} x {}) for requested size {} x {}.", + smartCropDef.getName(), asset.getPath(), croppedWidth, croppedHeight, width, height); + } + return isMatchingSize; + } + + private static boolean isValidTopLeft(double... numbers) { + return Arrays.stream(numbers).allMatch(value -> value >= 0 && value <= 1); + } + + private static boolean isValidWidthHeight(double... numbers) { + return Arrays.stream(numbers).allMatch(value -> value >= MIN_NORMALIZED_WIDTH_HEIGHT && value <= 1); + } + +} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/AssetSynchonizationService.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/AssetSynchonizationService.java index daba84dc..fe9e7d7c 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/AssetSynchonizationService.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/AssetSynchonizationService.java @@ -29,7 +29,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.util.concurrent.Striped; +import io.wcm.handler.mediasource.dam.impl.metadata.concurrency.StripedLazyWeakLock; /** * Synchronized the generation of rendition metadata through the ways (metadata service, workflow process) @@ -43,11 +43,11 @@ public final class AssetSynchonizationService { private static final Logger log = LoggerFactory.getLogger(AssetSynchonizationService.class); - private Striped lazyWeakLock; + private StripedLazyWeakLock lazyWeakLock; @Activate private void activate() { - lazyWeakLock = Striped.lazyWeakLock(STRIPE_COUNT); + lazyWeakLock = new StripedLazyWeakLock(STRIPE_COUNT); } @Deactivate diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataGenerator.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataGenerator.java index 39260f4a..1110e15a 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataGenerator.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataGenerator.java @@ -36,6 +36,7 @@ import java.util.Calendar; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.apache.commons.io.FilenameUtils; @@ -55,7 +56,6 @@ import com.day.cq.dam.api.Rendition; import com.day.cq.dam.api.handler.store.AssetStore; import com.day.image.Layer; -import com.google.common.collect.ImmutableMap; import io.wcm.handler.media.Dimension; import io.wcm.handler.media.MediaFileType; @@ -206,7 +206,7 @@ public boolean renditionAddedOrUpdated(String renditionPath) throws PersistenceE if (metadataResource == null) { metadataResource = ResourceUtil.getOrCreateResource(resourceResolver, metdataResourcePath, - ImmutableMap.of(JCR_PRIMARYTYPE, NT_UNSTRUCTURED), + Map.of(JCR_PRIMARYTYPE, NT_UNSTRUCTURED), null, false); } @@ -278,7 +278,7 @@ public boolean renditionRemoved(String renditionPath) throws PersistenceExceptio } /** - * Get dimension (with/height) of rendition. + * Get dimension (width/height) of rendition. * @param renditionResource Rendition * @return Dimension or null if it could not be detected */ diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataListenerService.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataListenerService.java index b0091959..e5dc9714 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataListenerService.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataListenerService.java @@ -22,6 +22,7 @@ import static com.day.cq.dam.api.DamConstants.ORIGINAL_FILE; import java.util.EnumSet; +import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -52,10 +53,9 @@ import com.day.cq.dam.api.DamEvent; import com.day.cq.dam.api.DamEvent.Type; import com.day.cq.dam.api.handler.store.AssetStore; -import com.google.common.collect.ImmutableMap; -import com.google.common.util.concurrent.ThreadFactoryBuilder; import io.wcm.handler.media.MediaFileType; +import io.wcm.handler.mediasource.dam.impl.metadata.concurrency.NamedThreadFactory; import io.wcm.wcm.commons.instancetype.InstanceTypeService; import io.wcm.wcm.commons.util.RunMode; @@ -130,7 +130,7 @@ private void activate(ComponentContext componentContext, Config config) { this.synchronousProcessing = config.threadPoolSize() <= 0; if (this.enabled && !this.synchronousProcessing) { this.executorService = Executors.newScheduledThreadPool(config.threadPoolSize(), - new ThreadFactoryBuilder().setNameFormat(getClass().getSimpleName() + "-%d").build()); + new NamedThreadFactory(getClass().getSimpleName())); } } @@ -223,7 +223,7 @@ public void run() { try { // open service user session for reading/writing rendition metadata serviceResourceResolver = resourceResolverFactory - .getServiceResourceResolver(ImmutableMap.of(ResourceResolverFactory.SUBSERVICE, SERVICEUSER_SUBSERVICE)); + .getServiceResourceResolver(Map.of(ResourceResolverFactory.SUBSERVICE, SERVICEUSER_SUBSERVICE)); // make sure asset resource exists Resource assetResource = serviceResourceResolver.getResource(assetPath); diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataNameConstants.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataNameConstants.java index b18845d8..8ed31e30 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataNameConstants.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/RenditionMetadataNameConstants.java @@ -30,7 +30,7 @@ public final class RenditionMetadataNameConstants { public static final String NN_RENDITIONS_METADATA = "renditionsMetadata"; /** - * Property for image with in pixels + * Property for image width in pixels */ public static final String PN_IMAGE_WIDTH = "imageWidth"; diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/NamedThreadFactory.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/NamedThreadFactory.java new file mode 100644 index 00000000..291b3d97 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/NamedThreadFactory.java @@ -0,0 +1,48 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2023 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.metadata.concurrency; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Creates new threads with a given formatted name, including a counter that is incremented for each new thread. + */ +public final class NamedThreadFactory implements ThreadFactory { + + private final String namePrefix; + private final AtomicLong counter = new AtomicLong(); + + /** + * @param namePrefix Prefix for thread name, will be suffixed with "-{number}". + */ + public NamedThreadFactory(String namePrefix) { + this.namePrefix = namePrefix; + } + + @Override + public Thread newThread(Runnable r) { + Thread thread = Executors.defaultThreadFactory().newThread(r); + thread.setName(namePrefix + "-" + counter.getAndIncrement()); + return thread; + } + +} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripeIndex.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripeIndex.java new file mode 100644 index 00000000..eb5178dd --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripeIndex.java @@ -0,0 +1,80 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2023 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.metadata.concurrency; + +/** + * Maps keys to a striped index set. Each key is mapped to a index within the max stripe count. + *

+ * The logic is extracted from Striped, + * initially written by Dimitris Andreou from the Guava team (Apache 2.0 license). + *

+ */ +final class StripeIndex { + + // Capacity (power of two) minus one, for fast mod evaluation + private final int mask; + private final int size; + + // A bit mask were all bits are set. + private static final int ALL_SET = ~0; + + // The largest power of two that can be represented as an {@code int}. + private static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2); + + /** + * @param stripes the minimum number of stripes required + */ + StripeIndex(int stripes) { + if (stripes <= 0) { + throw new IllegalArgumentException("Invalid number of stripes: " + stripes); + } + this.mask = stripes > MAX_POWER_OF_TWO ? ALL_SET : ceilToPowerOfTwo(stripes) - 1; + this.size = (mask == ALL_SET) ? Integer.MAX_VALUE : mask + 1; + } + + /** Returns the total number of stripes in this instance. */ + int size() { + return size; + } + + /** + * Returns the index to which the given key is mapped, so that getAt(indexFor(key)) == get(key). + */ + int indexFor(Object key) { + int hash = smear(key.hashCode()); + return hash & mask; + } + + private static int smear(int hashCode) { + int newHashCode = hashCode; + newHashCode ^= (newHashCode >>> 20) ^ (newHashCode >>> 12); + return newHashCode ^ (newHashCode >>> 7) ^ (newHashCode >>> 4); + } + + private static int ceilToPowerOfTwo(int x) { + return 1 << log2RoundCeiling(x); + } + + private static int log2RoundCeiling(int x) { + return Integer.SIZE - Integer.numberOfLeadingZeros(x - 1); + } + +} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripedLazyWeakLock.java b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripedLazyWeakLock.java new file mode 100644 index 00000000..a748ac48 --- /dev/null +++ b/src/main/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripedLazyWeakLock.java @@ -0,0 +1,66 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2023 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.metadata.concurrency; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; + +/** + * A striped {@code Lock}. This offers the underlying lock striping similar + * to that of {@code ConcurrentHashMap} in a reusable form. + * Conceptually, lock striping is the technique of dividing a lock into many + * stripes, increasing the granularity of a single lock and allowing independent operations + * to lock different stripes and proceed concurrently, instead of creating contention for a single + * lock. + *

+ * This is inspired by Guava's Striped, + * but uses Caffeine internally. + *

+ */ +public final class StripedLazyWeakLock { + + private final StripeIndex stripeIndex; + private final LoadingCache locks; + + /** + * Creates a {@code Striped} with lazily initialized, weakly referenced locks. Every lock is + * reentrant. + * @param stripes the minimum number of stripes (locks) required + */ + public StripedLazyWeakLock(int stripes) { + this.stripeIndex = new StripeIndex(stripes); + this.locks = Caffeine.newBuilder().weakValues().build(key -> new ReentrantLock()); + } + + /** + * Returns the stripe that corresponds to the passed key. It is always guaranteed that if {@code + * key1.equals(key2)}, then {@code get(key1) == get(key2)}. + * @param key an arbitrary, non-null key + * @return the stripe that the passed key corresponds to + */ + public Lock get(Object key) { + return locks.get(stripeIndex.indexFor(key)); + } + +} diff --git a/src/main/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilder.java b/src/main/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilder.java index 8536a499..4227df35 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilder.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/markup/DamVideoMediaMarkupBuilder.java @@ -19,16 +19,11 @@ */ package io.wcm.handler.mediasource.dam.markup; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -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; @@ -42,12 +37,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.day.cq.commons.jcr.JcrConstants; import com.day.cq.dam.api.Asset; import com.day.cq.dam.commons.util.PrefixRenditionPicker; import com.day.cq.dam.video.VideoConstants; import com.day.cq.dam.video.VideoProfile; -import com.google.common.collect.ImmutableList; import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.commons.dom.Video; @@ -56,7 +49,6 @@ import io.wcm.handler.media.markup.MediaMarkupBuilderUtil; import io.wcm.handler.media.spi.MediaMarkupBuilder; import io.wcm.handler.url.UrlHandler; -import io.wcm.wcm.commons.contenttype.ContentType; /** * Default implementation of {@link MediaMarkupBuilder} for DAM video assets. @@ -71,7 +63,7 @@ public class DamVideoMediaMarkupBuilder implements MediaMarkupBuilder { private static final String OGG_PROFILE = "format_ogg"; private static final String LEGACY_H264_PROFILE = "hq"; // for AEM 6.3 private static final String LEGACY_OGG_PROFILE = "firefoxhq"; // for AEM 6.3 - private static final List VIDEO_PROFILE_NAMES = ImmutableList.of(H264_PROFILE, OGG_PROFILE, + private static final List VIDEO_PROFILE_NAMES = List.of(H264_PROFILE, OGG_PROFILE, LEGACY_H264_PROFILE, LEGACY_OGG_PROFILE); private static final Logger log = LoggerFactory.getLogger(DamVideoMediaMarkupBuilder.class); @@ -149,8 +141,8 @@ protected Video getVideoPlayerElement(@NotNull Media media) { Dimension dimension = MediaMarkupBuilderUtil.getMediaformatDimension(media); Video video = new Video(); - video.setWidth((int)dimension.getWidth()); - video.setHeight((int)dimension.getHeight()); + video.setWidth(dimension.getWidth()); + video.setHeight(dimension.getHeight()); video.setControls(true); // add video sources for each video profile @@ -180,91 +172,6 @@ protected void addSources(Video video, Media media) { } } - /** - * Build flash player element - * @param media Media metadata - * @param dimension Dimension - * @return Media element - * @deprecated Usage of flash for video player fallback is deprecated - */ - @Deprecated - protected HtmlElement getFlashPlayerElement(Media media, Dimension dimension) { - Asset asset = getDamAsset(media); - if (asset == null) { - return null; - } - - com.day.cq.dam.api.Rendition rendition = asset.getRendition(new PrefixRenditionPicker(VideoConstants.RENDITION_PREFIX + H264_PROFILE)); - if (rendition == null) { - rendition = asset.getRendition(new PrefixRenditionPicker(VideoConstants.RENDITION_PREFIX + LEGACY_H264_PROFILE)); - if (rendition == null) { - return null; - } - } - - String playerUrl = urlHandler.get("/etc/clientlibs/foundation/video/swf/StrobeMediaPlayback.swf") - .buildExternalResourceUrl(); - - // strobe specialty: path must be relative to swf file - String renditionUrl = "../../../../.." + rendition.getPath(); - - // manually apply jcr_content namespace mangling - renditionUrl = StringUtils.replace(renditionUrl, JcrConstants.JCR_CONTENT, "_jcr_content"); - - HtmlElement object = new HtmlElement("object"); - object.setAttribute("type", ContentType.SWF); - object.setAttribute("data", playerUrl); - object.setAttribute("width", Long.toString(dimension.getWidth())); - object.setAttribute("height", Long.toString(dimension.getHeight())); - - // get flashvars - Map flashvars = new HashMap<>(); - flashvars.put("src", renditionUrl); - flashvars.putAll(getAdditionalFlashPlayerFlashVars(media, dimension)); - - // get flash parameters - Map parameters = new HashMap<>(); - parameters.put("movie", playerUrl); - parameters.put("flashvars", buildFlashVarsString(flashvars)); - parameters.putAll(getAdditionalFlashPlayerParameters(media, dimension)); - - // set parameters on object element - for (Map.Entry entry : parameters.entrySet()) { - HtmlElement param = object.create("param"); - param.setAttribute("name", entry.getKey()); - param.setAttribute("value", entry.getValue()); - } - - return object; - } - - /** - * Build flashvars string to be used on HTML object element for flash embedding. - * @param flashVars flashvars map - * @return flashvars string with proper encoding - * @deprecated Usage of flash for video player fallback is deprecated - */ - @Deprecated - protected String buildFlashVarsString(Map flashVars) { - try { - StringBuilder flashvarsString = new StringBuilder(); - Iterator> flashvarsIterator = flashVars.entrySet().iterator(); - while (flashvarsIterator.hasNext()) { - Map.Entry entry = flashvarsIterator.next(); - flashvarsString.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name())); - flashvarsString.append('='); - flashvarsString.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name())); - if (flashvarsIterator.hasNext()) { - flashvarsString.append('&'); - } - } - return flashvarsString.toString(); - } - catch (UnsupportedEncodingException ex) { - throw new RuntimeException("Unsupported encoding.", ex); - } - } - /** * Get additional parameters to be set as <param> elements on html object element for flash player. * @param media Media metadata diff --git a/src/main/java/io/wcm/handler/mediasource/dam/markup/package-info.java b/src/main/java/io/wcm/handler/mediasource/dam/markup/package-info.java index 0008873e..6fcce5f6 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/markup/package-info.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/markup/package-info.java @@ -20,5 +20,5 @@ /** * Default media markup implementations for DAM. */ -@org.osgi.annotation.versioning.Version("1.0.1") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.mediasource.dam.markup; diff --git a/src/main/java/io/wcm/handler/mediasource/dam/package-info.java b/src/main/java/io/wcm/handler/mediasource/dam/package-info.java index 09a70799..43882982 100644 --- a/src/main/java/io/wcm/handler/mediasource/dam/package-info.java +++ b/src/main/java/io/wcm/handler/mediasource/dam/package-info.java @@ -20,5 +20,5 @@ /** * Media source implementation for assets stored in DAM. */ -@org.osgi.annotation.versioning.Version("1.5") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.mediasource.dam; diff --git a/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java b/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java index 09253564..3a8ffa28 100644 --- a/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java +++ b/src/main/java/io/wcm/handler/mediasource/inline/InlineAsset.java @@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull; import io.wcm.handler.media.Asset; +import io.wcm.handler.media.Dimension; import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.MediaFileType; @@ -124,18 +125,6 @@ public Rendition getImageRendition(@NotNull MediaArgs mediaArgs) { } } - @SuppressWarnings("deprecation") - @Override - public Rendition getFlashRendition(@NotNull MediaArgs mediaArgs) { - Rendition rendition = getRendition(mediaArgs); - if (rendition != null && rendition.isFlash()) { - return rendition; - } - else { - return null; - } - } - @Override public Rendition getDownloadRendition(@NotNull MediaArgs mediaArgs) { Rendition rendition = getRendition(mediaArgs); @@ -173,8 +162,9 @@ public AdapterType adaptTo(Class type) { throw new UnsupportedOperationException("Unable to build URI template for this asset type: " + getPath()); } Rendition originalRendition = getInlineRendition(new MediaArgs()); - return new InlineUriTemplate(type, originalRendition.getWidth(), originalRendition.getHeight(), - this.resource, fileName, defaultMediaArgs, adaptable); + Dimension dimension = new Dimension(originalRendition.getWidth(), originalRendition.getHeight()); + return new InlineUriTemplate(type, dimension, this.resource, fileName, + null, null, defaultMediaArgs, adaptable); } @Override diff --git a/src/main/java/io/wcm/handler/mediasource/inline/InlineRendition.java b/src/main/java/io/wcm/handler/mediasource/inline/InlineRendition.java index d522c37f..14cf7e82 100644 --- a/src/main/java/io/wcm/handler/mediasource/inline/InlineRendition.java +++ b/src/main/java/io/wcm/handler/mediasource/inline/InlineRendition.java @@ -49,6 +49,8 @@ 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.format.MediaFormat; import io.wcm.handler.media.format.Ratio; import io.wcm.handler.media.format.impl.MediaFormatSupport; @@ -74,6 +76,7 @@ class InlineRendition extends SlingAdaptable implements Rendition { private final String fileExtension; private final String originalFileExtension; private final Dimension imageDimension; + private final Dimension maxImageDimension; private final String url; private CropDimension cropDimension; private final Integer rotation; @@ -110,6 +113,7 @@ class InlineRendition extends SlingAdaptable implements Rendition { boolean isVectorImage = MediaFileType.isVectorImage(this.originalFileExtension); Dimension dimension = null; + Dimension maxDimension = null; Dimension scaledDimension = null; String processedFileName = fileName; if (isImage) { @@ -117,6 +121,7 @@ class InlineRendition extends SlingAdaptable implements Rendition { List dimensionCandidates = getImageOrCroppedDimensions(); for (int i = 0; i < dimensionCandidates.size(); i++) { dimension = dimensionCandidates.get(i); + maxDimension = dimension; if (isVectorImage && (this.rotation != null || this.cropDimension != null)) { // transformation not possible for vector images scaledDimension = SCALING_NOT_POSSIBLE_DIMENSION; @@ -149,6 +154,7 @@ class InlineRendition extends SlingAdaptable implements Rendition { List autoCropDimensions = autoCropping.calculateAutoCropDimensions(); for (CropDimension autoCropDimension : autoCropDimensions) { scaledDimension = getScaledDimension(autoCropDimension); + maxDimension = autoCropDimension; if (scaledDimension == null) { scaledDimension = autoCropDimension; } @@ -169,13 +175,15 @@ class InlineRendition extends SlingAdaptable implements Rendition { this.fileName = processedFileName; this.fileExtension = FilenameUtils.getExtension(processedFileName); this.imageDimension = dimension; + this.maxImageDimension = maxDimension; // build media url (it is null if no rendition is available for the given media args) this.url = buildMediaUrl(scaledDimension); // set first media format as resolved format - because only the first is supported - if (url != null && mediaArgs.getMediaFormats() != null && mediaArgs.getMediaFormats().length > 0) { - this.resolvedMediaFormat = mediaArgs.getMediaFormats()[0]; + MediaFormat[] mediaFormats = mediaArgs.getMediaFormats(); + if (url != null && mediaFormats != null && mediaFormats.length > 0) { + this.resolvedMediaFormat = mediaFormats[0]; } } @@ -246,10 +254,10 @@ private Dimension getImageDimension() { // calculate missing width/height from ratio if not specified if (requestedWidth == 0 && requestedHeight > 0) { - requestedWidth = (int)Math.round(requestedHeight * imageRatio); + requestedWidth = Math.round(requestedHeight * imageRatio); } else if (requestedWidth > 0 && requestedHeight == 0) { - requestedHeight = (int)Math.round(requestedWidth / imageRatio); + requestedHeight = Math.round(requestedWidth / imageRatio); } // calculate requested ratio @@ -473,10 +481,8 @@ private double getRequestedRatio() { // check for dimensions from mediaformat (evaluate only first media format) MediaFormat[] mediaFormats = mediaArgs.getMediaFormats(); - if (mediaFormats != null && mediaFormats.length > 0) { - if (mediaFormats[0].getRatio() > 0) { - return mediaFormats[0].getRatio(); - } + if (mediaFormats != null && mediaFormats.length > 0 && mediaFormats[0].getRatio() > 0) { + return mediaFormats[0].getRatio(); } // no ratio @@ -569,15 +575,9 @@ public boolean isVectorImage() { return MediaFileType.isVectorImage(getFileExtension()); } - @Override - @SuppressWarnings("deprecation") - public boolean isFlash() { - return MediaFileType.isFlash(getFileExtension()); - } - @Override public boolean isDownload() { - return !isImage() && !isFlash(); + return !isImage(); } @Override @@ -605,6 +605,23 @@ public boolean isFallback() { return fallback; } + @Override + public @NotNull UriTemplate getUriTemplate(@NotNull UriTemplateType type) { + if (type == UriTemplateType.CROP_CENTER) { + throw new IllegalArgumentException("CROP_CENTER not supported for rendition URI templates."); + } + if (!isImage() || isVectorImage()) { + throw new UnsupportedOperationException("Unable to build URI template for " + resource.getPath()); + } + if (this.maxImageDimension == null) { + throw new IllegalStateException("Unable to detect dimension for inline image: " + resource.getPath()); + } + + Dimension dimension = ImageTransformation.rotateMapDimension(maxImageDimension, rotation); + return new InlineUriTemplate(type, dimension, this.resource, fileName, + this.cropDimension, this.rotation, mediaArgs, adaptable); + } + @Override @SuppressWarnings({ "unchecked", "null" }) public AdapterType adaptTo(Class type) { diff --git a/src/main/java/io/wcm/handler/mediasource/inline/InlineUriTemplate.java b/src/main/java/io/wcm/handler/mediasource/inline/InlineUriTemplate.java index 72dc074f..a48dac84 100644 --- a/src/main/java/io/wcm/handler/mediasource/inline/InlineUriTemplate.java +++ b/src/main/java/io/wcm/handler/mediasource/inline/InlineUriTemplate.java @@ -26,7 +26,10 @@ import org.apache.sling.api.adapter.Adaptable; import org.apache.sling.api.resource.Resource; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.Dimension; import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.UriTemplate; import io.wcm.handler.media.UriTemplateType; @@ -40,19 +43,21 @@ class InlineUriTemplate implements UriTemplate { private final String uriTemplate; private final UriTemplateType type; - private final long width; - private final long height; - - InlineUriTemplate(@NotNull UriTemplateType type, long width, long height, - @NotNull Resource resource, @NotNull String fileName, @NotNull MediaArgs mediaArgs, @NotNull Adaptable adaptable) { - this.uriTemplate = buildUriTemplate(type, resource, fileName, mediaArgs, adaptable); + private final Dimension dimension; + + @SuppressWarnings("java:S107") // allow more than 7 params + InlineUriTemplate(@NotNull UriTemplateType type, @NotNull Dimension dimension, + @NotNull Resource resource, @NotNull String fileName, + @Nullable CropDimension cropDimension, @Nullable Integer rotation, + @NotNull MediaArgs mediaArgs, @NotNull Adaptable adaptable) { + this.uriTemplate = buildUriTemplate(type, resource, fileName, cropDimension, rotation, mediaArgs, adaptable); this.type = type; - this.width = width; - this.height = height; + this.dimension = dimension; } private static String buildUriTemplate(@NotNull UriTemplateType type, @NotNull Resource resource, - @NotNull String fileName, @NotNull MediaArgs mediaArgs, @NotNull Adaptable adaptable) { + @NotNull String fileName, @Nullable CropDimension cropDimension, @Nullable Integer rotation, + @NotNull MediaArgs mediaArgs, @NotNull Adaptable adaptable) { String resourcePath = resource.getPath(); // if parent resource is a nt:file resource, use this one as path for scaled image @@ -65,10 +70,10 @@ private static String buildUriTemplate(@NotNull UriTemplateType type, @NotNull R final long DUMMY_WIDTH = 999991; final long DUMMY_HEIGHT = 999992; String path = resourcePath - + "." + ImageFileServlet.buildSelectorString(DUMMY_WIDTH, DUMMY_HEIGHT, null, null, false) - + "." + MediaFileServlet.EXTENSION + "/" + + "." + ImageFileServlet.buildSelectorString(DUMMY_WIDTH, DUMMY_HEIGHT, cropDimension, rotation, false) + + "." + MediaFileServlet.EXTENSION // replace extension based on the format supported by ImageFileServlet for rendering for this rendition - + ImageFileServlet.getImageFileName(fileName, mediaArgs.getEnforceOutputFileExtension()); + + "/" + ImageFileServlet.getImageFileName(fileName, mediaArgs.getEnforceOutputFileExtension()); // build externalized URL UrlHandler urlHandler = AdaptTo.notNull(adaptable, UrlHandler.class); @@ -94,23 +99,23 @@ private static String buildUriTemplate(@NotNull UriTemplateType type, @NotNull R } @Override - public String getUriTemplate() { - return uriTemplate; + public UriTemplateType getType() { + return type; } @Override - public UriTemplateType getType() { - return type; + public String getUriTemplate() { + return uriTemplate; } @Override public long getMaxWidth() { - return width; + return dimension.getWidth(); } @Override public long getMaxHeight() { - return height; + return dimension.getHeight(); } @Override diff --git a/src/main/java/io/wcm/handler/mediasource/inline/package-info.java b/src/main/java/io/wcm/handler/mediasource/inline/package-info.java index 26f5a7dc..be5154f6 100644 --- a/src/main/java/io/wcm/handler/mediasource/inline/package-info.java +++ b/src/main/java/io/wcm/handler/mediasource/inline/package-info.java @@ -20,5 +20,5 @@ /** * Media source implementation for binaries stored inline in the page content. */ -@org.osgi.annotation.versioning.Version("1.4") +@org.osgi.annotation.versioning.Version("2.0.0") package io.wcm.handler.mediasource.inline; diff --git a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js index 299bc154..67ac8cb2 100644 --- a/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js +++ b/src/main/webapp/app-root/clientlibs/authoring/dialog/js/fileupload.js @@ -96,8 +96,8 @@ var mimeType = self._detectMimeType(assetPath); var thumbnailObject; if (mimeType) { - var thumbnailUrl = assetPath + "/jcr:content/renditions/cq5dam.thumbnail.319.319.png"; - thumbnailObject = $(""); + var thumbnailUrl = assetPath + ".thumb.319.319.png?ck=" + new Date().getTime(); + thumbnailObject = $("").attr({"src": thumbnailUrl}); } self._$element.trigger($.Event("assetselected", { path: assetPath, @@ -139,6 +139,12 @@ if (fileExtension == "svg") { return "image/svg+xml"; } + if (fileExtension == "mp4") { + return "video/mpeg"; + } + if (fileExtension == "mov") { + return "video/quicktime"; + } return null; }; diff --git a/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp b/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp index 2e22a0c1..740502d3 100644 --- a/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp +++ b/src/main/webapp/app-root/components/granite/form/fileupload/fileupload.jsp @@ -145,7 +145,7 @@ if (contentResource != null) { propFileReferenceDefault = namePrefix + mediaHandlerConfig.getMediaRefProperty(); // check if any transformations are defined - ValueMap contentProps = contentResource.getValueMap(); + ValueMap contentProps = getPropertiesContentResource(contentResource, namePrefix).getValueMap(); hasTransformation = (contentProps.get(mediaHandlerConfig.getMediaCropProperty(), String.class) != null) || (contentProps.get(mediaHandlerConfig.getMediaRotationProperty(), String.class) != null) || (contentProps.get(mediaHandlerConfig.getMediaMapProperty(), String.class) != null); @@ -159,20 +159,21 @@ fileUploadProps.put("fileReferenceParameter", cfg.get("fileReferenceParameter", // default values for allowUpload and mimeTypes fileUploadProps.put("allowUpload", ex.getBoolean(cfg.get("allowUpload", String.class))); fileUploadProps.put("mimeTypes", cfg.get("mimeTypes", new String[] { - "image/gif","image/jpeg","image/png","image/tiff","image/svg+xml" })); + "image/gif","image/jpeg","image/png","image/tiff","image/svg+xml","video/mpeg","video/quicktime"})); // media format properties for validation of associated media reference String[] mediaFormats = null; String[] mediaFormatsMandatory = null; boolean mediaCropAuto = false; if (contentResource != null) { - MediaComponentPropertyResolver componentPropertyResolver = new MediaComponentPropertyResolver(contentResource); - mediaFormats = getStringArrayWithExpressionSupport("mediaFormats", - MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS, cfg, ex, componentPropertyResolver.getMediaFormatNames()); - mediaFormatsMandatory = getStringArrayWithExpressionSupport("mediaFormatsMandatory", - MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS_MANDATORY, cfg, ex, componentPropertyResolver.getMandatoryMediaFormatNames()); - mediaCropAuto = getBooleanWithExpressionSupport("mediaCropAuto", - MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP, cfg, ex, componentPropertyResolver.isAutoCrop()); + try (MediaComponentPropertyResolver componentPropertyResolver = contentResource.adaptTo(MediaComponentPropertyResolver.class)) { + mediaFormats = getStringArrayWithExpressionSupport("mediaFormats", + MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS, cfg, ex, componentPropertyResolver.getMediaFormatNames()); + mediaFormatsMandatory = getStringArrayWithExpressionSupport("mediaFormatsMandatory", + MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS_MANDATORY, cfg, ex, componentPropertyResolver.getMandatoryMediaFormatNames()); + mediaCropAuto = getBooleanWithExpressionSupport("mediaCropAuto", + MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP, cfg, ex, componentPropertyResolver.isAutoCrop()); + } // add info about media formats in field description String mediaFormatsFieldDescription = buildMediaFormatsFieldDescription(mediaFormats, contentResource, i18n); @@ -228,4 +229,25 @@ if (!dataProps.isEmpty()) { dispatcher = slingRequest.getRequestDispatcher(pathField); dispatcher.include(slingRequest, slingResponse); +%><%! +/** + * With setting nameprefix to a subnode like "./mySubNode/" it's possible to store the media reference + * and the related transformation paramters in another node. This method tries to detect this, and + * returns the child node where the properties are stored in. + * @param contentResource Content resource of the current edit dialog + * @return Sub node content resource or the current content resource + */ +Resource getPropertiesContentResource(Resource contentResource, String namePrefix) { + // the prefix is expected to prefix existing property names. use a dummy property name to get the parent path - + // which may be the same path of the content resource, or pointint to a sub resource + String dummyPropertyPath = namePrefix + "dummy"; + String subResourcePath = ResourceUtil.getParent(dummyPropertyPath); + Resource propertiesContentResource = contentResource.getChild(subResourcePath); + if (propertiesContentResource != null) { + return propertiesContentResource; + } + else { + return contentResource; + } +} %> diff --git a/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp b/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp index 883fae67..d516f731 100644 --- a/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp +++ b/src/main/webapp/app-root/components/granite/form/pathfield/pathfield.jsp @@ -132,13 +132,14 @@ String[] mediaFormats = null; String[] mediaFormatsMandatory = null; boolean mediaCropAuto = false; if (contentResource != null) { - MediaComponentPropertyResolver componentPropertyResolver = new MediaComponentPropertyResolver(contentResource); - mediaFormats = getStringArrayWithExpressionSupport("mediaFormats", - MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS, cfg, ex, componentPropertyResolver.getMediaFormatNames()); - mediaFormatsMandatory = getStringArrayWithExpressionSupport("mediaFormatsMandatory", - MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS_MANDATORY, cfg, ex, componentPropertyResolver.getMandatoryMediaFormatNames()); - mediaCropAuto = getBooleanWithExpressionSupport("mediaCropAuto", - MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP, cfg, ex, componentPropertyResolver.isAutoCrop()); + try (MediaComponentPropertyResolver componentPropertyResolver = contentResource.adaptTo(MediaComponentPropertyResolver.class)) { + mediaFormats = getStringArrayWithExpressionSupport("mediaFormats", + MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS, cfg, ex, componentPropertyResolver.getMediaFormatNames()); + mediaFormatsMandatory = getStringArrayWithExpressionSupport("mediaFormatsMandatory", + MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS_MANDATORY, cfg, ex, componentPropertyResolver.getMandatoryMediaFormatNames()); + mediaCropAuto = getBooleanWithExpressionSupport("mediaCropAuto", + MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP, cfg, ex, componentPropertyResolver.isAutoCrop()); + } // add info about media formats in field description String mediaFormatsFieldDescription = buildMediaFormatsFieldDescription(mediaFormats, contentResource, i18n); diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 383f52ab..29a9800b 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -43,6 +43,7 @@ Read the [general concepts][general-concepts] to get an overview of the function |Media Handler version |AEM version supported |----------------------|---------------------- +|2.0.x or higher |AEM 6.5.17+, AEMaaCS |1.14.4 or higher |AEM 6.5.7+, AEMaaCS |1.14.0 - 1.14.2 |AEM 6.5+, AEMaaCS |1.10.x - 1.13.x |AEM 6.4.5+, AEMaaCS diff --git a/src/test/java/io/wcm/handler/media/MediaArgsTest.java b/src/test/java/io/wcm/handler/media/MediaArgsTest.java index 7480013b..7ffec4a3 100644 --- a/src/test/java/io/wcm/handler/media/MediaArgsTest.java +++ b/src/test/java/io/wcm/handler/media/MediaArgsTest.java @@ -34,8 +34,6 @@ import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableMap; - import io.wcm.handler.media.MediaArgs.ImageSizes; import io.wcm.handler.media.MediaArgs.MediaFormatOption; import io.wcm.handler.media.MediaArgs.PictureSource; @@ -49,7 +47,6 @@ class MediaArgsTest { @Test - @SuppressWarnings("deprecation") void testGetMediaFormats() { MediaArgs mediaArgs; @@ -57,32 +54,27 @@ void testGetMediaFormats() { assertArrayEquals(new MediaFormat[] { EDITORIAL_1COL }, mediaArgs.getMediaFormats()); - assertFalse(mediaArgs.isMediaFormatsMandatory()); mediaArgs = new MediaArgs("editorial_1col"); assertArrayEquals(new String[] { "editorial_1col" }, mediaArgs.getMediaFormatNames()); - assertFalse(mediaArgs.isMediaFormatsMandatory()); mediaArgs = new MediaArgs(EDITORIAL_1COL, EDITORIAL_2COL); assertArrayEquals(new MediaFormat[] { EDITORIAL_1COL, EDITORIAL_2COL }, mediaArgs.getMediaFormats()); - assertFalse(mediaArgs.isMediaFormatsMandatory()); mediaArgs = new MediaArgs("editorial_1col", "editorial_2col"); assertArrayEquals(new String[] { "editorial_1col", "editorial_2col" }, mediaArgs.getMediaFormatNames()); - assertFalse(mediaArgs.isMediaFormatsMandatory()); assertNull(new MediaArgs().mediaFormat((MediaFormat)null).getMediaFormats()); assertNull(new MediaArgs().mediaFormatName((String)null).getMediaFormatNames()); } @Test - @SuppressWarnings("deprecation") void testGetMediaFormatsMandatory() { MediaArgs mediaArgs; @@ -90,13 +82,11 @@ void testGetMediaFormatsMandatory() { assertArrayEquals(new MediaFormat[] { EDITORIAL_1COL, EDITORIAL_2COL }, mediaArgs.getMediaFormats()); - assertTrue(mediaArgs.isMediaFormatsMandatory()); mediaArgs = new MediaArgs().mandatoryMediaFormatNames("editorial_1col", "editorial_2col"); assertArrayEquals(new String[] { "editorial_1col", "editorial_2col" }, mediaArgs.getMediaFormatNames()); - assertTrue(mediaArgs.isMediaFormatsMandatory()); } @Test @@ -130,7 +120,7 @@ void testGetFileExtensions() { @Test void testGetProperties() { - Map props = ImmutableMap.of("prop1", "value1"); + Map props = Map.of("prop1", "value1"); MediaArgs mediaArgs = new MediaArgs() .property("prop3", "value3") diff --git a/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java b/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java index 7748fd6d..5b5f3a0b 100644 --- a/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java +++ b/src/test/java/io/wcm/handler/media/MediaComponentPropertyResolverTest.java @@ -31,8 +31,6 @@ import static io.wcm.handler.media.MediaComponentPropertyResolver.RESPONSIVE_TYPE_PICTURE_SOURCES; import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVEIMAGE_SIZES; import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVEPICTURE_SOURCES; -import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES; -import static io.wcm.handler.media.MediaNameConstants.NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES; import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_AUTOCROP; import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS; import static io.wcm.handler.media.MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS_MANDATORY; @@ -46,8 +44,11 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; + import org.apache.sling.api.resource.Resource; -import org.junit.jupiter.api.BeforeEach; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.wrappers.ValueMapDecorator; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -56,9 +57,9 @@ import io.wcm.handler.media.MediaArgs.PictureSource; import io.wcm.handler.media.MediaArgs.WidthOption; import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.sling.commons.adapter.AdaptTo; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; -import io.wcm.wcm.commons.component.ComponentPropertyResolverFactory; @ExtendWith(AemContextExtension.class) class MediaComponentPropertyResolverTest { @@ -67,19 +68,12 @@ class MediaComponentPropertyResolverTest { private final AemContext context = AppAemContext.newAemContext(); - private ComponentPropertyResolverFactory componentPropertyResolverFactory; - - @BeforeEach - void setUp() { - componentPropertyResolverFactory = context.getService(ComponentPropertyResolverFactory.class); - } - @Test void testIsAutoCrop_Default() throws Exception { Resource resource = context.create().resource("/content/r1"); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertFalse(underTest.isAutoCrop()); } } @@ -92,7 +86,7 @@ void testIsAutoCrop_Component() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertTrue(underTest.isAutoCrop()); } } @@ -108,7 +102,7 @@ void testIsAutoCrop_Component_Subresource() throws Exception { Resource subresource2 = context.create().resource(subresource1, "subresource2"); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(subresource2, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(subresource2, MediaComponentPropertyResolver.class)) { assertTrue(underTest.isAutoCrop()); } } @@ -124,11 +118,20 @@ void testIsAutoCrop_Component_Policy() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertFalse(underTest.isAutoCrop()); } } + @Test + void testIsAutoCrop_ValueMap() throws Exception { + ValueMap valueMap = new ValueMapDecorator(Map.of(PN_COMPONENT_MEDIA_AUTOCROP, true)); + + try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(valueMap)) { + assertTrue(underTest.isAutoCrop()); + } + } + @Test void testGetMediaFormatOptions_Single() throws Exception { context.create().resource(RESOURCE_TYPE, @@ -137,7 +140,7 @@ void testGetMediaFormatOptions_Single() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new MediaFormatOption[] { new MediaFormatOption("home_stage", false) }, underTest.getMediaFormatOptions()); @@ -152,7 +155,7 @@ void testGetMediaFormatOptions_Multi() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new MediaFormatOption[] { new MediaFormatOption("home_stage", false), new MediaFormatOption("home_teaser", false) @@ -175,7 +178,7 @@ void testGetMediaFormatOptions_Multi_MandatoryLegacy() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new MediaFormatOption[] { new MediaFormatOption("home_stage", true), new MediaFormatOption("home_teaser", true) @@ -201,7 +204,7 @@ void testGetMediaFormatOptions_Multi_Mandatory() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new MediaFormatOption[] { new MediaFormatOption("home_stage", true), new MediaFormatOption("home_teaser", false) @@ -226,7 +229,7 @@ void testGetMediaFormatOptions_Multi_Mandatory_Names() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new MediaFormatOption[] { new MediaFormatOption("home_stage", true), new MediaFormatOption("home_teaser", false), @@ -272,7 +275,7 @@ void testGetImageSizes_NotExisting() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getImageSizes()); } } @@ -285,7 +288,7 @@ void testGetImageSizes_Empty() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getImageSizes()); } } @@ -300,7 +303,7 @@ void testGetImageSizes_Valid() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertEquals(new ImageSizes("sizes1", new WidthOption[] { new WidthOption(200, true), new WidthOption(400, true), @@ -319,7 +322,7 @@ void testGetImageSizes_Invalid() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getImageSizes()); } } @@ -335,7 +338,7 @@ void testGetImageSizes_Valid_Active() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertEquals(new ImageSizes("sizes1", 200, 400), underTest.getImageSizes()); } } @@ -351,96 +354,11 @@ void testGetImageSizes_Valid_NotActive() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertNull(underTest.getImageSizes()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetImageSizes_Empty_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE); - context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertNull(underTest.getImageSizes()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetImageSizes_Valid_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE); - context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES, - PN_IMAGES_SIZES_SIZES, "sizes1", - PN_IMAGES_SIZES_WIDTHS, "200,400,600?"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertEquals(new ImageSizes("sizes1", new WidthOption[] { - new WidthOption(200, true), - new WidthOption(400, true), - new WidthOption(600, false), - }), underTest.getImageSizes()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetImageSizes_Invalid_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE); - context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES, - PN_IMAGES_SIZES_SIZES, "sizes1", - PN_IMAGES_SIZES_WIDTHS, "wurst?"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertNull(underTest.getImageSizes()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetImageSizes_Valid_Active_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE, - PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, RESPONSIVE_TYPE_IMAGE_SIZES); - context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES, - PN_IMAGES_SIZES_SIZES, "sizes1", - PN_IMAGES_SIZES_WIDTHS, "200,400"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertEquals(new ImageSizes("sizes1", 200, 400), underTest.getImageSizes()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetImageSizes_Valid_NotActive_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE, - PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, RESPONSIVE_TYPE_PICTURE_SOURCES); - context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_IMAGE_SIZES, - PN_IMAGES_SIZES_SIZES, "sizes1", - PN_IMAGES_SIZES_WIDTHS, "200,400"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getImageSizes()); } } - @Test void testGetPictureSources_NotExisting() throws Exception { context.create().resource(RESOURCE_TYPE); @@ -448,7 +366,7 @@ void testGetPictureSources_NotExisting() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getPictureSources()); } } @@ -461,7 +379,7 @@ void testGetPictureSources_Empty() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getImageSizes()); } } @@ -482,7 +400,7 @@ void testGetPictureSources_Valid() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new PictureSource[] { new PictureSource("home_stage") .media("media1") @@ -511,7 +429,7 @@ void testGetPictureSources_Invalid() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getPictureSources()); } } @@ -529,7 +447,7 @@ void testGetPictureSources_Valid_Active() throws Exception { context.resourceResolver().commit(); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertArrayEquals(new PictureSource[] { new PictureSource("home_stage").widths(200, 400) }, underTest.getPictureSources()); @@ -548,111 +466,7 @@ void testGetPictureSources_Valid_Inactive() throws Exception { PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); context.resourceResolver().commit(); - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertNull(underTest.getPictureSources()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetPictureSources_Empty_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE); - context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertNull(underTest.getImageSizes()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetPictureSources_Valid_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE); - Resource sources = context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES); - context.create().resource(sources, "source1", - PN_PICTURE_SOURCES_MEDIAFORMAT, "home_stage", - PN_PICTURE_SOURCES_MEDIA, "media1", - PN_PICTURE_SOURCES_SIZES, "sizes1", - PN_PICTURE_SOURCES_WIDTHS, "200,400?"); - context.create().resource(sources, "source2", - PN_PICTURE_SOURCES_MEDIAFORMAT, "home_teaser", - PN_PICTURE_SOURCES_WIDTHS, "200,300"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertArrayEquals(new PictureSource[] { - new PictureSource("home_stage") - .media("media1") - .sizes("sizes1") - .widthOptions(new WidthOption[] { - new WidthOption(200, true), - new WidthOption(400, false) - }), - new PictureSource("home_teaser") - .widths(200, 300) - }, underTest.getPictureSources()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetPictureSources_Invalid_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE); - Resource sources = context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES); - context.create().resource(sources, "source1", - PN_PICTURE_SOURCES_MEDIAFORMAT, "home_stage", - PN_PICTURE_SOURCES_MEDIA, "media1", - PN_PICTURE_SOURCES_WIDTHS, "jodel,kaiser"); - context.create().resource(sources, "source2", - PN_PICTURE_SOURCES_WIDTHS, "200,300"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertNull(underTest.getPictureSources()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetPictureSources_Valid_Active_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE, - PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, RESPONSIVE_TYPE_PICTURE_SOURCES); - Resource sources = context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES); - context.create().resource(sources, "source1", - PN_PICTURE_SOURCES_MEDIAFORMAT, "home_stage", - PN_PICTURE_SOURCES_WIDTHS, "200,400"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { - assertArrayEquals(new PictureSource[] { - new PictureSource("home_stage").widths(200, 400) - }, underTest.getPictureSources()); - } - } - - @Test - @SuppressWarnings("deprecation") - void testGetPictureSources_Valid_Inactive_Deprecated() throws Exception { - Resource component = context.create().resource(RESOURCE_TYPE, - PN_COMPONENT_MEDIA_RESPONSIVE_TYPE, RESPONSIVE_TYPE_IMAGE_SIZES); - Resource sources = context.create().resource(component, NN_COMPONENT_MEDIA_RESPONSIVE_PICTURE_SOURCES); - context.create().resource(sources, "source1", - PN_PICTURE_SOURCES_MEDIAFORMAT, "home_stage", - PN_PICTURE_SOURCES_WIDTHS, "200,400"); - Resource resource = context.create().resource("/content/r1", - PROPERTY_RESOURCE_TYPE, RESOURCE_TYPE); - context.resourceResolver().commit(); - - try (MediaComponentPropertyResolver underTest = new MediaComponentPropertyResolver(resource, componentPropertyResolverFactory)) { + try (MediaComponentPropertyResolver underTest = AdaptTo.notNull(resource, MediaComponentPropertyResolver.class)) { assertNull(underTest.getPictureSources()); } } diff --git a/src/test/java/io/wcm/handler/media/MediaFileTypeTest.java b/src/test/java/io/wcm/handler/media/MediaFileTypeTest.java index a14b541c..d4d0a16a 100644 --- a/src/test/java/io/wcm/handler/media/MediaFileTypeTest.java +++ b/src/test/java/io/wcm/handler/media/MediaFileTypeTest.java @@ -23,9 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; +import java.util.Set; -import com.google.common.collect.ImmutableSet; +import org.junit.jupiter.api.Test; import io.wcm.wcm.commons.contenttype.ContentType; @@ -44,12 +44,12 @@ void testIsImage() { @Test void testGetImageFileExtensions() { - assertEquals(ImmutableSet.of("jpg", "jpeg", "gif", "png", "svg", "tif", "tiff"), MediaFileType.getImageFileExtensions()); + assertEquals(Set.of("jpg", "jpeg", "gif", "png", "svg", "tif", "tiff"), MediaFileType.getImageFileExtensions()); } @Test void testGetImageContentTypes() { - assertEquals(ImmutableSet.of(ContentType.JPEG, ContentType.GIF, ContentType.PNG, ContentType.SVG, ContentType.TIFF), + assertEquals(Set.of(ContentType.JPEG, ContentType.GIF, ContentType.PNG, ContentType.SVG, ContentType.TIFF), MediaFileType.getImageContentTypes()); } @@ -66,12 +66,12 @@ void testIsBrowserImage() { @Test void testGetBrowserImageFileExtensions() { - assertEquals(ImmutableSet.of("jpg", "jpeg", "gif", "png", "svg"), MediaFileType.getBrowserImageFileExtensions()); + assertEquals(Set.of("jpg", "jpeg", "gif", "png", "svg"), MediaFileType.getBrowserImageFileExtensions()); } @Test void testGetBrowserImageContentTypes() { - assertEquals(ImmutableSet.of(ContentType.JPEG, ContentType.GIF, ContentType.PNG, ContentType.SVG), + assertEquals(Set.of(ContentType.JPEG, ContentType.GIF, ContentType.PNG, ContentType.SVG), MediaFileType.getBrowserImageContentTypes()); } @@ -85,32 +85,12 @@ void testIsVectorImage() { @Test void testGetVectorImageFileExtensions() { - assertEquals(ImmutableSet.of("svg"), MediaFileType.getVectorImageFileExtensions()); + assertEquals(Set.of("svg"), MediaFileType.getVectorImageFileExtensions()); } @Test void testGetVectorImageContentTypes() { - assertEquals(ImmutableSet.of(ContentType.SVG), MediaFileType.getVectorImageContentTypes()); - } - - @Test - @SuppressWarnings("deprecation") - void testIsFlash() { - assertTrue(MediaFileType.isFlash("swf")); - assertFalse(MediaFileType.isFlash("pdf")); - assertFalse(MediaFileType.isFlash(null)); - } - - @Test - @SuppressWarnings("deprecation") - void testGetFlashFileExtensions() { - assertEquals(ImmutableSet.of("swf"), MediaFileType.getFlashFileExtensions()); - } - - @Test - @SuppressWarnings("deprecation") - void testGetFlashContentTypes() { - assertEquals(ImmutableSet.of(ContentType.SWF), MediaFileType.getFlashContentTypes()); + assertEquals(Set.of(ContentType.SVG), MediaFileType.getVectorImageContentTypes()); } } diff --git a/src/test/java/io/wcm/handler/media/MediaTest.java b/src/test/java/io/wcm/handler/media/MediaTest.java index dbe84634..573f48be 100644 --- a/src/test/java/io/wcm/handler/media/MediaTest.java +++ b/src/test/java/io/wcm/handler/media/MediaTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.mock; import java.util.Collection; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; @@ -35,8 +36,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.google.common.collect.ImmutableList; - import io.wcm.handler.commons.dom.Div; import io.wcm.handler.media.spi.MediaSource; @@ -68,13 +67,13 @@ void testMediaSourceRequest() { } @Test - void testElement() { + void testElementBuilder() { Div div = new Div(); div.setText("test"); - underTest.setElement(div); + underTest.setElementBuilder(m -> div.setTitle("title1")); assertSame(div, underTest.getElement()); - assertEquals("
test
", underTest.getMarkup()); + assertEquals("
test
", underTest.getMarkup()); } @Test @@ -99,7 +98,7 @@ void testRenditions() { Rendition rendition1 = mock(Rendition.class); Rendition rendition2 = mock(Rendition.class); - Collection renditions = ImmutableList.of(rendition1, rendition2); + Collection renditions = List.of(rendition1, rendition2); underTest.setRenditions(renditions); assertSame(rendition1, underTest.getRendition()); diff --git a/src/test/java/io/wcm/handler/media/format/MediaFormatBuilderTest.java b/src/test/java/io/wcm/handler/media/format/MediaFormatBuilderTest.java index 4fc084f5..aadbafce 100644 --- a/src/test/java/io/wcm/handler/media/format/MediaFormatBuilderTest.java +++ b/src/test/java/io/wcm/handler/media/format/MediaFormatBuilderTest.java @@ -30,8 +30,6 @@ import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableMap; - @SuppressWarnings("null") class MediaFormatBuilderTest { @@ -162,7 +160,7 @@ void testMinWidthHeight_withOtherWidthHeightRestrictions() { @Test void testProperties() { - Map props = ImmutableMap.of("prop1", "value1"); + Map props = Map.of("prop1", "value1"); MediaFormat mf = MediaFormatBuilder.create("name1") .property("prop3", "value3") diff --git a/src/test/java/io/wcm/handler/media/format/MediaFormatHandlerTest.java b/src/test/java/io/wcm/handler/media/format/MediaFormatHandlerTest.java index 2eb4f557..3fe834b5 100644 --- a/src/test/java/io/wcm/handler/media/format/MediaFormatHandlerTest.java +++ b/src/test/java/io/wcm/handler/media/format/MediaFormatHandlerTest.java @@ -41,7 +41,7 @@ import static io.wcm.handler.media.testcontext.DummyMediaFormats.NONFIXED_TAB_FULLSIZE; import static io.wcm.handler.media.testcontext.DummyMediaFormats.NONFIXED_TAB_SMALL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.NORATIO_LARGE_MINWIDTH; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SPECIAL_4COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.WALLPAPER; import static io.wcm.handler.media.testcontext.DummyMediaFormats.WALLPAPER_1024_768; @@ -398,7 +398,7 @@ void testDetectMediaFormats() { assertEquals("showroom_campaign", mediaFormats.first().getName(), "showroom_campaign"); // test ratio match - MediaFormat ratioFormat = RATIO; + MediaFormat ratioFormat = RATIO_16_10; // ratio mismatch mediaFormats = underTest.detectMediaFormats("png", 100, 50, 50); assertFalse(mediaFormats.contains(ratioFormat), "nonfixed_raw ratio mismatch"); diff --git a/src/test/java/io/wcm/handler/media/format/MediaFormatTest.java b/src/test/java/io/wcm/handler/media/format/MediaFormatTest.java index 019850a4..a3c80a51 100644 --- a/src/test/java/io/wcm/handler/media/format/MediaFormatTest.java +++ b/src/test/java/io/wcm/handler/media/format/MediaFormatTest.java @@ -31,8 +31,6 @@ import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableList; - class MediaFormatTest { @Test @@ -207,7 +205,7 @@ void testSort() { set.add(create("mf3").build()); set.add(create("mf2").build()); - List result = ImmutableList.copyOf(set); + List result = List.copyOf(set); assertEquals("mf1", result.get(0).getName()); assertEquals("mf2", result.get(1).getName()); assertEquals("mf3", result.get(2).getName()); diff --git a/src/test/java/io/wcm/handler/media/format/RatioTest.java b/src/test/java/io/wcm/handler/media/format/RatioTest.java index a6058c48..33086cb8 100644 --- a/src/test/java/io/wcm/handler/media/format/RatioTest.java +++ b/src/test/java/io/wcm/handler/media/format/RatioTest.java @@ -39,9 +39,9 @@ void testMatchesDouble() { @Test void testMatchesMediaFormat() { - assertTrue(Ratio.matches(DummyMediaFormats.RATIO, DummyMediaFormats.RATIO)); - assertFalse(Ratio.matches(DummyMediaFormats.RATIO, DummyMediaFormats.RATIO2)); - assertFalse(Ratio.matches(DummyMediaFormats.RATIO, DummyMediaFormats.DOWNLOAD)); + assertTrue(Ratio.matches(DummyMediaFormats.RATIO_16_10, DummyMediaFormats.RATIO_16_10)); + assertFalse(Ratio.matches(DummyMediaFormats.RATIO_16_10, DummyMediaFormats.RATIO_4_3)); + assertFalse(Ratio.matches(DummyMediaFormats.RATIO_16_10, DummyMediaFormats.DOWNLOAD)); assertFalse(Ratio.matches(DummyMediaFormats.DOWNLOAD, DummyMediaFormats.DOWNLOAD)); } diff --git a/src/test/java/io/wcm/handler/media/format/ResponsiveMediaFormatsBuilderTest.java b/src/test/java/io/wcm/handler/media/format/ResponsiveMediaFormatsBuilderTest.java deleted file mode 100644 index 1cce370d..00000000 --- a/src/test/java/io/wcm/handler/media/format/ResponsiveMediaFormatsBuilderTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * #%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.media.format; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -import io.wcm.handler.media.MediaNameConstants; -import io.wcm.handler.media.testcontext.DummyMediaFormats; - -@SuppressWarnings("deprecation") -class ResponsiveMediaFormatsBuilderTest { - - @Test - void testBuild() { - MediaFormat[] mediaFormats = new ResponsiveMediaFormatsBuilder(DummyMediaFormats.RATIO) - .breakpoint("B1", 160, 100) - .breakpoint("B2", 320, 200) - .build(); - - assertEquals(ResponsiveMediaFormatsBuilder.buildCombinedName(DummyMediaFormats.RATIO, "B1", 160, 100), mediaFormats[0].getName()); - assertEquals(DummyMediaFormats.RATIO.getLabel(), mediaFormats[0].getLabel()); - assertArrayEquals(DummyMediaFormats.RATIO.getExtensions(), mediaFormats[0].getExtensions()); - assertEquals(DummyMediaFormats.RATIO.getRatio(), mediaFormats[0].getRatio(), 0.001d); - assertEquals(160, mediaFormats[0].getWidth()); - assertEquals(100, mediaFormats[0].getHeight()); - assertEquals("B1", mediaFormats[0].getProperties().get(MediaNameConstants.PROP_BREAKPOINT)); - - assertEquals(ResponsiveMediaFormatsBuilder.buildCombinedName(DummyMediaFormats.RATIO, "B2", 320, 200), mediaFormats[1].getName()); - assertEquals(DummyMediaFormats.RATIO.getLabel(), mediaFormats[1].getLabel()); - assertArrayEquals(DummyMediaFormats.RATIO.getExtensions(), mediaFormats[1].getExtensions()); - assertEquals(DummyMediaFormats.RATIO.getRatio(), mediaFormats[1].getRatio(), 0.001d); - assertEquals(320, mediaFormats[1].getWidth()); - assertEquals(200, mediaFormats[1].getHeight()); - assertEquals("B2", mediaFormats[1].getProperties().get(MediaNameConstants.PROP_BREAKPOINT)); - } - -} diff --git a/src/test/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProviderTest.java b/src/test/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProviderTest.java index 03077391..c0233ae3 100644 --- a/src/test/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProviderTest.java +++ b/src/test/java/io/wcm/handler/media/format/impl/DefaultMediaFormatListProviderTest.java @@ -19,13 +19,17 @@ */ package io.wcm.handler.media.format.impl; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasItem; -import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import com.jayway.jsonpath.DocumentContext; +import com.jayway.jsonpath.JsonPath; + import io.wcm.handler.media.testcontext.AppAemContext; import io.wcm.handler.media.testcontext.DummyMediaFormats; import io.wcm.testing.mock.aem.junit5.AemContext; @@ -48,9 +52,11 @@ void testGet() throws Exception { underTest.service(context.request(), context.response()); String response = context.response().getOutputAsString(); - assertTrue(StringUtils.contains(response, "\"" + DummyMediaFormats.EDITORIAL_1COL.getName() + "\"")); - assertTrue(StringUtils.contains(response, "\"" + DummyMediaFormats.EDITORIAL_2COL.getName() + "\"")); - assertTrue(StringUtils.contains(response, "\"" + DummyMediaFormats.EDITORIAL_3COL.getName() + "\"")); + + DocumentContext json = JsonPath.parse(response); + assertThat(json, hasJsonPath("$[*].name", hasItem(DummyMediaFormats.EDITORIAL_1COL.getName()))); + assertThat(json, hasJsonPath("$[*].name", hasItem(DummyMediaFormats.EDITORIAL_2COL.getName()))); + assertThat(json, hasJsonPath("$[*].name", hasItem(DummyMediaFormats.EDITORIAL_3COL.getName()))); } } diff --git a/src/test/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImplTest.java b/src/test/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImplTest.java index 9520beb8..3ad342f7 100644 --- a/src/test/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImplTest.java +++ b/src/test/java/io/wcm/handler/media/format/impl/MediaFormatProviderManagerImplTest.java @@ -22,9 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.when; +import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; +import java.util.TreeSet; import org.apache.sling.api.resource.Resource; import org.junit.jupiter.api.BeforeEach; @@ -36,8 +39,6 @@ import org.mockito.quality.Strictness; import org.osgi.framework.Constants; -import com.google.common.collect.ImmutableSortedSet; - import io.wcm.handler.media.format.MediaFormat; import io.wcm.handler.media.format.MediaFormatBuilder; import io.wcm.handler.media.format.MediaFormatProviderManager; @@ -53,11 +54,11 @@ class MediaFormatProviderManagerImplTest { private static final MediaFormat MF11 = MediaFormatBuilder.create("mf11").description("desc-from-1").build(); private static final MediaFormat MF12 = MediaFormatBuilder.create("mf12").description("desc-from-1").build(); - private static final SortedSet MEDIAFORMATS_1 = ImmutableSortedSet.of(MF11, MF12); + private static final SortedSet MEDIAFORMATS_1 = new TreeSet<>(Set.of(MF11, MF12)); private static final MediaFormat MF11_FROM2 = MediaFormatBuilder.create("mf11").description("desc-from-2").build(); private static final MediaFormat MF21 = MediaFormatBuilder.create("mf21").description("desc-from-2").build(); - private static final SortedSet MEDIAFORMATS_2 = ImmutableSortedSet.of(MF11_FROM2, MF21); + private static final SortedSet MEDIAFORMATS_2 = new TreeSet<>(Set.of(MF11_FROM2, MF21)); private final AemContext context = new AemContext(); @@ -89,7 +90,7 @@ void setUp() { @Test void testWithResource() { SortedSet result = underTest.getMediaFormats(resource); - assertEquals(ImmutableSortedSet.of(MF11, MF12, MF21), result); + assertEquals(new TreeSet<>(Set.of(MF11, MF12, MF21)), result); MediaFormat first = result.iterator().next(); assertEquals("mf11", first.getName()); @@ -99,7 +100,7 @@ void testWithResource() { @Test void testNullResource() { - assertEquals(ImmutableSortedSet.of(), underTest.getMediaFormats(null)); + assertEquals(Collections.emptySet(), underTest.getMediaFormats(null)); } @Test @@ -112,7 +113,7 @@ void testGetAllMediaFormats() { assertEquals("mock-bundle", entry.getKey()); SortedSet mediaFormats = entry.getValue(); - assertEquals(ImmutableSortedSet.of(MF11, MF12, MF21), mediaFormats); + assertEquals(new TreeSet<>(Set.of(MF11, MF12, MF21)), mediaFormats); } } diff --git a/src/test/java/io/wcm/handler/media/imagemap/impl/ImageMapParserImplTest.java b/src/test/java/io/wcm/handler/media/imagemap/impl/ImageMapParserImplTest.java index 7d61811d..6babff16 100644 --- a/src/test/java/io/wcm/handler/media/imagemap/impl/ImageMapParserImplTest.java +++ b/src/test/java/io/wcm/handler/media/imagemap/impl/ImageMapParserImplTest.java @@ -29,8 +29,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; -import com.google.common.collect.ImmutableList; - import io.wcm.handler.media.imagemap.ImageMapArea; import io.wcm.handler.media.imagemap.ImageMapParser; import io.wcm.handler.media.spi.ImageMapLinkResolver; @@ -56,7 +54,7 @@ public class ImageMapParserImplTest { + "[rect(256,171,1023,682)\"" + INVALID_CONTENT_REF + "\"|\"\"|\"altText\"|(0.1992,0.2005,0.7992,0.7995)]"; @SuppressWarnings("null") - public static final List> EXPECTED_AREAS_RESOLVED = ImmutableList.of( + public static final List> EXPECTED_AREAS_RESOLVED = List.of( new ImageMapAreaImpl("circle", "256,256,256", "0.2000,0.3001,0.2000", EXTERNAL_REF, EXTERNAL_REF, null, null), new ImageMapAreaImpl("rect", "256,171,1023,682", "0.1992,0.2005,0.7992,0.7995", @@ -66,7 +64,7 @@ public class ImageMapParserImplTest { null)); @SuppressWarnings("null") - public static final List> EXPECTED_AREAS_UNRESOLVED = ImmutableList.of( + public static final List> EXPECTED_AREAS_UNRESOLVED = List.of( new ImageMapAreaImpl("circle", "256,256,256", "0.2000,0.3001,0.2000", null, EXTERNAL_REF, null, null), new ImageMapAreaImpl("rect", "256,171,1023,682", "0.1992,0.2005,0.7992,0.7995", diff --git a/src/test/java/io/wcm/handler/media/impl/ImageFileServletTest.java b/src/test/java/io/wcm/handler/media/impl/ImageFileServletTest.java index ff0381e9..74eaf344 100644 --- a/src/test/java/io/wcm/handler/media/impl/ImageFileServletTest.java +++ b/src/test/java/io/wcm/handler/media/impl/ImageFileServletTest.java @@ -125,6 +125,28 @@ void testGet_Cropping() throws Exception { assertResponseLayerSize(10, 15); } + @Test + void testGet_Cropping_OnlyWidth() throws Exception { + context.requestPathInfo().setSelectorString("image_file.50.0.10,10,110,70"); + + underTest.service(context.request(), context.response()); + + assertEquals(SC_OK, context.response().getStatus()); + assertEquals(ContentType.JPEG, context.response().getContentType()); + assertResponseLayerSize(50, 30); + } + + @Test + void testGet_Cropping_OnlyHeight() throws Exception { + context.requestPathInfo().setSelectorString("image_file.0.30.10,10,110,70"); + + underTest.service(context.request(), context.response()); + + assertEquals(SC_OK, context.response().getStatus()); + assertEquals(ContentType.JPEG, context.response().getContentType()); + assertResponseLayerSize(50, 30); + } + @Test void testGet_Cropping_InvalidSyntax() throws Exception { context.requestPathInfo().setSelectorString("image_file.215.102.10,10"); diff --git a/src/test/java/io/wcm/handler/media/impl/JcrBinaryTest.java b/src/test/java/io/wcm/handler/media/impl/JcrBinaryTest.java index 3b2bc6f1..6613b748 100644 --- a/src/test/java/io/wcm/handler/media/impl/JcrBinaryTest.java +++ b/src/test/java/io/wcm/handler/media/impl/JcrBinaryTest.java @@ -27,14 +27,14 @@ import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; -import org.apache.sling.api.wrappers.ValueMapDecorator; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import com.day.cq.commons.jcr.JcrConstants; -import com.google.common.collect.ImmutableMap; + +import io.wcm.sling.commons.resource.ImmutableValueMap; @ExtendWith(MockitoExtension.class) class JcrBinaryTest { @@ -74,8 +74,8 @@ void testIsNtFileResource_Other() { @Test void testGetMimeType_Resource() { when(resource.getResourceType()).thenReturn(JcrConstants.NT_RESOURCE); - when(resource.getValueMap()).thenReturn(new ValueMapDecorator(ImmutableMap.builder() - .put(JcrConstants.JCR_MIMETYPE, MIMETYPE_GIF).build())); + when(resource.getValueMap()).thenReturn(ImmutableValueMap.of( + JcrConstants.JCR_MIMETYPE, MIMETYPE_GIF)); assertEquals(MIMETYPE_GIF, JcrBinary.getMimeType(resource)); } @@ -93,8 +93,8 @@ void testGetMimeType_File() { when(resource.getResourceType()).thenReturn(JcrConstants.NT_FILE); when(resource.getChild(JcrConstants.JCR_CONTENT)).thenReturn(subResource); when(subResource.getResourceType()).thenReturn(JcrConstants.NT_RESOURCE); - when(subResource.getValueMap()).thenReturn(new ValueMapDecorator(ImmutableMap.builder() - .put(JcrConstants.JCR_MIMETYPE, MIMETYPE_GIF).build())); + when(subResource.getValueMap()).thenReturn(ImmutableValueMap.of( + JcrConstants.JCR_MIMETYPE, MIMETYPE_GIF)); assertEquals(MIMETYPE_GIF, JcrBinary.getMimeType(resource)); } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaFormatResolverTest.java b/src/test/java/io/wcm/handler/media/impl/MediaFormatResolverTest.java index 620795d7..2c8129c7 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaFormatResolverTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaFormatResolverTest.java @@ -23,8 +23,8 @@ import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_1COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_2COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.IMAGE_UNCONSTRAINED; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO2; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -61,8 +61,8 @@ class MediaFormatResolverTest { void setUp() { when(mediaFormatHandler.getMediaFormat(EDITORIAL_1COL.getName())).thenReturn(EDITORIAL_1COL); when(mediaFormatHandler.getMediaFormat(EDITORIAL_2COL.getName())).thenReturn(EDITORIAL_2COL); - when(mediaFormatHandler.getMediaFormat(RATIO.getName())).thenReturn(RATIO); - when(mediaFormatHandler.getMediaFormat(RATIO2.getName())).thenReturn(RATIO2); + when(mediaFormatHandler.getMediaFormat(RATIO_16_10.getName())).thenReturn(RATIO_16_10); + when(mediaFormatHandler.getMediaFormat(RATIO_4_3.getName())).thenReturn(RATIO_4_3); underTest = new MediaFormatResolver(mediaFormatHandler); } @@ -110,67 +110,67 @@ void testMediaFormatNamesAllInvalid() { @Test void testImageSizes() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .imageSizes(new ImageSizes("size1", 10, 20)); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(3, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 10, true, mediaFormatOptions[1]); - assertResponsiveMediaFormat(RATIO, 20, true, mediaFormatOptions[2]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 10, true, mediaFormatOptions[1]); + assertResponsiveMediaFormat(RATIO_16_10, 20, true, mediaFormatOptions[2]); } @Test void testImageSizes_MixedMandatory() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .imageSizes(new ImageSizes("size1", new WidthOption(10, true), new WidthOption(20, false))); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(3, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 10, true, mediaFormatOptions[1]); - assertResponsiveMediaFormat(RATIO, 20, false, mediaFormatOptions[2]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 10, true, mediaFormatOptions[1]); + assertResponsiveMediaFormat(RATIO_16_10, 20, false, mediaFormatOptions[2]); } @Test void testImageSizes_MultipleMediaFormats() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormats(RATIO, RATIO2) + .mediaFormats(RATIO_16_10, RATIO_4_3) .imageSizes(new ImageSizes("size1", 10, 20)); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(6, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertEquals(RATIO2, mediaFormatOptions[1].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 10, true, mediaFormatOptions[2]); - assertResponsiveMediaFormat(RATIO, 20, true, mediaFormatOptions[3]); - assertResponsiveMediaFormat(RATIO2, 10, true, mediaFormatOptions[4]); - assertResponsiveMediaFormat(RATIO2, 20, true, mediaFormatOptions[5]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertEquals(RATIO_4_3, mediaFormatOptions[1].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 10, true, mediaFormatOptions[2]); + assertResponsiveMediaFormat(RATIO_16_10, 20, true, mediaFormatOptions[3]); + assertResponsiveMediaFormat(RATIO_4_3, 10, true, mediaFormatOptions[4]); + assertResponsiveMediaFormat(RATIO_4_3, 20, true, mediaFormatOptions[5]); } @Test void testImageSizes_MultipleMediaFormats_MixedMandatory() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormats(RATIO, RATIO2) + .mediaFormats(RATIO_16_10, RATIO_4_3) .imageSizes(new ImageSizes("size1", new WidthOption(10, true), new WidthOption(20, false))); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(6, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertEquals(RATIO2, mediaFormatOptions[1].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 10, true, mediaFormatOptions[2]); - assertResponsiveMediaFormat(RATIO, 20, false, mediaFormatOptions[3]); - assertResponsiveMediaFormat(RATIO2, 10, true, mediaFormatOptions[4]); - assertResponsiveMediaFormat(RATIO2, 20, false, mediaFormatOptions[5]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertEquals(RATIO_4_3, mediaFormatOptions[1].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 10, true, mediaFormatOptions[2]); + assertResponsiveMediaFormat(RATIO_16_10, 20, false, mediaFormatOptions[3]); + assertResponsiveMediaFormat(RATIO_4_3, 10, true, mediaFormatOptions[4]); + assertResponsiveMediaFormat(RATIO_4_3, 20, false, mediaFormatOptions[5]); } @Test @@ -199,101 +199,101 @@ void testImageSizes_NoMediaFormat() { @Test void testPictureSources_DifferentRatio() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .pictureSources(new PictureSource[] { - new PictureSource(RATIO).media("media1").widths(20, 30), - new PictureSource(RATIO2).widths(10, 20) + new PictureSource(RATIO_16_10).media("media1").widths(20, 30), + new PictureSource(RATIO_4_3).widths(10, 20) }); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(5, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 20, true, mediaFormatOptions[1]); - assertResponsiveMediaFormat(RATIO, 30, true, mediaFormatOptions[2]); - assertResponsiveMediaFormat(RATIO2, 10, true, mediaFormatOptions[3]); - assertResponsiveMediaFormat(RATIO2, 20, true, mediaFormatOptions[4]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 20, true, mediaFormatOptions[1]); + assertResponsiveMediaFormat(RATIO_16_10, 30, true, mediaFormatOptions[2]); + assertResponsiveMediaFormat(RATIO_4_3, 10, true, mediaFormatOptions[3]); + assertResponsiveMediaFormat(RATIO_4_3, 20, true, mediaFormatOptions[4]); } @Test void testPictureSources_DifferentRatio_MediaFormatNames() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .pictureSources(new PictureSource[] { - new PictureSource("ratio").media("media1").widths(20, 30), - new PictureSource("ratio2").widths(10, 20) + new PictureSource(RATIO_16_10.getName()).media("media1").widths(20, 30), + new PictureSource(RATIO_4_3.getName()).widths(10, 20) }); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(5, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 20, true, mediaFormatOptions[1]); - assertResponsiveMediaFormat(RATIO, 30, true, mediaFormatOptions[2]); - assertResponsiveMediaFormat(RATIO2, 10, true, mediaFormatOptions[3]); - assertResponsiveMediaFormat(RATIO2, 20, true, mediaFormatOptions[4]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 20, true, mediaFormatOptions[1]); + assertResponsiveMediaFormat(RATIO_16_10, 30, true, mediaFormatOptions[2]); + assertResponsiveMediaFormat(RATIO_4_3, 10, true, mediaFormatOptions[3]); + assertResponsiveMediaFormat(RATIO_4_3, 20, true, mediaFormatOptions[4]); } @Test void testPictureSources_DifferentRatio_MixedMandatory() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .pictureSources(new PictureSource[] { - new PictureSource(RATIO).media("media1").widthOptions(new WidthOption(20, true), new WidthOption(30, false)), - new PictureSource(RATIO2).widthOptions(new WidthOption(10, false), new WidthOption(20, true)) + new PictureSource(RATIO_16_10).media("media1").widthOptions(new WidthOption(20, true), new WidthOption(30, false)), + new PictureSource(RATIO_4_3).widthOptions(new WidthOption(10, false), new WidthOption(20, true)) }); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(5, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 20, true, mediaFormatOptions[1]); - assertResponsiveMediaFormat(RATIO, 30, false, mediaFormatOptions[2]); - assertResponsiveMediaFormat(RATIO2, 10, false, mediaFormatOptions[3]); - assertResponsiveMediaFormat(RATIO2, 20, true, mediaFormatOptions[4]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 20, true, mediaFormatOptions[1]); + assertResponsiveMediaFormat(RATIO_16_10, 30, false, mediaFormatOptions[2]); + assertResponsiveMediaFormat(RATIO_4_3, 10, false, mediaFormatOptions[3]); + assertResponsiveMediaFormat(RATIO_4_3, 20, true, mediaFormatOptions[4]); } @Test void testPictureSources_SameRatio() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .pictureSources(new PictureSource[] { - new PictureSource(RATIO).media("media1").widths(20, 30), - new PictureSource(RATIO).widths(10, 20) + new PictureSource(RATIO_16_10).media("media1").widths(20, 30), + new PictureSource(RATIO_16_10).widths(10, 20) }); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(4, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); - assertResponsiveMediaFormat(RATIO, 20, true, mediaFormatOptions[1]); - assertResponsiveMediaFormat(RATIO, 30, true, mediaFormatOptions[2]); - assertResponsiveMediaFormat(RATIO, 10, true, mediaFormatOptions[3]); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); + assertResponsiveMediaFormat(RATIO_16_10, 20, true, mediaFormatOptions[1]); + assertResponsiveMediaFormat(RATIO_16_10, 30, true, mediaFormatOptions[2]); + assertResponsiveMediaFormat(RATIO_16_10, 10, true, mediaFormatOptions[3]); } @Test void testPictureSources_NoWidths() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .pictureSources(new PictureSource[] { - new PictureSource(RATIO).media("media1") + new PictureSource(RATIO_16_10).media("media1") }); assertTrue(underTest.resolve(mediaArgs)); MediaFormatOption[] mediaFormatOptions = mediaArgs.getMediaFormatOptions(); assertEquals(1, mediaFormatOptions.length); - assertEquals(RATIO, mediaFormatOptions[0].getMediaFormat()); + assertEquals(RATIO_16_10, mediaFormatOptions[0].getMediaFormat()); } @Test void testPictureSources_InvalidMediaFormatName() { MediaArgs mediaArgs = new MediaArgs() - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .pictureSources(new PictureSource[] { new PictureSource("invalid-format-name").media("media1") }); diff --git a/src/test/java/io/wcm/handler/media/impl/MediaFormatValidateServletTest.java b/src/test/java/io/wcm/handler/media/impl/MediaFormatValidateServletTest.java index 7536e5c3..38bf541c 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaFormatValidateServletTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaFormatValidateServletTest.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; +import java.util.Map; import javax.servlet.http.HttpServletResponse; @@ -44,8 +45,6 @@ import org.skyscreamer.jsonassert.JSONAssert; import com.day.cq.dam.api.Asset; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaInvalidReason; @@ -74,11 +73,11 @@ void setUp() { @SuppressWarnings("null") void testValid() throws Exception { Asset asset = context.create().asset("/content/dam/sample.jpg", - (int)EDITORIAL_1COL.getWidth(), - (int)EDITORIAL_1COL.getHeight(), + EDITORIAL_1COL.getWidth(), + EDITORIAL_1COL.getHeight(), ContentType.JPEG); - context.request().setParameterMap(ImmutableMap.of( + context.request().setParameterMap(Map.of( RP_MEDIA_FORMATS, EDITORIAL_1COL.getName(), RP_MEDIA_REF, asset.getPath())); underTest.service(context.request(), context.response()); @@ -89,11 +88,11 @@ void testValid() throws Exception { @Test void testValid_MultipleFormats() throws Exception { Asset asset = context.create().asset("/content/dam/sample.jpg", - (int)EDITORIAL_1COL.getWidth(), - (int)EDITORIAL_1COL.getHeight(), + EDITORIAL_1COL.getWidth(), + EDITORIAL_1COL.getHeight(), ContentType.JPEG); - context.request().setParameterMap(ImmutableMap.of( + context.request().setParameterMap(Map.of( RP_MEDIA_FORMATS, HOME_STAGE.getName() + "," + EDITORIAL_1COL.getName(), RP_MEDIA_FORMATS_MANDATORY, HOME_STAGE.getName() + "," + EDITORIAL_1COL.getName(), RP_MEDIA_REF, asset.getPath())); @@ -108,11 +107,11 @@ void testValid_MultipleFormats() throws Exception { @SuppressWarnings("null") void testValid_MultipleFormats_Optional() throws Exception { Asset asset = context.create().asset("/content/dam/sample.jpg", - (int)EDITORIAL_1COL.getWidth(), - (int)EDITORIAL_1COL.getHeight(), + EDITORIAL_1COL.getWidth(), + EDITORIAL_1COL.getHeight(), ContentType.JPEG); - context.request().setParameterMap(ImmutableMap.of( + context.request().setParameterMap(Map.of( RP_MEDIA_FORMATS, HOME_STAGE.getName() + "," + EDITORIAL_1COL.getName(), RP_MEDIA_FORMATS_MANDATORY, EDITORIAL_1COL.getName(), RP_MEDIA_REF, asset.getPath())); @@ -129,7 +128,7 @@ void testInvalid() throws Exception { 10, ContentType.JPEG); - context.request().setParameterMap(ImmutableMap.of( + context.request().setParameterMap(Map.of( RP_MEDIA_FORMATS, EDITORIAL_1COL.getName(), RP_MEDIA_REF, asset.getPath())); underTest.service(context.request(), context.response()); @@ -145,16 +144,16 @@ void testInvalid_CustomMessageKey() throws Exception { context.registerService(MediaHandlerConfig.class, new DummyMediaHandlerConfig() { @Override public @NotNull List> getPreProcessors() { - return ImmutableList.of(AllInvalidMediaPreProcessor.class); + return List.of(AllInvalidMediaPreProcessor.class); } }, Constants.SERVICE_RANKING, 1000); Asset asset = context.create().asset("/content/dam/sample.jpg", - (int)EDITORIAL_1COL.getWidth(), - (int)EDITORIAL_1COL.getHeight(), + EDITORIAL_1COL.getWidth(), + EDITORIAL_1COL.getHeight(), ContentType.JPEG); - context.request().setParameterMap(ImmutableMap.of( + context.request().setParameterMap(Map.of( RP_MEDIA_FORMATS, EDITORIAL_1COL.getName(), RP_MEDIA_REF, asset.getPath())); underTest.service(context.request(), context.response()); diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java new file mode 100644 index 00000000..d9d38928 --- /dev/null +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplEnd2EndDynamicMediaSmartCropTest.java @@ -0,0 +1,173 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.media.impl; + +import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.CROP_TYPE_SMART; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_BANNER; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_CROP_TYPE; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_HEIGHT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_WIDTH; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.osgi.framework.Constants; + +import com.day.cq.dam.api.Asset; +import com.day.cq.dam.api.DamConstants; +import com.day.cq.dam.scene7.api.constants.Scene7Constants; + +import io.wcm.handler.media.Media; +import io.wcm.handler.media.MediaArgs.PictureSource; +import io.wcm.handler.media.MediaHandler; +import io.wcm.handler.media.MediaInvalidReason; +import io.wcm.handler.media.Rendition; +import io.wcm.handler.media.format.MediaFormat; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; +import io.wcm.wcm.commons.contenttype.ContentType; + +/** + * Test media handling with Dynamic Media and Smart Cropping end-2-end. + */ +@ExtendWith(AemContextExtension.class) +class MediaHandlerImplEnd2EndDynamicMediaSmartCropTest { + + private final AemContext context = AppAemContext.newAemContext(); + + private Asset asset; + private MediaHandler mediaHandler; + + @BeforeEach + void setUp() { + Resource profile1 = context.create().resource("/conf/global/settings/dam/adminui-extension/imageprofile/profile1", + PN_CROP_TYPE, CROP_TYPE_SMART, + PN_BANNER, "16-10,16,10|4-3,40,30"); + + Resource assetFolder = context.create().resource("/content/dam/folder1"); + context.create().resource(assetFolder, JCR_CONTENT, DamConstants.IMAGE_PROFILE, profile1.getPath()); + + asset = context.create().asset(assetFolder.getPath() + "/test.jpg", 160, 100, ContentType.JPEG, + Scene7Constants.PN_S7_FILE, "DummyFolder/test"); + context.create().assetRenditionWebEnabled(asset, 128, 80); // simulate web rendition that is a bit smaller + + // original asset size is 160x100px + // the 4-3 smart crop rendition defines a cropping area of 80x60px + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/4-3/" + JCR_CONTENT, + PN_NORMALIZED_WIDTH, 0.5d, + PN_NORMALIZED_HEIGHT, 0.6d); + // the 16-10 smart crop rendition defines a cropping area of 120x75px + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/16-10/" + JCR_CONTENT, + PN_NORMALIZED_WIDTH, 0.75d, + PN_NORMALIZED_HEIGHT, 0.75d); + + mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + } + + @Test + void testValidSmartCroppedRenditionAndWidths() { + Media media = getMediaWithWidths(RATIO_4_3, 80, 40); + assertTrue(media.isValid()); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(2, renditions.size()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=80&hei=60&fit=stretch", renditions.get(0).getUrl()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=40&hei=30&fit=stretch", renditions.get(1).getUrl()); + } + + @Test + void testValidSmartCroppedRenditionAndWidths_DisableValidateSmartCropRenditionSizes() { + context.registerInjectActivateService(DynamicMediaSupportServiceImpl.class, + "validateSmartCropRenditionSizes", false, + Constants.SERVICE_RANKING, 100); + mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + + Media media = getMediaWithWidths(RATIO_4_3, 100, 80, 40); + assertTrue(media.isValid()); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(3, renditions.size()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=100&hei=75&fit=stretch", renditions.get(0).getUrl()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=80&hei=60&fit=stretch", renditions.get(1).getUrl()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=40&hei=30&fit=stretch", renditions.get(2).getUrl()); + } + + @Test + void testInvalidSmartCroppedRendition() { + Media media = getMediaWithWidths(RATIO_4_3, 100); + assertFalse(media.isValid()); + assertEquals(MediaInvalidReason.NO_MATCHING_RENDITION, media.getMediaInvalidReason()); + } + + @Test + void testSomeInvalidSmartCroppedRendition() { + Media media = getMediaWithWidths(RATIO_4_3, 100, 80, 40); + assertFalse(media.isValid()); + assertEquals(MediaInvalidReason.NOT_ENOUGH_MATCHING_RENDITIONS, media.getMediaInvalidReason()); + } + + @Test + void testValidSmartCroppedRendition_OnlyRatio() { + Media media = getMediaWithRatio(RATIO_4_3); + assertTrue(media.isValid()); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(1, renditions.size()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A4-3?wid=80&hei=60&fit=stretch", renditions.get(0).getUrl()); + } + + @Test + void testValidSmartCroppedRenditionOnlyRatio_MatchingOriginalRatio() { + Media media = getMediaWithRatio(RATIO_16_10); + assertTrue(media.isValid()); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(1, renditions.size()); + assertEquals("https://dummy.scene7.com/is/image/DummyFolder/test%3A16-10?wid=120&hei=75&fit=stretch", renditions.get(0).getUrl()); + } + + private Media getMediaWithWidths(MediaFormat mediaFormat, long... widths) { + return mediaHandler.get(asset.getPath()) + .pictureSource(new PictureSource(mediaFormat).widths(widths)) + .autoCrop(true) + .build(); + } + + private Media getMediaWithRatio(MediaFormat mediaFormat) { + return mediaHandler.get(asset.getPath()) + .mediaFormat(mediaFormat) + .autoCrop(true) + .build(); + } + +} diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java index 8fa0db99..8bd87e61 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest.java @@ -19,11 +19,9 @@ */ package io.wcm.handler.media.impl; -import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl.ASSETS_SCENE7_FEATURE_FLAG_PID; import static org.junit.jupiter.api.Assertions.assertFalse; import org.apache.commons.io.FilenameUtils; -import org.apache.sling.featureflags.impl.ConfiguredFeature; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,23 +36,24 @@ /** * Executes the same "end-to-end" as {@link MediaHandlerImplImageFileTypesEnd2EndTest}, but - * with rendering via dynamic media. As for some cases that are not suited for dynamic media - * standard media handling delivery is used, this method overrides only the test cases where - * scene7 is actually used. + * with rendering via dynamic media. The fallback to AEM-rendered renditions is disabled. */ @ExtendWith(AemContextExtension.class) class MediaHandlerImplImageFileTypesEnd2EndDynamicMediaNoFallbackTest extends MediaHandlerImplImageFileTypesEnd2EndTest { + @Override + boolean isCreateAssetWithDynamicMediaMetadata() { + // enable dynamic media metadata in asset + return true; + } + @Override @BeforeEach void setUp() { - // activate dynamic media - context.registerInjectActivateService(new ConfiguredFeature(), - "name", ASSETS_SCENE7_FEATURE_FLAG_PID, - "enabled", true); - // disable AEM fallback + // explicitly activate DM capability, disable AEM fallback, context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), Constants.SERVICE_RANKING, 100, + "dmCapabilityDetection", "ON", "disableAemFallback", true); super.setUp(); } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java index eb0511c9..0581afa9 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest.java @@ -19,10 +19,6 @@ */ package io.wcm.handler.media.impl; -import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl.ASSETS_SCENE7_FEATURE_FLAG_PID; - -import org.apache.sling.featureflags.impl.ConfiguredFeature; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -43,13 +39,9 @@ class MediaHandlerImplImageFileTypesEnd2EndDynamicMediaTest extends MediaHandlerImplImageFileTypesEnd2EndTest { @Override - @BeforeEach - void setUp() { - // activate dynamic media - context.registerInjectActivateService(new ConfiguredFeature(), - "name", ASSETS_SCENE7_FEATURE_FLAG_PID, - "enabled", true); - super.setUp(); + boolean isCreateAssetWithDynamicMediaMetadata() { + // enable dynamic media metadata in asset + return true; } @Override diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java index b7b556db..7dace687 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplImageFileTypesEnd2EndTest.java @@ -26,6 +26,8 @@ import java.io.IOException; import java.io.InputStream; +import java.util.Collections; +import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; @@ -33,6 +35,7 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.resource.Resource; +import org.apache.sling.testing.mock.osgi.MapUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -81,6 +84,10 @@ void setUp() { mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); } + boolean isCreateAssetWithDynamicMediaMetadata() { + return false; + } + @Test void testAsset_JPEG_Original() { Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); @@ -377,8 +384,14 @@ void testFileUpload_SVG_AutoCrop() { Asset createSampleAsset(String classpathResource, String contentType) { String fileName = FilenameUtils.getName(classpathResource); String fileExtension = FilenameUtils.getExtension(classpathResource); - Asset asset = context.create().asset("/content/dam/" + fileName, classpathResource, contentType, - Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); + Map metadata; + if (isCreateAssetWithDynamicMediaMetadata()) { + metadata = MapUtil.toMap(Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); + } + else { + metadata = Collections.emptyMap(); + } + Asset asset = context.create().asset("/content/dam/" + fileName, classpathResource, contentType, metadata); context.create().assetRendition(asset, "cq5dam.web.sample." + fileExtension, classpathResource, contentType); return asset; } diff --git a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java index a2f68ff2..1c76b16a 100644 --- a/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java +++ b/src/test/java/io/wcm/handler/media/impl/MediaHandlerImplTest.java @@ -39,8 +39,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.osgi.framework.Constants; -import com.google.common.collect.ImmutableList; - import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.commons.dom.Image; import io.wcm.handler.media.Media; @@ -105,9 +103,9 @@ void testPipelining() { assertTrue(media.isValid()); assertEquals("http://xyz/content/dummymedia.post1/item1/pre1.gif", media.getUrl()); assertNotNull(media.getElement()); - assertEquals("http://xyz/content/dummymedia/item1/pre1.gif", media.getElement().getAttributeValue("src")); + assertEquals("http://xyz/content/dummymedia.post1/item1/pre1.gif", media.getElement().getAttributeValue("src")); - assertEquals("", media.getMarkup()); + assertEquals("", media.getMarkup()); } @Test @@ -167,7 +165,6 @@ void testFailedMediaFormatResolving() { } @Test - @SuppressWarnings("deprecation") void testAllBuilderProps() { MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); @@ -190,7 +187,6 @@ void testAllBuilderProps() { MediaArgs args = media.getMediaRequest().getMediaArgs(); assertArrayEquals(mediaFormats, args.getMediaFormats()); - assertTrue(args.isMediaFormatsMandatory()); assertArrayEquals(fileExtensions, args.getFileExtensions()); assertEquals(200, args.getFixedWidth()); assertEquals(100, args.getFixedHeight()); @@ -251,7 +247,6 @@ void shouldResolveComponentPropertiesFromContextResourceWhenBuildingFromMediaRef } @Test - @SuppressWarnings("deprecation") void testComponentProperties_Legacy_SingleMandatoryFlag() { Resource component = context.create().resource("/apps/app1/components/comp1", MediaNameConstants.PN_COMPONENT_MEDIA_FORMATS, new String[] { "home_stage", "home_teaser" }, @@ -274,7 +269,6 @@ void testComponentProperties_Legacy_SingleMandatoryFlag() { assertNull(mediaFormatNames); assertTrue(metadata.getMediaRequest().getMediaArgs().isAutoCrop()); - assertTrue(metadata.getMediaRequest().getMediaArgs().isMediaFormatsMandatory()); } @Test @@ -314,22 +308,22 @@ public static class TestMediaHandlerConfig extends MediaHandlerConfig { @Override public List> getPreProcessors() { - return ImmutableList.>of(TestPreProcessor.class); + return List.of(TestPreProcessor.class); } @Override public List> getSources() { - return ImmutableList.>of(TestMediaSource.class); + return List.of(TestMediaSource.class); } @Override public List> getMarkupBuilders() { - return ImmutableList.>of(TestMediaMarkupBuilder.class); + return List.of(TestMediaMarkupBuilder.class); } @Override public List> getPostProcessors() { - return ImmutableList.>of(TestPostProcessor.class); + return List.of(TestPostProcessor.class); } }; diff --git a/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java b/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java index 19170b8e..6440050d 100644 --- a/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java +++ b/src/test/java/io/wcm/handler/media/impl/ipeconfig/CroppingRatiosTest.java @@ -33,8 +33,6 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import com.google.common.collect.ImmutableSet; - import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.MediaRequest; import io.wcm.handler.media.format.MediaFormat; @@ -87,7 +85,7 @@ void testGetMediaFormatsForCropping_MediaArgsWithMediaFormats() { Set result = underTest.getMediaFormatsForCropping(mediaRequest); - assertEquals(ImmutableSet.of("ratio16_9", "ratio32_10", "ratio2_1a"), result); + assertEquals(Set.of("ratio16_9", "ratio32_10", "ratio2_1a"), result); } @Test @@ -96,7 +94,7 @@ void testGetMediaFormatsForCropping_AllMediaFormats() { Set result = underTest.getMediaFormatsForCropping(mediaRequest); - assertEquals(ImmutableSet.of(MEDIAFORMAT_FREE_CROP.getName(), "ratio16_9", "ratio4_3", "ratio32_10", "ratio2_1c"), result); + assertEquals(Set.of(MEDIAFORMAT_FREE_CROP.getName(), "ratio16_9", "ratio4_3", "ratio32_10", "ratio2_1c"), result); } private MediaFormat addMediaFormat(MediaFormatBuilder mediaFormatBuilder) { diff --git a/src/test/java/io/wcm/handler/media/impl/ipeconfig/IPEConfigResourceProviderTest.java b/src/test/java/io/wcm/handler/media/impl/ipeconfig/IPEConfigResourceProviderTest.java index 9070ce5b..dc5c43ec 100644 --- a/src/test/java/io/wcm/handler/media/impl/ipeconfig/IPEConfigResourceProviderTest.java +++ b/src/test/java/io/wcm/handler/media/impl/ipeconfig/IPEConfigResourceProviderTest.java @@ -30,9 +30,11 @@ import static org.mockito.Mockito.when; import java.util.List; +import java.util.Set; import javax.jcr.Node; +import org.apache.commons.collections4.IteratorUtils; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.hamcrest.ResourceMatchers; @@ -49,8 +51,6 @@ import com.day.cq.wcm.api.components.ComponentManager; import com.day.cq.wcm.api.components.EditConfig; import com.day.cq.wcm.api.components.InplaceEditingConfig; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.wcm.handler.media.format.MediaFormatHandler; @@ -116,23 +116,24 @@ public Component answer(InvocationOnMock invocation) throws Throwable { when(mediaFormatHandler.getMediaFormat(SHOWROOM_STANDARD.getName())).thenReturn(SHOWROOM_STANDARD); when(mediaFormatHandler.getMediaFormat(NONFIXED_RAW.getName())).thenReturn(NONFIXED_RAW); } + @Test @SuppressWarnings("null") void testCustomIPEConfig() { String path = IPEConfigResourceProvider.buildPath(componentContentResource.getPath(), - ImmutableSet.of(EDITORIAL_1COL.getName(), SHOWROOM_STANDARD.getName(), NONFIXED_RAW.getName())); + Set.of(EDITORIAL_1COL.getName(), SHOWROOM_STANDARD.getName(), NONFIXED_RAW.getName())); Resource ipeConfig = context.resourceResolver().getResource(path); assertNotNull(ipeConfig); - List ipeConfigChildren = ImmutableList.copyOf(ipeConfig.listChildren()); + List ipeConfigChildren = IteratorUtils.toList(ipeConfig.listChildren()); assertEquals(1, ipeConfigChildren.size()); assertEquals("plugins", ipeConfigChildren.get(0).getName()); Resource aspectRatios = ipeConfig.getChild("plugins/crop/aspectRatios"); assertNotNull(aspectRatios); - List aspectRatiosChildren = ImmutableList.copyOf(aspectRatios.listChildren()); + List aspectRatiosChildren = IteratorUtils.toList(aspectRatios.listChildren()); assertEquals(3, aspectRatiosChildren.size()); assertThat(aspectRatiosChildren.get(0), ResourceMatchers.nameAndProps(EDITORIAL_1COL.getName(), "name", EDITORIAL_1COL.getLabel() + " (215:102)", "ratio", 1d / EDITORIAL_1COL.getRatio())); diff --git a/src/test/java/io/wcm/handler/media/impl/ipeconfig/PathParserTest.java b/src/test/java/io/wcm/handler/media/impl/ipeconfig/PathParserTest.java index 1d2e49a4..b85fe675 100644 --- a/src/test/java/io/wcm/handler/media/impl/ipeconfig/PathParserTest.java +++ b/src/test/java/io/wcm/handler/media/impl/ipeconfig/PathParserTest.java @@ -25,9 +25,9 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.Test; +import java.util.Set; -import com.google.common.collect.ImmutableSet; +import org.junit.jupiter.api.Test; class PathParserTest { @@ -42,10 +42,10 @@ void testInvalidPath() { @Test void testValidPath() { - PathParser underTest = new PathParser(buildPath("/my/path", ImmutableSet.of("mf1", "mf2"))); + PathParser underTest = new PathParser(buildPath("/my/path", Set.of("mf1", "mf2"))); assertTrue(underTest.isValid()); assertEquals("/my/path", underTest.getComponentContentPath()); - assertEquals(ImmutableSet.of("mf1", "mf2"), underTest.getMediaFormatNames()); + assertEquals(Set.of("mf1", "mf2"), underTest.getMediaFormatNames()); assertNull(underTest.getRelativeConfigPath()); assertFalse(underTest.isPluginsCropNode()); @@ -56,10 +56,10 @@ void testValidPath() { @Test void testPluginsCropNode() { - PathParser underTest = new PathParser(buildPath("/my/path", ImmutableSet.of("mf1")) + "/plugins/crop"); + PathParser underTest = new PathParser(buildPath("/my/path", Set.of("mf1")) + "/plugins/crop"); assertTrue(underTest.isValid()); assertEquals("/my/path", underTest.getComponentContentPath()); - assertEquals(ImmutableSet.of("mf1"), underTest.getMediaFormatNames()); + assertEquals(Set.of("mf1"), underTest.getMediaFormatNames()); assertEquals("/plugins/crop", underTest.getRelativeConfigPath()); assertTrue(underTest.isPluginsCropNode()); @@ -70,10 +70,10 @@ void testPluginsCropNode() { @Test void testAspectRatiosNode() { - PathParser underTest = new PathParser(buildPath("/my/path", ImmutableSet.of("mf1")) + "/plugins/crop/aspectRatios"); + PathParser underTest = new PathParser(buildPath("/my/path", Set.of("mf1")) + "/plugins/crop/aspectRatios"); assertTrue(underTest.isValid()); assertEquals("/my/path", underTest.getComponentContentPath()); - assertEquals(ImmutableSet.of("mf1"), underTest.getMediaFormatNames()); + assertEquals(Set.of("mf1"), underTest.getMediaFormatNames()); assertEquals("/plugins/crop/aspectRatios", underTest.getRelativeConfigPath()); assertFalse(underTest.isPluginsCropNode()); @@ -84,10 +84,10 @@ void testAspectRatiosNode() { @Test void testAspectRatioItem() { - PathParser underTest = new PathParser(buildPath("/my/path", ImmutableSet.of("mf1")) + "/plugins/crop/aspectRatios/mf2"); + PathParser underTest = new PathParser(buildPath("/my/path", Set.of("mf1")) + "/plugins/crop/aspectRatios/mf2"); assertTrue(underTest.isValid()); assertEquals("/my/path", underTest.getComponentContentPath()); - assertEquals(ImmutableSet.of("mf1"), underTest.getMediaFormatNames()); + assertEquals(Set.of("mf1"), underTest.getMediaFormatNames()); assertEquals("/plugins/crop/aspectRatios/mf2", underTest.getRelativeConfigPath()); assertFalse(underTest.isPluginsCropNode()); diff --git a/src/test/java/io/wcm/handler/media/markup/DummyImageMediaMarkupBuilderTest.java b/src/test/java/io/wcm/handler/media/markup/DummyImageMediaMarkupBuilderTest.java index 03965e3b..de114f3c 100644 --- a/src/test/java/io/wcm/handler/media/markup/DummyImageMediaMarkupBuilderTest.java +++ b/src/test/java/io/wcm/handler/media/markup/DummyImageMediaMarkupBuilderTest.java @@ -27,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; +import java.util.List; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -36,7 +38,6 @@ import org.mockito.quality.Strictness; import com.day.cq.wcm.api.WCMMode; -import com.google.common.collect.ImmutableList; import io.wcm.handler.commons.dom.HtmlElement; import io.wcm.handler.commons.dom.Image; @@ -106,7 +107,7 @@ void testAccepts_DISABLED() { // test with wcm modes, with rendition, with mediaformat, no suppress mediaRequest.getMediaArgs().mediaFormat(DUMMY_FORMAT).dummyImage(true); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); assertFalse(builder.accepts(media)); // test invalid @@ -140,7 +141,7 @@ void testAccepts_PREVIEW() { // test with wcm modes, with rendition, with mediaformat, no suppress mediaRequest.getMediaArgs().mediaFormat(DUMMY_FORMAT).dummyImage(true); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); assertFalse(builder.accepts(media)); // test invalid @@ -174,7 +175,7 @@ void testAccepts_EDIT() { // test with wcm modes, with rendition, with mediaformat, no suppress mediaRequest.getMediaArgs().mediaFormat(DUMMY_FORMAT).dummyImage(true); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); assertFalse(builder.accepts(media)); // test invalid diff --git a/src/test/java/io/wcm/handler/media/markup/DummyResponsiveImageMediaMarkupBuilderTest.java b/src/test/java/io/wcm/handler/media/markup/DummyResponsiveImageMediaMarkupBuilderTest.java deleted file mode 100644 index 3f4ba507..00000000 --- a/src/test/java/io/wcm/handler/media/markup/DummyResponsiveImageMediaMarkupBuilderTest.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * #%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.media.markup; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import org.apache.commons.lang3.StringUtils; -import org.apache.sling.api.resource.Resource; -import org.apache.sling.commons.json.JSONArray; -import org.apache.sling.commons.json.JSONException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.day.cq.wcm.api.WCMMode; -import com.google.common.collect.ImmutableList; - -import io.wcm.handler.commons.dom.HtmlElement; -import io.wcm.handler.commons.dom.Image; -import io.wcm.handler.media.Asset; -import io.wcm.handler.media.Media; -import io.wcm.handler.media.MediaArgs; -import io.wcm.handler.media.MediaNameConstants; -import io.wcm.handler.media.MediaRequest; -import io.wcm.handler.media.Rendition; -import io.wcm.handler.media.impl.DummyImageServlet; -import io.wcm.handler.media.spi.MediaMarkupBuilder; -import io.wcm.handler.media.spi.MediaSource; -import io.wcm.handler.media.testcontext.AppAemContext; -import io.wcm.handler.media.testcontext.DummyMediaFormats; -import io.wcm.sling.commons.adapter.AdaptTo; -import io.wcm.testing.mock.aem.junit5.AemContext; -import io.wcm.testing.mock.aem.junit5.AemContextExtension; - -/** - * Test DummyResponsiveImageMediaMarkupBuilder - */ -@ExtendWith(AemContextExtension.class) -@ExtendWith(MockitoExtension.class) -@SuppressWarnings({ "deprecation", "null" }) -class DummyResponsiveImageMediaMarkupBuilderTest { - - private final AemContext context = AppAemContext.newAemContext(); - - @Mock - private MediaSource mediaSource; - @Mock - private Asset asset; - - @Mock - private Rendition rendition; - - @Mock - private Resource resource; - - @Test - void testAccepts_DISABLED() { - WCMMode.DISABLED.toRequest(context.request()); - MediaMarkupBuilder underTest = AdaptTo.notNull(context.request(), DummyResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - assertFalse(underTest.accepts(media)); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1); - assertFalse(underTest.accepts(media)); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - - media.setRenditions(ImmutableList.of(rendition)); - assertFalse(underTest.accepts(media)); - media.setRenditions(null); - - assertFalse(underTest.accepts(media)); - - } - - @Test - void testAccepts_EDIT() { - WCMMode.EDIT.toRequest(context.request()); - MediaMarkupBuilder underTest = AdaptTo.notNull(context.request(), DummyResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - assertFalse(underTest.accepts(media)); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1); - assertFalse(underTest.accepts(media)); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - - assertTrue(underTest.accepts(media)); - - media.setRenditions(ImmutableList.of(rendition)); - assertFalse(underTest.accepts(media)); - - media.setRenditions(null); - mediaRequest.getMediaArgs().dummyImage(false); - assertFalse(underTest.accepts(media)); - - } - - @Test - void testAccepts_PREVIEW() { - WCMMode.PREVIEW.toRequest(context.request()); - MediaMarkupBuilder underTest = AdaptTo.notNull(context.request(), DummyResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - assertFalse(underTest.accepts(media)); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1); - assertFalse(underTest.accepts(media)); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - - assertTrue(underTest.accepts(media)); - - media.setRenditions(ImmutableList.of(rendition)); - assertFalse(underTest.accepts(media)); - - media.setRenditions(null); - mediaRequest.getMediaArgs().dummyImage(false); - assertFalse(underTest.accepts(media)); - } - - @Test - void testBuild() throws JSONException { - - WCMMode.PREVIEW.toRequest(context.request()); - MediaMarkupBuilder underTest = AdaptTo.notNull(context.request(), DummyResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest("/media/dummy", - new MediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1)); - Media media = new Media(mediaSource, mediaRequest); - - HtmlElement image = underTest.build(media); - assertTrue(StringUtils.equals(image.getAttributeValue("class"), MediaNameConstants.CSS_DUMMYIMAGE)); - JSONArray sources = new JSONArray(image.getAttributeValue("data-resp-src")); - assertNotNull(sources); - assertEquals(2, sources.length()); - - assertEquals("L1", sources.getJSONObject(0).get(MediaNameConstants.PROP_BREAKPOINT)); - assertEquals(DummyImageServlet.PATH + ".suffix.png/height=540/mf=Responsive~2032~3A9/width=1920.png", - sources.getJSONObject(0).get("src")); - - assertEquals("M1", sources.getJSONObject(1).get(MediaNameConstants.PROP_BREAKPOINT)); - assertEquals(DummyImageServlet.PATH + ".suffix.png/height=360/mf=Responsive~2032~3A9/width=1281.png", - sources.getJSONObject(1).get("src")); - assertNull(image.getAttributeValue("alt"), "alt"); - - } - - @Test - void testIsValidMedia() { - MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), DummyImageMediaMarkupBuilder.class); - - assertFalse(builder.isValidMedia(null)); - assertFalse(builder.isValidMedia(new Image())); - assertFalse(builder.isValidMedia(new Image("/any/path.gif"))); - assertFalse(builder.isValidMedia(new Image().setData("resp-src", "[{'mg': 'test', 'src':'/dummy/img.png'}]"))); - } - -} diff --git a/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java b/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java index 5fb10126..9b3b6637 100644 --- a/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java +++ b/src/test/java/io/wcm/handler/media/markup/MediaMarkupBuilderUtilTest.java @@ -23,8 +23,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.util.Set; + import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; @@ -40,6 +43,9 @@ import com.day.cq.wcm.api.Page; import com.day.cq.wcm.api.PageManager; import com.day.cq.wcm.api.components.ComponentContext; +import com.day.cq.wcm.api.components.EditConfig; +import com.day.cq.wcm.api.components.EditContext; +import com.day.cq.wcm.api.components.InplaceEditingConfig; import io.wcm.handler.commons.dom.Image; import io.wcm.handler.media.Dimension; @@ -71,6 +77,14 @@ class MediaMarkupBuilderUtilTest { private SlingHttpServletRequest request; @Mock private ComponentContext componentContext; + @Mock + private Resource componentResource; + @Mock + private EditContext editContext; + @Mock + private EditConfig editConfig; + @Mock + private InplaceEditingConfig inplaceEditingConfig; @BeforeEach @SuppressWarnings("null") @@ -80,6 +94,12 @@ void setUp() throws Exception { when(request.getResource()).thenReturn(resource); when(pageManager.getContainingPage(resource)).thenReturn(page); when(request.getParameter(DiffService.REQUEST_PARAM_DIFF_TO)).thenReturn(VERSION_LABEL); + when(componentContext.getResource()).thenReturn(componentResource); + when(componentResource.getResourceResolver()).thenReturn(resolver); + when(componentContext.getEditContext()).thenReturn(editContext); + when(editContext.getEditConfig()).thenReturn(editConfig); + when(editConfig.getInplaceEditingConfig()).thenReturn(inplaceEditingConfig); + when(inplaceEditingConfig.getEditorType()).thenReturn("image"); } @Test @@ -149,4 +169,51 @@ void testCanApplyDragDropSupport_DragDropSupport_Auto() { assertTrue(MediaMarkupBuilderUtil.canApplyDragDropSupport(mediaRequest, componentContext)); } + @Test + void testCanSetCustomIPECropRatios_ALWAYS() { + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.ALWAYS)); + assertTrue(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)); + } + + @Test + void testCanSetCustomIPECropRatios_ALWAYS_CustomEditorType_Mismatch() { + when(inplaceEditingConfig.getEditorType()).thenReturn("custom"); + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.ALWAYS)); + assertFalse(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)); + } + + @Test + void testCanSetCustomIPECropRatios_ALWAYS_CustomEditorType_Match() { + when(inplaceEditingConfig.getEditorType()).thenReturn("custom"); + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.ALWAYS)); + assertTrue(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext, Set.of("image", "custom"))); + } + + @Test + void testCanSetCustomIPECropRatios_NEVER() { + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.NEVER)); + assertFalse(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)); + } + + @Test + void testCanSetCustomIPECropRatios_AUTO() { + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.AUTO)); + assertTrue(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)); + } + + @Test + void testCanSetCustomIPECropRatios_AUTO_IPEConfigPath() { + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.AUTO)); + when(inplaceEditingConfig.getConfigPath()).thenReturn("/apps/components/comp1"); + assertTrue(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)); + } + + @Test + void testCanSetCustomIPECropRatios_AUTO_ExistingRatios() { + MediaRequest mediaRequest = new MediaRequest("/content/dam/path", new MediaArgs().ipeRatioCustomize(IPERatioCustomize.AUTO)); + when(inplaceEditingConfig.getConfigPath()).thenReturn("/apps/components/comp1"); + when(resolver.getResource("/apps/components/comp1/plugins/crop/aspectRatios")).thenReturn(mock(Resource.class)); + assertFalse(MediaMarkupBuilderUtil.canSetCustomIPECropRatios(mediaRequest, componentContext)); + } + } diff --git a/src/test/java/io/wcm/handler/media/markup/ResponsiveImageMediaMarkupBuilderTest.java b/src/test/java/io/wcm/handler/media/markup/ResponsiveImageMediaMarkupBuilderTest.java deleted file mode 100644 index 5d8f09ff..00000000 --- a/src/test/java/io/wcm/handler/media/markup/ResponsiveImageMediaMarkupBuilderTest.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * #%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.media.markup; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import org.apache.sling.api.resource.Resource; -import org.apache.sling.commons.json.JSONArray; -import org.apache.sling.commons.json.JSONException; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import com.day.cq.wcm.api.WCMMode; -import com.google.common.collect.ImmutableList; - -import io.wcm.handler.commons.dom.HtmlElement; -import io.wcm.handler.commons.dom.Image; -import io.wcm.handler.media.Asset; -import io.wcm.handler.media.Media; -import io.wcm.handler.media.MediaArgs; -import io.wcm.handler.media.MediaInvalidReason; -import io.wcm.handler.media.MediaNameConstants; -import io.wcm.handler.media.MediaRequest; -import io.wcm.handler.media.Rendition; -import io.wcm.handler.media.spi.MediaMarkupBuilder; -import io.wcm.handler.media.spi.MediaSource; -import io.wcm.handler.media.testcontext.AppAemContext; -import io.wcm.handler.media.testcontext.DummyMediaFormats; -import io.wcm.sling.commons.adapter.AdaptTo; -import io.wcm.testing.mock.aem.junit5.AemContext; -import io.wcm.testing.mock.aem.junit5.AemContextExtension; - -/** - * Test ResponsiveImageMediaMarkupBuilder - */ -@ExtendWith(AemContextExtension.class) -@ExtendWith(MockitoExtension.class) -@SuppressWarnings({ "deprecation", "null" }) -class ResponsiveImageMediaMarkupBuilderTest { - - private final AemContext context = AppAemContext.newAemContext(); - - @Mock - private MediaSource mediaSource; - @Mock - private Asset asset; - - @Mock - private Rendition renditionL; - @Mock - private Rendition renditionS; - - @Mock - private Resource resource; - - @Test - void testAccepts() { - MediaMarkupBuilder underTest = AdaptTo.notNull(context.request(), ResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - - assertFalse(underTest.accepts(media), "no rendition"); - - media.setRenditions(ImmutableList.of(renditionL)); - - assertFalse(underTest.accepts(media),"media format not mandatory"); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1); - - assertFalse(underTest.accepts(media),"no multiple media formats"); - - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - - assertFalse(underTest.accepts(media),"only one rendition"); - - media.setRenditions(ImmutableList.of(renditionL, renditionS)); - - assertTrue(underTest.accepts(media)); - - media.setMediaInvalidReason(MediaInvalidReason.MEDIA_REFERENCE_MISSING); - - assertFalse(underTest.accepts(media)); - } - - @Test - void testBuild() throws JSONException { - MediaMarkupBuilder underTest = AdaptTo.notNull(context.request(), ResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - - media.setRenditions(ImmutableList.of(renditionL, renditionS)); - when(renditionL.getUrl()).thenReturn("/media/dummy/1920x600png"); - when(renditionL.getMediaFormat()).thenReturn(DummyMediaFormats.RESPONSIVE_32_9_L1); - when(renditionS.getUrl()).thenReturn("/media/dummy/120x100png"); - when(renditionS.getMediaFormat()).thenReturn(DummyMediaFormats.RESPONSIVE_32_9_M1); - - HtmlElement image = underTest.build(media); - assertNotNull(image); - - JSONArray sources = new JSONArray(image.getAttributeValue("data-resp-src")); - assertNotNull(sources); - assertEquals(2, sources.length()); - - assertEquals("L1", sources.getJSONObject(0).get(MediaNameConstants.PROP_BREAKPOINT)); - assertEquals("/media/dummy/1920x600png", sources.getJSONObject(0).get("src")); - - assertEquals("M1", sources.getJSONObject(1).get(MediaNameConstants.PROP_BREAKPOINT)); - assertEquals("/media/dummy/120x100png", sources.getJSONObject(1).get("src")); - assertNull(image.getAttributeValue("alt"), "alt"); - - when(asset.getAltText()).thenReturn("Alt Text"); - media.setAsset(asset); - image = underTest.build(media); - assertEquals("Alt Text", image.getAttributeValue("alt"), "alt"); - - // compare whole string - assertEquals( - "\"Alt", - image.toString()); - } - - @Test - void testBuild_EditMode() { - WCMMode.EDIT.toRequest(context.request()); - - MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), ResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest(resource, new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - media.setAsset(asset); - media.setRenditions(ImmutableList.of(renditionL, renditionS)); - when(renditionL.getUrl()).thenReturn("/media/dummy/1920x600png"); - when(renditionL.getMediaFormat()).thenReturn(DummyMediaFormats.RESPONSIVE_32_9_L1); - when(renditionS.getUrl()).thenReturn("/media/dummy/120x100png"); - when(renditionS.getMediaFormat()).thenReturn(DummyMediaFormats.RESPONSIVE_32_9_M1); - - HtmlElement element = builder.build(media); - verify(mediaSource).enableMediaDrop(element, mediaRequest); - } - - - @Test - void testBuild_PreviewMode() { - WCMMode.PREVIEW.toRequest(context.request()); - - MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), ResponsiveImageMediaMarkupBuilder.class); - - MediaRequest mediaRequest = new MediaRequest(resource, new MediaArgs()); - Media media = new Media(mediaSource, mediaRequest); - mediaRequest.getMediaArgs().mandatoryMediaFormats(DummyMediaFormats.RESPONSIVE_32_9_L1, DummyMediaFormats.RESPONSIVE_32_9_M1); - media.setAsset(asset); - media.setRenditions(ImmutableList.of(renditionL, renditionS)); - when(renditionL.getUrl()).thenReturn("/media/dummy/1920x600png"); - when(renditionL.getMediaFormat()).thenReturn(DummyMediaFormats.RESPONSIVE_32_9_L1); - when(renditionS.getUrl()).thenReturn("/media/dummy/120x100png"); - when(renditionS.getMediaFormat()).thenReturn(DummyMediaFormats.RESPONSIVE_32_9_M1); - - HtmlElement element = builder.build(media); - verify(mediaSource).enableMediaDrop(element, mediaRequest); - } - - @Test - void testIsValidMedia() { - MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), ResponsiveImageMediaMarkupBuilder.class); - - assertFalse(builder.isValidMedia(null)); - assertFalse(builder.isValidMedia(new Image())); - assertFalse(builder.isValidMedia(new Image("/any/path.gif"))); - assertFalse(builder.isValidMedia(new Image(MediaMarkupBuilder.DUMMY_IMAGE).setCssClass(MediaNameConstants.CSS_DUMMYIMAGE))); - - assertTrue(builder.isValidMedia(new Image().setData("resp-src", "[{'mg': 'test', 'src':'/dummy/img.png'}]"))); - - } - -} diff --git a/src/test/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilderTest.java b/src/test/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilderTest.java index bea50eea..3a0f907f 100644 --- a/src/test/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilderTest.java +++ b/src/test/java/io/wcm/handler/media/markup/SimpleImageMediaMarkupBuilderTest.java @@ -22,8 +22,8 @@ import static io.wcm.handler.media.imagemap.impl.ImageMapParserImplTest.EXPECTED_AREAS_RESOLVED; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_1COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.NONFIXED_RAW; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO2; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -45,7 +45,6 @@ import org.mockito.quality.Strictness; import com.day.cq.wcm.api.WCMMode; -import com.google.common.collect.ImmutableList; import io.wcm.handler.commons.dom.Area; import io.wcm.handler.commons.dom.HtmlElement; @@ -103,7 +102,7 @@ void testAccepts() { assertFalse(builder.accepts(media), "no rendition"); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); assertFalse(builder.accepts(media), "invalid rendition"); @@ -142,7 +141,7 @@ void testBuild_InvalidRendition() { mediaRequest.getMediaArgs().mediaFormat(EDITORIAL_1COL); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); assertNull(builder.build(media), "invalid rendition"); } @@ -155,7 +154,7 @@ void testBuild_ValidRendition() { mediaRequest.getMediaArgs().mediaFormat(EDITORIAL_1COL); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); when(rendition.getUrl()).thenReturn("/media/dummy.gif"); HtmlElement element = builder.build(media); @@ -175,7 +174,7 @@ void testBuild_ValidRendition_WidthHeightAlt() { mediaRequest.getMediaArgs().property("custom-property", "value1"); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); when(rendition.getUrl()).thenReturn("/media/dummy.gif"); when(rendition.getWidth()).thenReturn(100L); @@ -199,7 +198,7 @@ void testBuild_ValidRendition_EmptyStringAlt() { mediaRequest.getMediaArgs().mediaFormat(EDITORIAL_1COL); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); when(rendition.getUrl()).thenReturn("/media/dummy.gif"); when(rendition.getWidth()).thenReturn(100L); @@ -219,12 +218,12 @@ void testBuild_ImageSizes() { MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), SimpleImageMediaMarkupBuilder.class); MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - mediaRequest.getMediaArgs().mediaFormat(RATIO); + mediaRequest.getMediaArgs().mediaFormat(RATIO_16_10); mediaRequest.getMediaArgs().imageSizes(new ImageSizes("sizes1", 64, 32, 16)); mediaRequest.getMediaArgs().property("custom-property", "value1"); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition(RATIO, 128), rendition(RATIO, 64), rendition(RATIO, 16))); + media.setRenditions(List.of(rendition(RATIO_16_10, 128), rendition(RATIO_16_10, 64), rendition(RATIO_16_10, 16))); HtmlElement element = builder.build(media); assertTrue(element instanceof Image); @@ -241,12 +240,12 @@ void testBuild_ImageSizes_MultipleMediaFormats() { MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), SimpleImageMediaMarkupBuilder.class); MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - mediaRequest.getMediaArgs().mediaFormats(RATIO2, RATIO); // <- only second format resolves + mediaRequest.getMediaArgs().mediaFormats(RATIO_4_3, RATIO_16_10); // <- only second format resolves mediaRequest.getMediaArgs().imageSizes(new ImageSizes("sizes1", 64, 32, 16)); mediaRequest.getMediaArgs().property("custom-property", "value1"); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition(RATIO, 128), rendition(RATIO, 64), rendition(RATIO, 16))); + media.setRenditions(List.of(rendition(RATIO_16_10, 128), rendition(RATIO_16_10, 64), rendition(RATIO_16_10, 16))); HtmlElement element = builder.build(media); assertTrue(element instanceof Image); @@ -267,7 +266,7 @@ void testBuild_ImageSizes_NoRatio() { mediaRequest.getMediaArgs().imageSizes(new ImageSizes("sizes1", 64, 32, 16)); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition(RATIO, 128), rendition(RATIO, 64), rendition(RATIO, 16))); + media.setRenditions(List.of(rendition(RATIO_16_10, 128), rendition(RATIO_16_10, 64), rendition(RATIO_16_10, 16))); HtmlElement element = builder.build(media); assertTrue(element instanceof Image); @@ -283,17 +282,17 @@ void testBuild_Picture() { MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), SimpleImageMediaMarkupBuilder.class); MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - mediaRequest.getMediaArgs().mediaFormat(RATIO); + mediaRequest.getMediaArgs().mediaFormat(RATIO_16_10); mediaRequest.getMediaArgs().pictureSources(new PictureSource[] { - new PictureSource(RATIO).media("media1").sizes("sizes1").widths(64, 32), + new PictureSource(RATIO_16_10).media("media1").sizes("sizes1").widths(64, 32), new PictureSource(EDITORIAL_1COL).media("media2").widths(215), - new PictureSource(RATIO2).widths(40), + new PictureSource(RATIO_4_3).widths(40), }); mediaRequest.getMediaArgs().property("custom-property", "value1"); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition(RATIO, 128), rendition(RATIO, 64), rendition(RATIO, 32), rendition(RATIO, 16), - rendition(RATIO2, 40), rendition(RATIO2, 20))); + media.setRenditions(List.of(rendition(RATIO_16_10, 128), rendition(RATIO_16_10, 64), rendition(RATIO_16_10, 32), rendition(RATIO_16_10, 16), + rendition(RATIO_4_3, 40), rendition(RATIO_4_3, 20))); HtmlElement picture = builder.build(media); assertTrue(picture instanceof Picture); @@ -330,7 +329,7 @@ void testBuild_EditMode() { mediaRequest.getMediaArgs().mediaFormat(EDITORIAL_1COL); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); when(rendition.getUrl()).thenReturn("/media/dummy.gif"); HtmlElement element = builder.build(media); @@ -348,7 +347,7 @@ void testBuild_PreviewMode_DiffDecoration() { mediaRequest.getMediaArgs().mediaFormat(EDITORIAL_1COL); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); when(rendition.getUrl()).thenReturn("/media/dummy.gif"); builder.build(media); @@ -363,7 +362,7 @@ void testBuild_Image_Map() { mediaRequest.getMediaArgs().mediaFormat(EDITORIAL_1COL); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition)); + media.setRenditions(List.of(rendition)); media.setMap((List)EXPECTED_AREAS_RESOLVED); when(rendition.getUrl()).thenReturn("/media/dummy.gif"); @@ -383,17 +382,17 @@ void testBuild_Picture_Map() { MediaMarkupBuilder builder = AdaptTo.notNull(context.request(), SimpleImageMediaMarkupBuilder.class); MediaRequest mediaRequest = new MediaRequest("/media/dummy", new MediaArgs()); - mediaRequest.getMediaArgs().mediaFormat(RATIO); + mediaRequest.getMediaArgs().mediaFormat(RATIO_16_10); mediaRequest.getMediaArgs().pictureSources(new PictureSource[] { - new PictureSource(RATIO).media("media1").widths(64, 32), + new PictureSource(RATIO_16_10).media("media1").widths(64, 32), new PictureSource(EDITORIAL_1COL).media("media2").widths(215), - new PictureSource(RATIO2).widths(40), + new PictureSource(RATIO_4_3).widths(40), }); mediaRequest.getMediaArgs().property("custom-property", "value1"); Media media = new Media(mediaSource, mediaRequest); media.setAsset(asset); - media.setRenditions(ImmutableList.of(rendition(RATIO, 128), rendition(RATIO, 64), rendition(RATIO, 32), rendition(RATIO, 16), - rendition(RATIO2, 40), rendition(RATIO2, 20))); + media.setRenditions(List.of(rendition(RATIO_16_10, 128), rendition(RATIO_16_10, 64), rendition(RATIO_16_10, 32), rendition(RATIO_16_10, 16), + rendition(RATIO_4_3, 40), rendition(RATIO_4_3, 20))); media.setMap((List)EXPECTED_AREAS_RESOLVED); HtmlElement picture = builder.build(media); diff --git a/src/test/java/io/wcm/handler/media/testcontext/AppAemContext.java b/src/test/java/io/wcm/handler/media/testcontext/AppAemContext.java index 38c7f0c3..c4433a00 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/AppAemContext.java +++ b/src/test/java/io/wcm/handler/media/testcontext/AppAemContext.java @@ -31,7 +31,7 @@ import io.wcm.handler.media.format.impl.MediaFormatProviderManagerImpl; import io.wcm.handler.media.impl.DefaultMediaHandlerConfig; -import io.wcm.handler.media.impl.MediaHandlerConfigAdapterFactory; +import io.wcm.handler.media.impl.MediaHandlerAdapterFactory; import io.wcm.handler.media.spi.MediaFormatProvider; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl; @@ -100,13 +100,13 @@ public static AemContext newAemContext(@Nullable ResourceResolverType resourceRe public void execute(@NotNull AemContext context) throws Exception { // handler SPI - context.registerInjectActivateService(new SiteRootDetectorImpl()); - context.registerInjectActivateService(new UrlHandlerAdapterFactory()); - context.registerInjectActivateService(new ClientlibProxyRewriterImpl()); - context.registerInjectActivateService(new DefaultUrlHandlerConfig()); + context.registerInjectActivateService(SiteRootDetectorImpl.class); + context.registerInjectActivateService(UrlHandlerAdapterFactory.class); + context.registerInjectActivateService(ClientlibProxyRewriterImpl.class); + context.registerInjectActivateService(DefaultUrlHandlerConfig.class); context.registerService(UrlHandlerConfig.class, new DummyUrlHandlerConfig()); - context.registerInjectActivateService(new MediaHandlerConfigAdapterFactory()); - context.registerInjectActivateService(new DefaultMediaHandlerConfig()); + context.registerInjectActivateService(MediaHandlerAdapterFactory.class); + context.registerInjectActivateService(DefaultMediaHandlerConfig.class); context.registerService(MediaHandlerConfig.class, new DummyMediaHandlerConfig()); // context path strategy @@ -114,10 +114,10 @@ public void execute(@NotNull AemContext context) throws Exception { // media formats context.registerService(MediaFormatProvider.class, new DummyMediaFormatProvider()); - context.registerInjectActivateService(new MediaFormatProviderManagerImpl()); + context.registerInjectActivateService(MediaFormatProviderManagerImpl.class); // dynamic media - context.registerInjectActivateService(new DynamicMediaSupportServiceImpl()); + context.registerInjectActivateService(DynamicMediaSupportServiceImpl.class); // sling models registration context.addModelsForPackage( diff --git a/src/test/java/io/wcm/handler/media/testcontext/DummyMediaFormats.java b/src/test/java/io/wcm/handler/media/testcontext/DummyMediaFormats.java index 0a11007e..f662a0e5 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/DummyMediaFormats.java +++ b/src/test/java/io/wcm/handler/media/testcontext/DummyMediaFormats.java @@ -21,7 +21,6 @@ import static io.wcm.handler.media.format.MediaFormatBuilder.create; -import io.wcm.handler.media.MediaNameConstants; import io.wcm.handler.media.format.MediaFormat; public final class DummyMediaFormats { @@ -507,12 +506,12 @@ private DummyMediaFormats() { .build(); /* format for testing ratio validation */ - public static final MediaFormat RATIO = create("ratio") + public static final MediaFormat RATIO_16_10 = create("ratio_16_10") .label("Ratio (16:10)") .ratio(16, 10) .extensions("gif", "jpg", "png") .build(); - public static final MediaFormat RATIO2 = create("ratio2") + public static final MediaFormat RATIO_4_3 = create("ratio_4_3") .label("Ratio (4:3)") .ratio(4, 3) .extensions("gif", "jpg", "png") @@ -523,20 +522,4 @@ private DummyMediaFormats() { .extensions("gif", "jpg", "png", "tif", "svg") .build(); - /* formats for testing responsive images */ - @SuppressWarnings("deprecation") - public static final MediaFormat RESPONSIVE_32_9_M1 = create("responsive_32_9_m1") - .label("Responsive 32:9") - .fixedDimension(1281, 360) - .extensions("gif", "jpg", "jpeg", "png") - .property(MediaNameConstants.PROP_BREAKPOINT, "M1") - .build(); - @SuppressWarnings("deprecation") - public static final MediaFormat RESPONSIVE_32_9_L1 = create("responsive_32_9_l1") - .label("Responsive 32:9") - .fixedDimension(1920, 540) - .extensions("gif", "jpg", "jpeg", "png") - .property(MediaNameConstants.PROP_BREAKPOINT, "L1") - .build(); - } diff --git a/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java b/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java index 03dbc4dd..6944ca3a 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java +++ b/src/test/java/io/wcm/handler/media/testcontext/DummyMediaHandlerConfig.java @@ -23,8 +23,6 @@ import org.jetbrains.annotations.NotNull; -import com.google.common.collect.ImmutableList; - import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.media.spi.MediaSource; import io.wcm.handler.mediasource.dam.DamMediaSource; @@ -35,10 +33,9 @@ */ public class DummyMediaHandlerConfig extends MediaHandlerConfig { - private static final List> MEDIA_SOURCES = ImmutableList.>of( + private static final List> MEDIA_SOURCES = List.of( DamMediaSource.class, - InlineMediaSource.class - ); + InlineMediaSource.class); @Override public @NotNull List> getSources() { diff --git a/src/test/java/io/wcm/handler/media/testcontext/DummyUrlHandlerConfig.java b/src/test/java/io/wcm/handler/media/testcontext/DummyUrlHandlerConfig.java index 5c3eac04..4cc5a70f 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/DummyUrlHandlerConfig.java +++ b/src/test/java/io/wcm/handler/media/testcontext/DummyUrlHandlerConfig.java @@ -24,8 +24,6 @@ import org.apache.sling.api.resource.Resource; import org.jetbrains.annotations.NotNull; -import com.google.common.collect.ImmutableList; - import io.wcm.handler.url.integrator.IntegratorMode; import io.wcm.handler.url.integrator.IntegratorModes; import io.wcm.handler.url.spi.UrlHandlerConfig; @@ -37,10 +35,9 @@ public class DummyUrlHandlerConfig extends UrlHandlerConfig { public static final int SITE_ROOT_LEVEL = 4; - private static final List INTEGRATOR_MODES = ImmutableList.of( + private static final List INTEGRATOR_MODES = List.of( IntegratorModes.SIMPLE, - IntegratorModes.EXTENDED - ); + IntegratorModes.EXTENDED); @Override public int getSiteRootLevel(Resource resource) { diff --git a/src/test/java/io/wcm/handler/media/testcontext/MediaSourceDamAppAemContext.java b/src/test/java/io/wcm/handler/media/testcontext/MediaSourceDamAppAemContext.java index 1a2ad879..72acb2ad 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/MediaSourceDamAppAemContext.java +++ b/src/test/java/io/wcm/handler/media/testcontext/MediaSourceDamAppAemContext.java @@ -33,7 +33,7 @@ import io.wcm.handler.media.format.impl.MediaFormatProviderManagerImpl; import io.wcm.handler.media.impl.DefaultMediaHandlerConfig; -import io.wcm.handler.media.impl.MediaHandlerConfigAdapterFactory; +import io.wcm.handler.media.impl.MediaHandlerAdapterFactory; import io.wcm.handler.media.spi.MediaFormatProvider; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl; @@ -97,13 +97,13 @@ public static AemContext newAemContext(ResourceResolverType resourceResolverType public void execute(@NotNull AemContext context) throws PersistenceException, IOException { // handler SPI - context.registerInjectActivateService(new SiteRootDetectorImpl()); - context.registerInjectActivateService(new UrlHandlerAdapterFactory()); - context.registerInjectActivateService(new ClientlibProxyRewriterImpl()); - context.registerInjectActivateService(new DefaultUrlHandlerConfig()); + context.registerInjectActivateService(SiteRootDetectorImpl.class); + context.registerInjectActivateService(UrlHandlerAdapterFactory.class); + context.registerInjectActivateService(ClientlibProxyRewriterImpl.class); + context.registerInjectActivateService(DefaultUrlHandlerConfig.class); context.registerService(UrlHandlerConfig.class, new DummyUrlHandlerConfig()); - context.registerInjectActivateService(new MediaHandlerConfigAdapterFactory()); - context.registerInjectActivateService(new DefaultMediaHandlerConfig()); + context.registerInjectActivateService(MediaHandlerAdapterFactory.class); + context.registerInjectActivateService(DefaultMediaHandlerConfig.class); context.registerService(MediaHandlerConfig.class, new DummyMediaHandlerConfig()); // context path strategy @@ -111,10 +111,10 @@ public void execute(@NotNull AemContext context) throws PersistenceException, IO // media formats context.registerService(MediaFormatProvider.class, new DummyMediaFormatProvider()); - context.registerInjectActivateService(new MediaFormatProviderManagerImpl()); + context.registerInjectActivateService(MediaFormatProviderManagerImpl.class); // dynamic media - context.registerInjectActivateService(new DynamicMediaSupportServiceImpl()); + context.registerInjectActivateService(DynamicMediaSupportServiceImpl.class); // sling models registration context.addModelsForPackage("io.wcm.handler.media", diff --git a/src/test/java/io/wcm/handler/media/testcontext/MediaSourceInlineAppAemContext.java b/src/test/java/io/wcm/handler/media/testcontext/MediaSourceInlineAppAemContext.java index 743db9ba..baadf2c5 100644 --- a/src/test/java/io/wcm/handler/media/testcontext/MediaSourceInlineAppAemContext.java +++ b/src/test/java/io/wcm/handler/media/testcontext/MediaSourceInlineAppAemContext.java @@ -33,7 +33,7 @@ import io.wcm.handler.media.format.impl.MediaFormatProviderManagerImpl; import io.wcm.handler.media.impl.DefaultMediaHandlerConfig; -import io.wcm.handler.media.impl.MediaHandlerConfigAdapterFactory; +import io.wcm.handler.media.impl.MediaHandlerAdapterFactory; import io.wcm.handler.media.spi.MediaFormatProvider; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl; @@ -86,13 +86,13 @@ public static AemContext newAemContext() { public void execute(@NotNull AemContext context) throws PersistenceException, IOException { // handler SPI - context.registerInjectActivateService(new SiteRootDetectorImpl()); - context.registerInjectActivateService(new UrlHandlerAdapterFactory()); - context.registerInjectActivateService(new ClientlibProxyRewriterImpl()); - context.registerInjectActivateService(new DefaultUrlHandlerConfig()); + context.registerInjectActivateService(SiteRootDetectorImpl.class); + context.registerInjectActivateService(UrlHandlerAdapterFactory.class); + context.registerInjectActivateService(ClientlibProxyRewriterImpl.class); + context.registerInjectActivateService(DefaultUrlHandlerConfig.class); context.registerService(UrlHandlerConfig.class, new DummyUrlHandlerConfig()); - context.registerInjectActivateService(new MediaHandlerConfigAdapterFactory()); - context.registerInjectActivateService(new DefaultMediaHandlerConfig()); + context.registerInjectActivateService(MediaHandlerAdapterFactory.class); + context.registerInjectActivateService(DefaultMediaHandlerConfig.class); context.registerService(MediaHandlerConfig.class, new DummyMediaHandlerConfig()); // context path strategy @@ -100,10 +100,10 @@ public void execute(@NotNull AemContext context) throws PersistenceException, IO // media formats context.registerService(MediaFormatProvider.class, new DummyMediaFormatProvider()); - context.registerInjectActivateService(new MediaFormatProviderManagerImpl()); + context.registerInjectActivateService(MediaFormatProviderManagerImpl.class); // dynamic media - context.registerInjectActivateService(new DynamicMediaSupportServiceImpl()); + context.registerInjectActivateService(DynamicMediaSupportServiceImpl.class); // sling models registration context.addModelsForPackage("io.wcm.handler.media", diff --git a/src/test/java/io/wcm/handler/media/testcontext/UriTemplateAssert.java b/src/test/java/io/wcm/handler/media/testcontext/UriTemplateAssert.java new file mode 100644 index 00000000..797ebbf7 --- /dev/null +++ b/src/test/java/io/wcm/handler/media/testcontext/UriTemplateAssert.java @@ -0,0 +1,62 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.media.testcontext; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.wcm.handler.media.Media; +import io.wcm.handler.media.Rendition; +import io.wcm.handler.media.UriTemplate; +import io.wcm.handler.media.UriTemplateType; + +/** + * Helper methods for asserting URI templates. + */ +public final class UriTemplateAssert { + + private UriTemplateAssert() { + // static methods only + } + + public static void assertUriTemplate(Media media, UriTemplateType type, + long expectedMaxWith, long expectedMaxHeight, String expectedTemplate) { + assertTrue(media.isValid(), "media valid"); + UriTemplate uriTemplate = media.getAsset().getUriTemplate(type); + assertEquals(type, uriTemplate.getType(), "uriTemplateType"); + assertEquals(expectedMaxWith, uriTemplate.getMaxWidth(), "maxWidth(" + type + ")"); + assertEquals(expectedMaxHeight, uriTemplate.getMaxHeight(), "maxHeight(" + type + ")"); + assertEquals(expectedTemplate, uriTemplate.getUriTemplate(), "uriTemplate(" + type + ")"); + assertEquals(expectedTemplate, uriTemplate.toString(), "toString(" + type + ")"); + } + + public static void assertUriTemplate(Rendition rendition, UriTemplateType type, + long expectedMaxWith, long expectedMaxHeight, String expectedTemplate) { + assertNotNull(rendition, "rendition valid"); + UriTemplate uriTemplate = rendition.getUriTemplate(type); + assertEquals(type, uriTemplate.getType(), "uriTemplateType"); + assertEquals(expectedMaxWith, uriTemplate.getMaxWidth(), "maxWidth(" + type + ")"); + assertEquals(expectedMaxHeight, uriTemplate.getMaxHeight(), "maxHeight(" + type + ")"); + assertEquals(expectedTemplate, uriTemplate.getUriTemplate(), "uriTemplate(" + type + ")"); + assertEquals(expectedTemplate, uriTemplate.toString(), "toString(" + type + ")"); + } + +} diff --git a/src/test/java/io/wcm/handler/media/ui/ResourceMediaTest.java b/src/test/java/io/wcm/handler/media/ui/ResourceMediaTest.java index aa31a1c4..2edd0368 100644 --- a/src/test/java/io/wcm/handler/media/ui/ResourceMediaTest.java +++ b/src/test/java/io/wcm/handler/media/ui/ResourceMediaTest.java @@ -23,7 +23,7 @@ import static io.wcm.handler.media.testcontext.AppAemContext.ROOTPATH_CONTENT; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_1COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_2COL; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CAMPAIGN; import static org.apache.sling.api.resource.ResourceResolver.PROPERTY_RESOURCE_TYPE; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -62,7 +62,7 @@ class ResourceMediaTest { @BeforeEach void setUp() { asset = context.create().asset("/content/dam/asset1.jpg", - (int)EDITORIAL_2COL.getWidth(), (int)EDITORIAL_2COL.getHeight(), ContentType.JPEG); + EDITORIAL_2COL.getWidth(), EDITORIAL_2COL.getHeight(), ContentType.JPEG); Resource resource = context.create().resource(ROOTPATH_CONTENT + "/jcr:content/media", PROPERTY_RESOURCE_TYPE, "/dummy/resourcetype", @@ -108,7 +108,7 @@ void testWithCss() { @Test void testWithRefCropProperty() { - context.request().setAttribute("mediaFormat", RATIO.getName()); + context.request().setAttribute("mediaFormat", RATIO_16_10.getName()); context.request().setAttribute("refProperty", "myRefProp"); context.request().setAttribute("cropProperty", "myCropProp"); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/DamMediaSourceTest.java b/src/test/java/io/wcm/handler/mediasource/dam/DamMediaSourceTest.java index 99a1d3f3..9722f2b4 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/DamMediaSourceTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/DamMediaSourceTest.java @@ -28,8 +28,8 @@ import static io.wcm.handler.media.testcontext.DummyMediaFormats.MATERIAL_TILE; import static io.wcm.handler.media.testcontext.DummyMediaFormats.NORATIO_LARGE_MINWIDTH; import static io.wcm.handler.media.testcontext.DummyMediaFormats.PRODUCT_CUTOUT_LARGE; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO2; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS_SCALE1; import static io.wcm.handler.media.testcontext.DummyMediaFormats.VIDEO_2COL; @@ -44,13 +44,13 @@ import static org.mockito.Mockito.when; import java.util.List; +import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.ModifiableValueMap; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -63,8 +63,6 @@ import com.day.cq.wcm.api.components.EditConfig; import com.day.cq.wcm.api.components.EditContext; import com.day.cq.wcm.api.components.InplaceEditingConfig; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import io.wcm.handler.commons.dom.Div; import io.wcm.handler.commons.dom.HtmlElement; @@ -626,7 +624,7 @@ void testMultipleMandatoryMediaFormats() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/standard.jpg/_jcr_content/renditions/original./standard.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -648,7 +646,7 @@ void testMultipleMandatoryMediaFormatsWithCropping() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/standard.jpg/_jcr_content/renditions/original.image_file.64.30.2,2,86,42.file/standard.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -661,12 +659,12 @@ void testMultipleMandatoryMediaFormatsWithCropping() { @Test void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal() { - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO); + MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO_16_10); Media media = mediaHandler().get(parSixteenTenMediaRefCrop).args(mediaArgs).build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.84.40.0,0,840,400.file/sixteen-ten.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -674,18 +672,18 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal() { assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", renditions.get(1).getUrl(), "rendition.mediaUrl.2"); - assertEquals(RATIO, renditions.get(1).getMediaFormat()); + assertEquals(RATIO_16_10, renditions.get(1).getMediaFormat()); } @Test void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() { - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO, RATIO2, EDITORIAL_STAGE_SMALL) + MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO_16_10, RATIO_4_3, EDITORIAL_STAGE_SMALL) .autoCrop(true); Media media = mediaHandler().get(parSixteenTenMediaRefCrop).args(mediaArgs).build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(4, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.84.40.0,0,840,400.file/sixteen-ten.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -694,7 +692,7 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.1333.1000.134,0,1467,1000.file/sixteen-ten.jpg", renditions.get(1).getUrl(), "rendition.mediaUrl.1"); - assertEquals(RATIO2, renditions.get(1).getMediaFormat()); + assertEquals(RATIO_4_3, renditions.get(1).getMediaFormat()); assertFalse(renditions.get(1).isFallback()); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.960.150.0,375,1600,625.file/sixteen-ten.jpg", @@ -704,7 +702,7 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", renditions.get(3).getUrl(), "rendition.mediaUrl.2"); - assertEquals(RATIO, renditions.get(3).getMediaFormat()); + assertEquals(RATIO_16_10, renditions.get(3).getMediaFormat()); assertTrue(renditions.get(3).isFallback()); } @@ -715,12 +713,12 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() */ @Test void testMultipleMediaFormatsWithCropping_PreferCroppingOverFallback() { - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(RATIO, SHOWROOM_CONTROLS); + MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(RATIO_16_10, SHOWROOM_CONTROLS); Media media = mediaHandler().get(parSixteenTenMediaRefCrop).args(mediaArgs).build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.84.40.0,0,840,400.file/sixteen-ten.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -728,7 +726,7 @@ void testMultipleMediaFormatsWithCropping_PreferCroppingOverFallback() { assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", renditions.get(1).getUrl(), "rendition.mediaUrl.2"); - assertEquals(RATIO, renditions.get(1).getMediaFormat()); + assertEquals(RATIO_16_10, renditions.get(1).getMediaFormat()); } @Test @@ -739,7 +737,7 @@ void testMultipleMandatoryMediaFormatsNotAllMatch() { assertEquals(MediaInvalidReason.NOT_ENOUGH_MATCHING_RENDITIONS, media.getMediaInvalidReason()); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/standard.jpg/_jcr_content/renditions/cq5dam.web.450.213.jpg./cq5dam.web.450.213.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -760,7 +758,7 @@ void testMultipleMandatoryMediaFormatsNotAllMatch_MixedMandatory() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/standard.jpg/_jcr_content/renditions/cq5dam.web.450.213.jpg./cq5dam.web.450.213.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.2"); @@ -783,7 +781,7 @@ void testOptionalMediaFormatNotMatch_ResponsiveChildFormatsMatch() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/dam/test/standard.jpg/_jcr_content/renditions/cq5dam.web.685.325.jpg.image_file.500.237.file/cq5dam.web.685.325.jpg", renditions.get(0).getUrl(), "Virtual rendition for width option 500px should match"); @@ -837,7 +835,7 @@ void testCustomizeIPEConfig() { // ensure a custom ipe config with a path pointing to IPEConfigResourceProvider is set String expectedPath = IPEConfigResourceProvider.buildPath(parStandardMediaRef.getPath(), - ImmutableSet.of("editorial_1col")); + Set.of("editorial_1col")); doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { @@ -850,60 +848,18 @@ public Object answer(InvocationOnMock invocation) throws Throwable { underTest.setCustomIPECropRatios(div, mediaRequest); } - @Test - @SuppressWarnings("deprecation") - void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats() { - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(new io.wcm.handler.media.format.ResponsiveMediaFormatsBuilder(RATIO) - .breakpoint("B1", 160, 100) - .breakpoint("B2", 320, 200) - .build()); - - Media media = mediaHandler().get(MEDIAITEM_PATH_16_10, mediaArgs).build(); - assertTrue(media.isValid(), "valid?"); - assertNotNull(media.getAsset(), "asset?"); - assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); - - Rendition rendition0 = renditions.get(0); - assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.160.100.file/sixteen-ten.jpg", - rendition0.getUrl(), "rendition.mediaUrl.1"); - assertEquals(160, rendition0.getWidth()); - assertEquals(100, rendition0.getHeight()); - assertEquals(160d / 100d, rendition0.getRatio(), 0.0001); - - MediaFormat mediaFormat0 = renditions.get(0).getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat0.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat0.getRatio(), 0.001d); - assertEquals(160, mediaFormat0.getWidth()); - assertEquals(100, mediaFormat0.getHeight()); - assertEquals("B1", mediaFormat0.getProperties().get(MediaNameConstants.PROP_BREAKPOINT)); - - Rendition rendition1 = renditions.get(1); - assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.320.200.file/sixteen-ten.jpg", - rendition1.getUrl(), "rendition.mediaUrl.2"); - assertEquals(320, rendition1.getWidth()); - assertEquals(200, rendition1.getHeight()); - - MediaFormat mediaFormat1 = renditions.get(1).getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat1.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat1.getRatio(), 0.001d); - assertEquals(320, mediaFormat1.getWidth()); - assertEquals(200, mediaFormat1.getHeight()); - assertEquals("B2", mediaFormat1.getProperties().get(MediaNameConstants.PROP_BREAKPOINT)); - } - @Test void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { Media media = mediaHandler().get(MEDIAITEM_PATH_16_10) - .mediaFormat(RATIO) - .pictureSource(new PictureSource(RATIO).media("media1").widths(160)) - .pictureSource(new PictureSource(RATIO).media("media2").widths(320)) + .mediaFormat(RATIO_16_10) + .pictureSource(new PictureSource(RATIO_16_10).media("media1").widths(160)) + .pictureSource(new PictureSource(RATIO_16_10).media("media2").widths(320)) .build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); Rendition rendition0 = renditions.get(0); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", @@ -913,7 +869,7 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { assertEquals(160d / 100d, rendition0.getRatio(), 0.0001); MediaFormat mediaFormat0 = rendition0.getMediaFormat(); - assertEquals(RATIO.getName(), mediaFormat0.getName()); + assertEquals(RATIO_16_10.getName(), mediaFormat0.getName()); Rendition rendition1 = renditions.get(1); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.160.100.file/sixteen-ten.jpg", @@ -923,8 +879,8 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { assertEquals(160d / 100d, rendition1.getRatio(), 0.0001); MediaFormat mediaFormat1 = rendition1.getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat1.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat1.getRatio(), 0.001d); + assertEquals(RATIO_16_10.getLabel(), mediaFormat1.getLabel()); + assertEquals(RATIO_16_10.getRatio(), mediaFormat1.getRatio(), 0.001d); assertEquals(160, mediaFormat1.getWidth()); Rendition rendition2 = renditions.get(2); @@ -934,8 +890,8 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { assertEquals(200, rendition2.getHeight()); MediaFormat mediaFormat2 = rendition2.getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat2.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat2.getRatio(), 0.001d); + assertEquals(RATIO_16_10.getLabel(), mediaFormat2.getLabel()); + assertEquals(RATIO_16_10.getRatio(), mediaFormat2.getRatio(), 0.001d); assertEquals(320, mediaFormat2.getWidth()); } @@ -950,7 +906,7 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources_NoRat assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); Rendition rendition0 = renditions.get(0); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", @@ -984,14 +940,14 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources_NoRat @Test void testMultipleMediaFormats_ImageSizes() { Media media = mediaHandler().get(MEDIAITEM_PATH_16_10) - .mediaFormats(RATIO2, RATIO) // <- only second media format matches + .mediaFormats(RATIO_4_3, RATIO_16_10) // <- only second media format matches .imageSizes("sizes", 160, 320) .build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); Rendition rendition0 = renditions.get(0); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", @@ -1001,7 +957,7 @@ void testMultipleMediaFormats_ImageSizes() { assertEquals(160d / 100d, rendition0.getRatio(), 0.0001); MediaFormat mediaFormat0 = rendition0.getMediaFormat(); - assertEquals(RATIO.getName(), mediaFormat0.getName()); + assertEquals(RATIO_16_10.getName(), mediaFormat0.getName()); Rendition rendition1 = renditions.get(1); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original.image_file.160.100.file/sixteen-ten.jpg", @@ -1011,8 +967,8 @@ void testMultipleMediaFormats_ImageSizes() { assertEquals(160d / 100d, rendition1.getRatio(), 0.0001); MediaFormat mediaFormat1 = rendition1.getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat1.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat1.getRatio(), 0.001d); + assertEquals(RATIO_16_10.getLabel(), mediaFormat1.getLabel()); + assertEquals(RATIO_16_10.getRatio(), mediaFormat1.getRatio(), 0.001d); assertEquals(160, mediaFormat1.getWidth()); Rendition rendition2 = renditions.get(2); @@ -1022,8 +978,8 @@ void testMultipleMediaFormats_ImageSizes() { assertEquals(200, rendition2.getHeight()); MediaFormat mediaFormat2 = rendition2.getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat2.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat2.getRatio(), 0.001d); + assertEquals(RATIO_16_10.getLabel(), mediaFormat2.getLabel()); + assertEquals(RATIO_16_10.getRatio(), mediaFormat2.getRatio(), 0.001d); assertEquals(320, mediaFormat2.getWidth()); } @@ -1037,7 +993,7 @@ void testImageSizes_UnconstrainedMediaFormatWithoutRatioOrMinSizes() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); Rendition rendition0 = renditions.get(0); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", @@ -1082,7 +1038,7 @@ void testPictureSources_UnconstrainedMediaFormatWithoutRatioOrMinSizes() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); Rendition rendition0 = renditions.get(0); assertEquals("/content/dam/test/sixteen-ten.jpg/_jcr_content/renditions/original./sixteen-ten.jpg", diff --git a/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateRenditionTest.java b/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateRenditionTest.java new file mode 100644 index 00000000..f10201e2 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateRenditionTest.java @@ -0,0 +1,327 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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; + +import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; +import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_CROP; +import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_REF; +import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_ROTATION; +import static io.wcm.handler.media.UriTemplateType.CROP_CENTER; +import static io.wcm.handler.media.UriTemplateType.SCALE_HEIGHT; +import static io.wcm.handler.media.UriTemplateType.SCALE_WIDTH; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_SQUARE; +import static io.wcm.handler.media.testcontext.UriTemplateAssert.assertUriTemplate; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.CROP_TYPE_SMART; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_BANNER; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_CROP_TYPE; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_HEIGHT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_WIDTH; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.sling.api.resource.Resource; +import org.apache.sling.testing.mock.osgi.MapUtil; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.day.cq.dam.api.Asset; +import com.day.cq.dam.api.DamConstants; +import com.day.cq.dam.scene7.api.constants.Scene7Constants; + +import io.wcm.handler.media.Media; +import io.wcm.handler.media.MediaArgs.PictureSource; +import io.wcm.handler.media.MediaHandler; +import io.wcm.handler.media.Rendition; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.handler.mediasource.dam.impl.metadata.AssetSynchonizationService; +import io.wcm.handler.mediasource.dam.impl.metadata.RenditionMetadataListenerService; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; +import io.wcm.wcm.commons.contenttype.ContentType; + +/** + * Test DAM URI template for renditions. + */ +@ExtendWith(AemContextExtension.class) +class DamUriTemplateRenditionTest { + + final AemContext context = AppAemContext.newAemContext(); + + private MediaHandler mediaHandler; + + @BeforeEach + void setUp() { + // register RenditionMetadataListenerService to generate rendition metadata + context.registerInjectActivateService(new AssetSynchonizationService()); + context.registerInjectActivateService(new RenditionMetadataListenerService(), + "threadPoolSize", 0, + "allowedRunMode", new String[0]); + + mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + } + + @Test + void testOriginal_CropCenter() { + Asset asset = createSampleAsset(); + Media media = mediaHandler.get(asset.getPath()) + .mediaFormat(RATIO_16_10) + .build(); + Rendition rendition = media.getRendition(); + + // CROP_CENTER not supported for renditions + assertThrows(IllegalArgumentException.class, () -> { + rendition.getUriTemplate(CROP_CENTER); + }); + } + + @Test + void testOriginal() { + Asset asset = createSampleAsset(); + Media media = mediaHandler.get(asset.getPath()) + .mediaFormat(RATIO_16_10) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 192, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 192, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.file/sample.jpg"); + } + + @Test + void testOriginal_DynamicMedia() { + Asset asset = createSampleAssetWithDynamicMedia(); + Media media = mediaHandler.get(asset.getPath()) + .mediaFormat(RATIO_16_10) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 192, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 192, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?hei={height}"); + } + + @Test + void test4_3() { + Asset asset = createSampleAsset(); + Media media = mediaHandler.get(asset.getPath()) + .mediaFormat(RATIO_4_3) + .autoCrop(true) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 120, 90, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/rendition43.jpg.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 120, 90, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/rendition43.jpg.image_file.0.{height}.file/sample.jpg"); + } + + @Test + void test4_3_DynamicMedia() { + Asset asset = createSampleAssetWithDynamicMedia(); + Media media = mediaHandler.get(asset.getPath()) + .mediaFormat(RATIO_4_3) + .autoCrop(true) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 160, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=16,0,160,120&wid={width}"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 160, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=16,0,160,120&hei={height}"); + } + + @Test + void testMultiple() { + Asset asset = createSampleAsset(); + Media media = mediaHandler.get(asset.getPath()) + .pictureSource(new PictureSource(RATIO_16_10).widths(120, 96)) + .pictureSource(new PictureSource(RATIO_SQUARE).widths(100)) + .pictureSource(new PictureSource(RATIO_4_3).widths(144)) + .autoCrop(true) + .build(); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(4, renditions.size()); + + assertUriTemplate(renditions.get(0), SCALE_WIDTH, 192, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(renditions.get(1), SCALE_WIDTH, 192, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(renditions.get(2), SCALE_WIDTH, 120, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.36,0,156,120.file/sample.jpg"); + assertUriTemplate(renditions.get(3), SCALE_WIDTH, 160, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.16,0,176,120.file/sample.jpg"); + + assertUriTemplate(renditions.get(0), SCALE_HEIGHT, 192, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.file/sample.jpg"); + assertUriTemplate(renditions.get(1), SCALE_HEIGHT, 192, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.file/sample.jpg"); + assertUriTemplate(renditions.get(2), SCALE_HEIGHT, 120, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.36,0,156,120.file/sample.jpg"); + assertUriTemplate(renditions.get(3), SCALE_HEIGHT, 160, 120, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.16,0,176,120.file/sample.jpg"); + } + + @Test + void testMultiple_DynamicMedia() { + Asset asset = createSampleAssetWithDynamicMedia(); + Media media = mediaHandler.get(asset.getPath()) + .pictureSource(new PictureSource(RATIO_16_10).widths(120, 96)) + .pictureSource(new PictureSource(RATIO_SQUARE).widths(100)) + .pictureSource(new PictureSource(RATIO_4_3).widths(144)) + .autoCrop(true) + .build(); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(4, renditions.size()); + + assertUriTemplate(renditions.get(0), SCALE_WIDTH, 192, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}"); + assertUriTemplate(renditions.get(1), SCALE_WIDTH, 192, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}"); + assertUriTemplate(renditions.get(2), SCALE_WIDTH, 120, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=36,0,120,120&wid={width}"); + assertUriTemplate(renditions.get(3), SCALE_WIDTH, 160, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=16,0,160,120&wid={width}"); + + assertUriTemplate(renditions.get(0), SCALE_HEIGHT, 192, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?hei={height}"); + assertUriTemplate(renditions.get(1), SCALE_HEIGHT, 192, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?hei={height}"); + assertUriTemplate(renditions.get(2), SCALE_HEIGHT, 120, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=36,0,120,120&hei={height}"); + assertUriTemplate(renditions.get(3), SCALE_HEIGHT, 160, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=16,0,160,120&hei={height}"); + } + + @Test + void testMultiple_DynamicMedia_SmartCropping() { + Asset asset = createSampleAssetWithDynamicMedia(); + + // create image profile and assign it to folder + Resource profile1 = context.create().resource("/conf/global/settings/dam/adminui-extension/imageprofile/profile1", + PN_CROP_TYPE, CROP_TYPE_SMART, + PN_BANNER, "16-10,16,10|4-3,40,30"); + context.create().resource("/content/dam/folder1/" + JCR_CONTENT, DamConstants.IMAGE_PROFILE, profile1.getPath()); + + // create DM smart cropping metadata + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/16-10/" + JCR_CONTENT, + PN_NORMALIZED_WIDTH, 0.9d, // 173px + PN_NORMALIZED_HEIGHT, 0.9d); // 108px + context.create().resource(asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + "/4-3/" + JCR_CONTENT, + PN_NORMALIZED_WIDTH, 0.6d, // 115px + PN_NORMALIZED_HEIGHT, 0.72d); // 86px + + Media media = mediaHandler.get(asset.getPath()) + .pictureSource(new PictureSource(RATIO_16_10).widths(120, 96)) + .pictureSource(new PictureSource(RATIO_SQUARE).widths(100)) + .pictureSource(new PictureSource(RATIO_4_3).widths(60)) + .autoCrop(true) + .build(); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(4, renditions.size()); + + assertUriTemplate(renditions.get(0), SCALE_WIDTH, 173, 108, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg%3A16-10?wid={width}&fit=constrain"); + assertUriTemplate(renditions.get(1), SCALE_WIDTH, 173, 108, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg%3A16-10?wid={width}&fit=constrain"); + assertUriTemplate(renditions.get(2), SCALE_WIDTH, 120, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=36,0,120,120&wid={width}"); + assertUriTemplate(renditions.get(3), SCALE_WIDTH, 115, 86, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg%3A4-3?wid={width}&fit=constrain"); + + assertUriTemplate(renditions.get(0), SCALE_HEIGHT, 173, 108, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg%3A16-10?hei={height}&fit=constrain"); + assertUriTemplate(renditions.get(1), SCALE_HEIGHT, 173, 108, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg%3A16-10?hei={height}&fit=constrain"); + assertUriTemplate(renditions.get(2), SCALE_HEIGHT, 120, 120, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=36,0,120,120&hei={height}"); + assertUriTemplate(renditions.get(3), SCALE_HEIGHT, 115, 86, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg%3A4-3?hei={height}&fit=constrain"); + } + + @Test + @SuppressWarnings("null") + void testManualCroppingRotation() { + Asset asset = createSampleAsset(); + Resource content = context.create().resource(context.currentPage(), "asset", + PN_MEDIA_REF, asset.getPath(), + PN_MEDIA_CROP, "5,5,80,55", + PN_MEDIA_ROTATION, 90); + Media media = mediaHandler.get(content) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 50, 75, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.5,5,80,55.90.file/sample.jpg"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 50, 75, + "/content/dam/folder1/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.5,5,80,55.90.file/sample.jpg"); + } + + @Test + @SuppressWarnings("null") + void testManualCroppingRotation_DynamicMedia() { + Asset asset = createSampleAssetWithDynamicMedia(); + Resource content = context.create().resource(context.currentPage(), "asset", + PN_MEDIA_REF, asset.getPath(), + PN_MEDIA_CROP, "5,5,80,55", + PN_MEDIA_ROTATION, 90); + Media media = mediaHandler.get(content) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 50, 75, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=5,5,75,50&rotate=90&wid={width}"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 50, 75, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?crop=5,5,75,50&rotate=90&hei={height}"); + } + + Asset createSampleAsset() { + return createSampleAsset(false); + } + + Asset createSampleAssetWithDynamicMedia() { + return createSampleAsset(true); + } + + Asset createSampleAsset(boolean dmMetadata) { + context.create().resource("/content/dam/folder1"); + + Map metadata; + if (dmMetadata) { + metadata = MapUtil.toMap(Scene7Constants.PN_S7_FILE, "DummyFolder/sample.jpg"); + } + else { + metadata = Collections.emptyMap(); + } + // create asset with original in 16:10 and rendition in 4:3 format + Asset asset = context.create().asset("/content/dam/folder1/sample.jpg", 192, 120, ContentType.JPEG, metadata); + context.create().assetRenditionWebEnabled(asset, 192, 120); + context.create().assetRendition(asset, "rendition43.jpg", 120, 90, ContentType.JPEG); + return asset; + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateTest.java b/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateTest.java index 2708cf05..4f5f46c5 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/DamUriTemplateTest.java @@ -2,7 +2,7 @@ * #%L * wcm.io * %% - * Copyright (C) 2014 wcm.io + * Copyright (C) 2021 wcm.io * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,16 @@ */ package io.wcm.handler.mediasource.dam; -import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl.ASSETS_SCENE7_FEATURE_FLAG_PID; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static io.wcm.handler.media.UriTemplateType.CROP_CENTER; +import static io.wcm.handler.media.UriTemplateType.SCALE_HEIGHT; +import static io.wcm.handler.media.UriTemplateType.SCALE_WIDTH; +import static io.wcm.handler.media.testcontext.UriTemplateAssert.assertUriTemplate; + +import java.util.Collections; +import java.util.Map; import org.apache.commons.io.FilenameUtils; -import org.apache.sling.featureflags.impl.ConfiguredFeature; +import org.apache.sling.testing.mock.osgi.MapUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -33,8 +38,6 @@ import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaHandler; -import io.wcm.handler.media.UriTemplate; -import io.wcm.handler.media.UriTemplateType; import io.wcm.handler.media.testcontext.AppAemContext; import io.wcm.sling.commons.adapter.AdaptTo; import io.wcm.testing.mock.aem.junit5.AemContext; @@ -43,7 +46,7 @@ import io.wcm.wcm.commons.contenttype.FileExtension; /** - * Test DAM URI template + * Test DAM URI template for assets. */ @ExtendWith(AemContextExtension.class) class DamUriTemplateTest { @@ -51,112 +54,71 @@ class DamUriTemplateTest { final AemContext context = AppAemContext.newAemContext(); private MediaHandler mediaHandler; - private Asset asset; @BeforeEach void setUp() { mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); - asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); } @Test - void testGetUriTemplate_CropCenter() { + void testGetUriTemplate() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); Media media = mediaHandler.get(asset.getPath()).build(); - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.CROP_CENTER); - assertEquals("/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.{height}.file/sample.jpg", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.CROP_CENTER, uriTemplate.getType()); + assertUriTemplate(media, CROP_CENTER, 100, 50, + "/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.{height}.file/sample.jpg"); + assertUriTemplate(media, SCALE_WIDTH, 100, 50, + "/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(media, SCALE_HEIGHT, 100, 50, + "/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.file/sample.jpg"); } @Test void testGetUriTemplate_CropCenter_EnforceOutputFileExtension() { + Asset asset = createSampleAsset("/filetype/sample.jpg", ContentType.JPEG); Media media = mediaHandler.get(asset.getPath()) .enforceOutputFileExtension(FileExtension.PNG) .build(); - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.CROP_CENTER); - assertEquals("/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.{height}.file/sample.png", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.CROP_CENTER, uriTemplate.getType()); - } - - @Test - void testGetUriTemplate_ScaleWidth() { - Media media = mediaHandler.get(asset.getPath()).build(); - - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.SCALE_WIDTH); - assertEquals("/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.file/sample.jpg", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.SCALE_WIDTH, uriTemplate.getType()); - } - - @Test - void testGetUriTemplate_ScaleHeight() { - Media media = mediaHandler.get(asset.getPath()).build(); - - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.SCALE_HEIGHT); - assertEquals("/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.file/sample.jpg", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.SCALE_HEIGHT, uriTemplate.getType()); + assertUriTemplate(media, CROP_CENTER, 100, 50, + "/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.{height}.file/sample.png"); + assertUriTemplate(media, SCALE_WIDTH, 100, 50, + "/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.{width}.0.file/sample.png"); + assertUriTemplate(media, SCALE_HEIGHT, 100, 50, + "/content/dam/sample.jpg/_jcr_content/renditions/original.image_file.0.{height}.file/sample.png"); } @Test - void testGetUriTemplate_CropCenter_DynamicMedia() { - // activate dynamic media - context.registerInjectActivateService(new ConfiguredFeature(), - "name", ASSETS_SCENE7_FEATURE_FLAG_PID, - "enabled", true); - + void testGetUriTemplate_DynamicMedia() { + Asset asset = createSampleAssetWithDynamicMedia("/filetype/sample.jpg", ContentType.JPEG); Media media = mediaHandler.get(asset.getPath()).build(); - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.CROP_CENTER); - assertEquals("https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}&hei={height}&fit=crop", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.CROP_CENTER, uriTemplate.getType()); + assertUriTemplate(media, CROP_CENTER, 100, 50, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}&hei={height}&fit=crop"); + assertUriTemplate(media, SCALE_WIDTH, 100, 50, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}"); + assertUriTemplate(media, SCALE_HEIGHT, 100, 50, + "https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?hei={height}"); } - @Test - void testGetUriTemplate_ScaleWidth_DynamicMedia() { - // activate dynamic media - context.registerInjectActivateService(new ConfiguredFeature(), - "name", ASSETS_SCENE7_FEATURE_FLAG_PID, - "enabled", true); - - Media media = mediaHandler.get(asset.getPath()).build(); - - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.SCALE_WIDTH); - assertEquals("https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?wid={width}", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.SCALE_WIDTH, uriTemplate.getType()); + Asset createSampleAsset(String classpathResource, String contentType) { + return createSampleAsset(classpathResource, contentType, false); } - @Test - void testGetUriTemplate_ScaleHeight_DynamicMedia() { - // activate dynamic media - context.registerInjectActivateService(new ConfiguredFeature(), - "name", ASSETS_SCENE7_FEATURE_FLAG_PID, - "enabled", true); - - Media media = mediaHandler.get(asset.getPath()).build(); - - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.SCALE_HEIGHT); - assertEquals("https://dummy.scene7.com/is/image/DummyFolder/sample.jpg?hei={height}", uriTemplate.getUriTemplate()); - assertEquals(100, uriTemplate.getMaxWidth()); - assertEquals(50, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.SCALE_HEIGHT, uriTemplate.getType()); + Asset createSampleAssetWithDynamicMedia(String classpathResource, String contentType) { + return createSampleAsset(classpathResource, contentType, true); } - Asset createSampleAsset(String classpathResource, String contentType) { + Asset createSampleAsset(String classpathResource, String contentType, boolean dmMetadata) { String fileName = FilenameUtils.getName(classpathResource); - return context.create().asset("/content/dam/" + fileName, classpathResource, contentType, - Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); + Map metadata; + if (dmMetadata) { + metadata = MapUtil.toMap(Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); + } + else { + metadata = Collections.emptyMap(); + } + return context.create().asset("/content/dam/" + fileName, classpathResource, contentType, metadata); } } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/AutoCroppingMediaHandlerTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/AutoCroppingMediaHandlerTest.java index 403ee79f..70aabdf3 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/AutoCroppingMediaHandlerTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/AutoCroppingMediaHandlerTest.java @@ -21,7 +21,7 @@ import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_1COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_2COL; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -82,7 +82,7 @@ void setUp() { @Test void testMediaFormatWithRatio() { Media media = mediaHandler.get(resource) - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .build(); assertTrue(media.isValid()); Rendition rendition = media.getRendition(); @@ -100,7 +100,7 @@ void testMediaFormatFixedDimension() { Rendition rendition = media.getRendition(); assertEquals(215, rendition.getWidth()); assertEquals(102, rendition.getHeight()); - assertEquals("/content/dam/test.jpg/_jcr_content/renditions/original.image_file.215.102.0,5,400,194.file/test.jpg", media.getUrl()); + assertEquals("/content/dam/test.jpg/_jcr_content/renditions/original.image_file.215.102.0,5,400,195.file/test.jpg", media.getUrl()); } @Test @@ -112,7 +112,7 @@ void testMultipleMediaFormatsFixedDimension() { Rendition rendition = media.getRendition(); assertEquals(215, rendition.getWidth()); assertEquals(102, rendition.getHeight()); - assertEquals("/content/dam/test.jpg/_jcr_content/renditions/original.image_file.215.102.0,5,400,194.file/test.jpg", media.getUrl()); + assertEquals("/content/dam/test.jpg/_jcr_content/renditions/original.image_file.215.102.0,6,400,195.file/test.jpg", media.getUrl()); } @Test @@ -134,7 +134,7 @@ void testManualCroppingParametersHaveHigherPrecedence() { MediaNameConstants.PN_MEDIA_CROP, new CropDimension(0, 0, 120, 75).getCropString()); Media media = mediaHandler.get(resource2) - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .build(); assertTrue(media.isValid()); Rendition rendition = media.getRendition(); @@ -154,7 +154,7 @@ void testInvalidManualCroppingParametersFallbackToAutoCropping() { MediaNameConstants.PN_MEDIA_CROP, new CropDimension(20, 20, 50, 50).getCropString()); Media media = mediaHandler.get(resource2) - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .build(); assertTrue(media.isValid()); Rendition rendition = media.getRendition(); @@ -167,7 +167,7 @@ void testInvalidManualCroppingParametersFallbackToAutoCropping() { @Test void testMediaFormatWithRatio_WebRenditionsExcludedFromMediaHandling() { Media media = mediaHandler.get(resource) - .mediaFormat(RATIO) + .mediaFormat(RATIO_16_10) .includeAssetWebRenditions(false) .build(); assertTrue(media.isValid()); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java index e418b05e..757e8db1 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/TransformedRenditionHandlerTest.java @@ -74,7 +74,7 @@ void setUp() throws Exception { // generate web-enabled rendition webRendition = context.create().assetRenditionWebEnabled(asset, 200, 150); - cropDimension = new CropDimension(20, 10, 100, 30); + cropDimension = new CropDimension(40, 20, 200, 60); } @Test @@ -95,7 +95,7 @@ void testCroppingWithoutWebRendition() throws PersistenceException { TransformedRenditionHandler underTest = new TransformedRenditionHandler(cropDimension, null, damContext); assertEquals(1, underTest.getAvailableRenditions(new MediaArgs()).size()); RenditionMetadata firstRendition = underTest.getAvailableRenditions(new MediaArgs()).iterator().next(); - assertEquals("/content/dam/cropTest.jpg/jcr:content/renditions/original.image_file.100.30.20,10,120,40.file/cropTest.jpg", + assertEquals("/content/dam/cropTest.jpg/jcr:content/renditions/original.image_file.200.60.40,20,240,80.file/cropTest.jpg", firstRendition.getMediaPath(false)); } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/DamAutoCroppingTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/WebEnabledRenditionCroppingTest.java similarity index 55% rename from src/test/java/io/wcm/handler/mediasource/dam/impl/DamAutoCroppingTest.java rename to src/test/java/io/wcm/handler/mediasource/dam/impl/WebEnabledRenditionCroppingTest.java index d1cc757e..3ee4b1c4 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/DamAutoCroppingTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/WebEnabledRenditionCroppingTest.java @@ -2,7 +2,7 @@ * #%L * wcm.io * %% - * Copyright (C) 2019 wcm.io + * Copyright (C) 2022 wcm.io * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,37 +21,47 @@ import static com.day.cq.dam.api.DamConstants.PREFIX_ASSET_WEB; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import com.day.cq.dam.api.Asset; -import com.day.cq.dam.api.Rendition; +import io.wcm.handler.media.CropDimension; import io.wcm.handler.media.testcontext.AppAemContext; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; @ExtendWith(AemContextExtension.class) -class DamAutoCroppingTest { +class WebEnabledRenditionCroppingTest { private final AemContext context = AppAemContext.newAemContext(); @Test - @SuppressWarnings("null") - void testGetWebRenditionForCropping() { + void testGetCropDimensionForOriginal() { Asset asset = context.create().asset("/content/dam/asset1.jpg", 160, 90, "image/jpeg"); - Rendition webRendition = context.create().assetRendition(asset, PREFIX_ASSET_WEB + ".80.45.jpg", 80, 45, "image/jpeg"); + context.create().assetRendition(asset, PREFIX_ASSET_WEB + ".80.45.jpg", 80, 45, "image/jpeg"); - RenditionMetadata result = DamAutoCropping.getWebRenditionForCropping(asset); - assertEquals(webRendition.getPath(), result.getRendition().getPath()); + CropDimension result = WebEnabledRenditionCropping.getCropDimensionForOriginal(asset, + new CropDimension(10, 15, 20, 30)); + + assertEquals(20, result.getLeft()); + assertEquals(30, result.getTop()); + assertEquals(40, result.getWidth()); + assertEquals(60, result.getHeight()); } @Test - void testGetWebRenditionNotExisting() { - Asset assetWithoutRenditions = context.create().asset("/content/dam/asset2.jpg", 160, 90, "image/jpeg"); - assertNull(DamAutoCropping.getWebRenditionForCropping(assetWithoutRenditions)); + void testGetCropDimensionForOriginal_WebEnabledRenditionDoesNotExist() { + Asset asset = context.create().asset("/content/dam/asset2.jpg", 160, 90, "image/jpeg"); + + CropDimension result = WebEnabledRenditionCropping.getCropDimensionForOriginal(asset, + new CropDimension(10, 15, 20, 30)); + + assertEquals(10, result.getLeft()); + assertEquals(15, result.getTop()); + assertEquals(20, result.getWidth()); + assertEquals(30, result.getHeight()); } } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java index 062cfb83..c64562ed 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaPathTest.java @@ -20,10 +20,14 @@ package io.wcm.handler.mediasource.dam.impl.dynamicmedia; import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.CROP_TYPE_SMART; import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_BANNER; import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_CROP_TYPE; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_HEIGHT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_WIDTH; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import org.apache.sling.api.resource.Resource; import org.junit.jupiter.api.BeforeEach; @@ -35,6 +39,7 @@ import com.day.cq.dam.scene7.api.constants.Scene7Constants; import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.MediaArgs; import io.wcm.handler.media.spi.MediaHandlerConfig; import io.wcm.handler.media.testcontext.AppAemContext; import io.wcm.handler.mediasource.dam.impl.DamContext; @@ -52,6 +57,7 @@ class DynamicMediaPathTest { private MediaHandlerConfig mediaHandlerConfig; private DynamicMediaSupportService dynamicMediaSupportService; private Resource assetFolder; + private Asset asset; @BeforeEach void setUp() { @@ -65,16 +71,33 @@ void setUp() { assetFolder = context.create().resource("/content/dam/folder1"); context.create().resource(assetFolder, JCR_CONTENT, DamConstants.IMAGE_PROFILE, profile1.getPath()); - Asset asset = context.create().asset(assetFolder.getPath() + "/test.jpg", 50, 30, ContentType.JPEG, + asset = context.create().asset(assetFolder.getPath() + "/test.jpg", 50, 30, ContentType.JPEG, Scene7Constants.PN_S7_FILE, "DummyFolder/test"); - damContext = new DamContext(asset, null, mediaHandlerConfig, + damContext = new DamContext(asset, new MediaArgs(), mediaHandlerConfig, dynamicMediaSupportService, context.request()); } @Test void testWidthHeight() { + String result = DynamicMediaPath.buildImage(damContext, 30, 25); + assertEquals("/is/image/DummyFolder/test?wid=30&hei=25&fit=stretch", result); + } + + @Test + void testWidthHeight_ImplicitSmartCrop() { String result = DynamicMediaPath.buildImage(damContext, 30, 20); - assertEquals("/is/image/DummyFolder/test?wid=30&hei=20&fit=stretch", result); + assertEquals("/is/image/DummyFolder/test%3ACrop-1?wid=30&hei=20&fit=stretch", result); + } + + @Test + void testWidthHeight_ImplicitSmartCrop_CroppingAreaTooSmall() { + String smartCropRenditionPath = asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + + "/Crop-1/" + JCR_CONTENT; + context.create().resource(smartCropRenditionPath, + PN_NORMALIZED_WIDTH, 0.5d, + PN_NORMALIZED_HEIGHT, 0.5666d); + + assertNull(DynamicMediaPath.buildImage(damContext, 30, 20)); } @Test @@ -141,7 +164,7 @@ void testBuildContent_Download() { void testBuildImage_SpecialChars() { Asset assetSpecialChars = context.create().asset(assetFolder.getPath() + "/test with spaces äöü߀.jpg", 50, 30, ContentType.JPEG, Scene7Constants.PN_S7_FILE, "DummyFolder/test with spaces äöü߀"); - damContext = new DamContext(assetSpecialChars, null, mediaHandlerConfig, + damContext = new DamContext(assetSpecialChars, new MediaArgs(), mediaHandlerConfig, dynamicMediaSupportService, context.request()); String result = DynamicMediaPath.buildContent(damContext, false); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImplTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImplTest.java index ce4767d8..6a57afd9 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImplTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/DynamicMediaSupportServiceImplTest.java @@ -20,7 +20,6 @@ package io.wcm.handler.mediasource.dam.impl.dynamicmedia; import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; -import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.DynamicMediaSupportServiceImpl.ASSETS_SCENE7_FEATURE_FLAG_PID; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -29,7 +28,6 @@ import java.util.stream.Stream; import org.apache.sling.api.resource.Resource; -import org.apache.sling.featureflags.impl.ConfiguredFeature; import org.apache.sling.testing.mock.caconfig.MockContextAwareConfig; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -65,54 +63,106 @@ void setUp() { Scene7Constants.PN_S7_FILE, "DummyFolder/dummy"); } - @Test - void testDefaultConfig_FeatureDisabled() { - DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl()); - assertFalse(underTest.isDynamicMediaEnabled()); - } - @Test void testDefaultConfig() { - activateDynamicMediaFeature(); DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl()); assertTrue(underTest.isDynamicMediaEnabled()); + assertTrue(underTest.isValidateSmartCropRenditionSizes()); assertEquals(new Dimension(2000, 2000), underTest.getImageSizeLimit()); - assertEquals("https://dummy.scene7.com", underTest.getDynamicMediaServerUrl(asset, null)); + assertEquals("https://dummy.scene7.com", underTest.getDynamicMediaServerUrl(asset, null, context.request())); + assertTrue(underTest.isDynamicMediaCapabilityEnabled(true)); + assertFalse(underTest.isDynamicMediaCapabilityEnabled(false)); } @Test void testConfigDisabled() { - activateDynamicMediaFeature(); DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), "enabled", false); assertFalse(underTest.isDynamicMediaEnabled()); } + @Test + void testConfigDMCapability_OFF() { + DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), + "dmCapabilityDetection", "OFF"); + assertFalse(underTest.isDynamicMediaCapabilityEnabled(true)); + assertFalse(underTest.isDynamicMediaCapabilityEnabled(false)); + } + + @Test + void testConfigDMCapability_ON() { + DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), + "dmCapabilityDetection", "ON"); + assertTrue(underTest.isDynamicMediaCapabilityEnabled(true)); + assertTrue(underTest.isDynamicMediaCapabilityEnabled(false)); + } + @Test void testAuthorPreviewMode() { - activateDynamicMediaFeature(); DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), "authorPreviewMode", true, + "validateSmartCropRenditionSizes", false, "imageSizeLimitWidth", 4000, "imageSizeLimitHeight", 3000); assertTrue(underTest.isDynamicMediaEnabled()); + assertFalse(underTest.isValidateSmartCropRenditionSizes()); assertEquals(new Dimension(4000, 3000), underTest.getImageSizeLimit()); - assertEquals("", underTest.getDynamicMediaServerUrl(asset, null)); + assertEquals("https://author.dummysite.org", underTest.getDynamicMediaServerUrl(asset, null, context.request())); } @Test void testAuthorPreviewMode_SiteConfig() { - activateDynamicMediaFeature(); + MockCAConfig.contextPathStrategyAbsoluteParent(context, 1); + MockContextAwareConfig.writeConfiguration(context, AppAemContext.ROOTPATH_CONTENT, SiteConfig.class, + "siteUrlAuthor", "https://author-dm"); + + DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), + "authorPreviewMode", true); + assertTrue(underTest.isDynamicMediaEnabled()); + assertEquals(new Dimension(2000, 2000), underTest.getImageSizeLimit()); + assertEquals("https://author-dm", underTest.getDynamicMediaServerUrl(asset, null, context.request())); + } + + /** + * Ensure Site URL auto-detection with <auto> placeholder works. + */ + @Test + void testAuthorPreviewMode_SiteConfig_AutoDetection() { + context.request().setServerName("servername-dm"); + context.request().setServerPort(8443); + context.request().setScheme("https"); MockCAConfig.contextPathStrategyAbsoluteParent(context, 1); - MockContextAwareConfig.writeConfiguration(context, "/content/dam", SiteConfig.class, - "siteUrlAuthor", "https://author"); + MockContextAwareConfig.writeConfiguration(context, AppAemContext.ROOTPATH_CONTENT, SiteConfig.class, + "siteUrlAuthor", "https://author-dm"); + + DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), + "authorPreviewMode", true); + assertTrue(underTest.isDynamicMediaEnabled()); + assertEquals(new Dimension(2000, 2000), underTest.getImageSizeLimit()); + assertEquals("https://servername-dm:8443", underTest.getDynamicMediaServerUrl(asset, null, context.request())); + } + + /** + * Ensure Site URL auto-detection with <auto> placeholder works with using the fallback URL + * when media handler is used outside request context (adapted from resource). + */ + @Test + @SuppressWarnings("null") + void testAuthorPreviewMode_SiteConfig_AutoDetection_Fallback() { + context.request().setServerName("servername-dm"); + context.request().setServerPort(8443); + context.request().setScheme("https"); + + MockCAConfig.contextPathStrategyAbsoluteParent(context, 1); + MockContextAwareConfig.writeConfiguration(context, AppAemContext.ROOTPATH_CONTENT, SiteConfig.class, + "siteUrlAuthor", "https://author-dm-fallback"); DynamicMediaSupportService underTest = context.registerInjectActivateService(new DynamicMediaSupportServiceImpl(), "authorPreviewMode", true); assertTrue(underTest.isDynamicMediaEnabled()); assertEquals(new Dimension(2000, 2000), underTest.getImageSizeLimit()); - assertEquals("https://author", underTest.getDynamicMediaServerUrl(asset, null)); + assertEquals("https://author-dm-fallback", underTest.getDynamicMediaServerUrl(asset, null, context.currentResource())); } /** @@ -169,9 +219,7 @@ void testGetImageProfileForAsset_MultipleFolders_IncompleteFolders() { @ParameterizedTest @MethodSource("forcePublicshUrlModes") - void testAuthorPreviewMode_SiteConfig_FourcePublish(UrlMode urlMode) { - activateDynamicMediaFeature(); - + void testAuthorPreviewMode_SiteConfig_ForcePublish(UrlMode urlMode) { MockCAConfig.contextPathStrategyAbsoluteParent(context, 1); MockContextAwareConfig.writeConfiguration(context, "/content/dam", SiteConfig.class, "siteUrlAuthor", "https://author"); @@ -180,7 +228,7 @@ void testAuthorPreviewMode_SiteConfig_FourcePublish(UrlMode urlMode) { "authorPreviewMode", true); assertTrue(underTest.isDynamicMediaEnabled()); assertEquals(new Dimension(2000, 2000), underTest.getImageSizeLimit()); - assertEquals("https://dummy.scene7.com", underTest.getDynamicMediaServerUrl(asset, urlMode)); + assertEquals("https://dummy.scene7.com", underTest.getDynamicMediaServerUrl(asset, urlMode, context.request())); } private static Stream forcePublicshUrlModes() { @@ -191,10 +239,4 @@ private static Stream forcePublicshUrlModes() { Arguments.of(UrlModes.FULL_URL_PUBLISH_PROTOCOLRELATIVE)); } - private void activateDynamicMediaFeature() { - context.registerInjectActivateService(new ConfiguredFeature(), - "name", ASSETS_SCENE7_FEATURE_FLAG_PID, - "enabled", true); - } - } diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImplTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImplTest.java index 4eb58d57..d27b76d5 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImplTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/ImageProfileImplTest.java @@ -27,6 +27,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import java.util.List; + import org.apache.sling.api.resource.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,7 +37,6 @@ import com.day.cq.dam.api.Asset; import com.day.cq.dam.api.DamConstants; import com.day.cq.dam.scene7.api.constants.Scene7Constants; -import com.google.common.collect.ImmutableList; import io.wcm.handler.media.testcontext.AppAemContext; import io.wcm.testing.mock.aem.junit5.AemContext; @@ -43,7 +44,6 @@ import io.wcm.wcm.commons.contenttype.ContentType; @ExtendWith(AemContextExtension.class) -@SuppressWarnings("null") class ImageProfileImplTest { private final AemContext context = AppAemContext.newAemContext(); @@ -69,7 +69,7 @@ void testValidProfileWithCroppingPresets() { ImageProfile underTest = dynamicMediaSupportService.getImageProfileForAsset(asset1); assertNotNull(underTest); - assertEquals(ImmutableList.of(new NamedDimension("Crop-1", 100, 60), new NamedDimension("Crop-2", 50, 30)), + assertEquals(List.of(new NamedDimension("Crop-1", 100, 60), new NamedDimension("Crop-2", 50, 30)), underTest.getSmartCropDefinitions()); } @@ -90,7 +90,7 @@ void testValidProfileWithCroppingPresetsDefinedInParentFolder() { ImageProfile underTest = dynamicMediaSupportService.getImageProfileForAsset(asset1); assertNotNull(underTest); - assertEquals(ImmutableList.of(new NamedDimension("Crop-1", 100, 60), new NamedDimension("Crop-2", 50, 30)), + assertEquals(List.of(new NamedDimension("Crop-1", 100, 60), new NamedDimension("Crop-2", 50, 30)), underTest.getSmartCropDefinitions()); } @@ -108,7 +108,7 @@ void testValidProfileWithInvalidCroppingPresets() { ImageProfile underTest = dynamicMediaSupportService.getImageProfileForAsset(asset1); assertNotNull(underTest); - assertEquals(ImmutableList.of(new NamedDimension("Crop-4", 10, 20)), underTest.getSmartCropDefinitions()); + assertEquals(List.of(new NamedDimension("Crop-4", 10, 20)), underTest.getSmartCropDefinitions()); } @Test diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java new file mode 100644 index 00000000..d8669bbb --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/dynamicmedia/SmartCropTest.java @@ -0,0 +1,229 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.dynamicmedia; + +import static com.day.cq.commons.jcr.JcrConstants.JCR_CONTENT; +import static com.day.cq.dam.api.DamConstants.RENDITIONS_FOLDER; +import static io.wcm.handler.media.testcontext.AppAemContext.DAM_PATH; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.CROP_TYPE_SMART; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_BANNER; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.ImageProfileImpl.PN_CROP_TYPE; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_LEFT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_HEIGHT; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_NORMALIZED_WIDTH; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.PN_TOP; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.canApply; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.getCropDimensionForAsset; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.getDimensionForRatio; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.getDimensionForWidthHeight; +import static io.wcm.handler.mediasource.dam.impl.dynamicmedia.SmartCrop.isMatchingSize; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.day.cq.dam.api.Asset; + +import io.wcm.handler.media.CropDimension; +import io.wcm.handler.media.format.Ratio; +import io.wcm.handler.media.testcontext.AppAemContext; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; +import io.wcm.wcm.commons.contenttype.ContentType; + +@ExtendWith(AemContextExtension.class) +class SmartCropTest { + + private final AemContext context = AppAemContext.newAemContext(); + + private Asset asset; + private NamedDimension dimension16_10; + + @BeforeEach + void setUp() { + asset = context.create().asset(DAM_PATH + "/image1.jpg", 160, 100, ContentType.JPEG); + dimension16_10 = new NamedDimension("16-10", 16, 10); + } + + @Test + void testCanApply() { + assertTrue(canApply(null, null)); + assertTrue(canApply(new CropDimension(0, 0, 10, 10, true), null)); + + assertFalse(canApply(null, 90)); + assertFalse(canApply(new CropDimension(0, 0, 10, 10, true), 90)); + assertFalse(canApply(new CropDimension(0, 0, 10, 10, false), null)); + } + + @Test + void testGetDimension() { + ImageProfile profile1 = new ImageProfileImpl( + context.create().resource("/conf/global/settings/dam/adminui-extension/imageprofile/profile1", + PN_CROP_TYPE, CROP_TYPE_SMART, + PN_BANNER, "16-10,160,100|4-3,40,30")); + + assertNull(getDimensionForRatio(profile1, 1d)); + assertNull(getDimensionForWidthHeight(profile1, 500, 500)); + + assertNamedDimension(getDimensionForRatio(profile1, Ratio.get(16, 10)), "16-10", 160, 100); + assertNamedDimension(getDimensionForWidthHeight(profile1, 320, 200), "16-10", 320, 200); + + assertNamedDimension(getDimensionForRatio(profile1, Ratio.get(4, 3)), "4-3", 40, 30); + assertNamedDimension(getDimensionForWidthHeight(profile1, 400, 300), "4-3", 400, 300); + } + + @Test + void testGetDimension_NoProfile() { + assertNull(getDimensionForRatio(null, 1d)); + assertNull(getDimensionForWidthHeight(null, 100, 100)); + } + + @Test + void testGetCropDimensionForAsset_NoRenditionResource() { + assertNull(getCropDimensionForAsset(asset, context.resourceResolver(), dimension16_10)); + } + + @Test + void testGetCropDimensionForAsset() { + prepareSmartCropRendition(0.1, 0.2, 0.5, 0.5); // results in 80x50 cropping area + CropDimension cropDimension = getCropDimensionForAsset(asset, context.resourceResolver(), dimension16_10); + + assertNotNull(cropDimension); + assertEquals(16, cropDimension.getLeft()); + assertEquals(20, cropDimension.getTop()); + assertEquals(80, cropDimension.getWidth()); + assertEquals(50, cropDimension.getHeight()); + } + + @Test + void testGetCropDimensionForAsset_WidthDeviation() { + prepareSmartCropRendition(0.1, 0.2, 0.75, 0.5); // results in 120x50 cropping area, treated as 80x50 + CropDimension cropDimension = getCropDimensionForAsset(asset, context.resourceResolver(), dimension16_10); + + assertNotNull(cropDimension); + assertEquals(16, cropDimension.getLeft()); + assertEquals(20, cropDimension.getTop()); + assertEquals(80, cropDimension.getWidth()); + assertEquals(50, cropDimension.getHeight()); + } + + @Test + void testGetCropDimensionForAsset_HeightDeviation() { + prepareSmartCropRendition(0.1, 0.2, 0.5, 0.75); // results in 80x75 cropping area, treated as 80x50 + CropDimension cropDimension = getCropDimensionForAsset(asset, context.resourceResolver(), dimension16_10); + + assertNotNull(cropDimension); + assertEquals(16, cropDimension.getLeft()); + assertEquals(20, cropDimension.getTop()); + assertEquals(80, cropDimension.getWidth()); + assertEquals(50, cropDimension.getHeight()); + } + + @Test + void testIsMatchingSize_NoRenditionResource() { + // assume everything is ok if no "16-10" rendition exists (we have no other chance) + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 80, 50)); + } + + @Test + void testIsMatchingSize_MatchesExact() { + prepareSmartCropRendition(0, 0, 0.5, 0.5); // results in 80x50 cropping area + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 80, 50)); + } + + @Test + void testIsMatchingSize_MatchesSmaller() { + prepareSmartCropRendition(0, 0, 0.5, 0.5); // results in 80x50 cropping area + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 40, 25)); + } + + @Test + void testIsMatchingSize_TooSmall() { + prepareSmartCropRendition(0, 0, 0.5, 0.5); // results in 80x50 cropping area + assertFalse(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 120, 75)); + } + + @Test + void testIsMatchingSize_MatchesExact_WidthDeviation() { + prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 120x50 cropping area, treated as 80x50 + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 80, 50)); + } + + @Test + void testIsMatchingSize_MatchesSmaller_WidthDeviation() { + prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 120x50 cropping area, treated as 80x50 + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 40, 25)); + } + + @Test + void testIsMatchingSize_TooSmall_WidthDeviation() { + prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 120x50 cropping area, treated as 80x50 + assertFalse(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 120, 75)); + } + + @Test + void testIsMatchingSize_MatchesExact_HeightDeviation() { + prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 80x75 cropping area, treated as 80x50 + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 80, 50)); + } + + @Test + void testIsMatchingSize_MatchesSmaller_HeightDeviation() { + prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 80x75 cropping area, treated as 80x50 + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 40, 25)); + } + + @Test + void testIsMatchingSize_TooSmall_HeightDeviation() { + prepareSmartCropRendition(0, 0, 0.75, 0.5); // results in 80x75 cropping area, treated as 80x50 + assertFalse(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 120, 75)); + } + + @Test + void testIsMatchingSize_InvalidNormalizedWidthHeight() { + prepareSmartCropRendition(0, 0, 0, 0); + // assume true because no valid normalized width/height provided - we do not know the cropping area + assertTrue(isMatchingSize(asset, context.resourceResolver(), dimension16_10, 80, 50)); + } + + private static void assertNamedDimension(NamedDimension namedDimension, + String expectedName, long expectedWith, long expectedHeight) { + assertNotNull(namedDimension); + assertEquals(expectedName, namedDimension.getName()); + assertEquals(expectedWith, namedDimension.getWidth()); + assertEquals(expectedHeight, namedDimension.getHeight()); + } + + private void prepareSmartCropRendition(double left, double top, double normalizedWidth, double normalizedHeight) { + String smartCropRenditionPath = asset.getPath() + "/" + JCR_CONTENT + "/" + RENDITIONS_FOLDER + + "/" + dimension16_10.getName() + "/" + JCR_CONTENT; + context.create().resource(smartCropRenditionPath, + PN_LEFT, left, + PN_TOP, top, + PN_NORMALIZED_WIDTH, normalizedWidth, + PN_NORMALIZED_HEIGHT, normalizedHeight); + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java index 1a130ee5..8971ec45 100644 --- a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/WorkflowProcessUtilTest.java @@ -44,7 +44,6 @@ import com.adobe.granite.workflow.exec.WorkflowData; import com.day.cq.dam.api.Asset; import com.day.cq.wcm.api.Page; -import com.google.common.collect.ImmutableList; import io.wcm.handler.media.testcontext.AppAemContext; import io.wcm.testing.mock.aem.junit5.AemContext; @@ -133,7 +132,7 @@ void testGetPayloadResourcePaths_AnyOtherResource() { @SuppressWarnings("null") private void assertPaths(String payloadPaths, String primaryTypeResourceType, String... expectedPaths) { - List expected = ImmutableList.copyOf(expectedPaths); + List expected = List.of(expectedPaths); List result = WorkflowProcessUtil.getPayloadResourcePaths(payloadPaths, primaryTypeResourceType, context.resourceResolver(), context.getService(ResourceCollectionManager.class)); assertEquals(expected, result); diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/NamedThreadFactoryTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/NamedThreadFactoryTest.java new file mode 100644 index 00000000..eb83e1cc --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/NamedThreadFactoryTest.java @@ -0,0 +1,45 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2023 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.metadata.concurrency; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.concurrent.ThreadFactory; + +import org.junit.jupiter.api.Test; + +class NamedThreadFactoryTest { + + private static final Runnable NOOP = () -> { + // do nothing + }; + + @Test + void testNewThread() { + ThreadFactory underTest = new NamedThreadFactory("mythread"); + + Thread thread1 = underTest.newThread(NOOP); + assertEquals("mythread-0", thread1.getName()); + + Thread thread2 = underTest.newThread(NOOP); + assertEquals("mythread-1", thread2.getName()); + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripeIndexTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripeIndexTest.java new file mode 100644 index 00000000..71f22faa --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripeIndexTest.java @@ -0,0 +1,64 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2023 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.metadata.concurrency; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.commons.lang3.Range; +import org.junit.jupiter.api.Test; + +class StripeIndexTest { + + @Test + void testSize() { + assertTrue(new StripeIndex(100).size() >= 100); + assertEquals(256, new StripeIndex(256).size()); + } + + @Test + void testInvalidSize() { + assertThrows(IllegalArgumentException.class, () -> new StripeIndex(0)); + assertThrows(IllegalArgumentException.class, () -> new StripeIndex(-1)); + } + + @Test + void testIndexFor() { + final int STRIPES = 256; + assertStripeIndexes(STRIPES, Range.between(0, STRIPES - 1)); + } + + @Test + void testIndexFor_MaxSize() { + final int STRIPES = Integer.MAX_VALUE; + assertStripeIndexes(STRIPES, Range.between(0, STRIPES)); + } + + private static void assertStripeIndexes(int stripes, Range validIndexes) { + StripeIndex underTest = new StripeIndex(stripes); + for (int i = 0; i < 10000; i++) { + Object key = new Object(); + int index = underTest.indexFor(key); + assertTrue(validIndexes.contains(index), "Index " + index + " for key " + key + " within range " + validIndexes); + } + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripedLazyWeakLockTest.java b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripedLazyWeakLockTest.java new file mode 100644 index 00000000..d83cb095 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/dam/impl/metadata/concurrency/StripedLazyWeakLockTest.java @@ -0,0 +1,37 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2023 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.metadata.concurrency; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.concurrent.locks.Lock; + +import org.junit.jupiter.api.Test; + +class StripedLazyWeakLockTest { + + @Test + void testGet() { + StripedLazyWeakLock underTest = new StripedLazyWeakLock(100); + Lock lock = underTest.get("any-key"); + assertNotNull(lock); + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java b/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java index 7165c36c..27aa8b68 100644 --- a/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java +++ b/src/test/java/io/wcm/handler/mediasource/inline/InlineMediaSourceTest.java @@ -24,15 +24,14 @@ import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_CROP; import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_IS_DECORATIVE; import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_ROTATION; -import static io.wcm.handler.media.MediaNameConstants.PROP_BREAKPOINT; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_1COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_2COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_3COL; import static io.wcm.handler.media.testcontext.DummyMediaFormats.EDITORIAL_STANDARD; import static io.wcm.handler.media.testcontext.DummyMediaFormats.PRODUCT_BANNER; import static io.wcm.handler.media.testcontext.DummyMediaFormats.PRODUCT_CUTOUT_13PLUS; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO; -import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO2; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS_SCALE1; import static io.wcm.handler.media.testcontext.DummyMediaFormats.SHOWROOM_CONTROLS_SCALE1_ONLYHEIGHT; @@ -61,7 +60,6 @@ import com.day.cq.wcm.api.Page; import com.day.image.Layer; -import com.google.common.collect.ImmutableList; import io.wcm.handler.media.Asset; import io.wcm.handler.media.Media; @@ -300,7 +298,6 @@ void testMediaInlineWithFile() { assertEquals(0, rendition.getHeight(), "rendition.height"); } - @SuppressWarnings("deprecation") @Test void testMediaInlineSampleImage() { MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); @@ -331,7 +328,6 @@ void testMediaInlineSampleImage() { assertEquals(215d / 102d, rendition.getRatio(), 0.0001, "rendition.ratio"); assertNotNull(media.getAsset().getImageRendition(new MediaArgs())); - assertNull(media.getAsset().getFlashRendition(new MediaArgs())); assertNull(media.getAsset().getDownloadRendition(new MediaArgs())); Layer layer = AdaptTo.notNull(rendition, Layer.class); @@ -757,13 +753,13 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal() { ModifiableValueMap props = mediaInlineSampleImageResource_16_10.adaptTo(ModifiableValueMap.class); props.put(PN_MEDIA_CROP, "0,0,320,152"); - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO); + MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO_16_10); MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); Media media = mediaHandler.get(mediaInlineSampleImageResource_16_10).args(mediaArgs).build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals( "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.84.40.0,0,320,152.file/sample_image_400x250.jpg", @@ -772,7 +768,7 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal() { assertEquals("/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline./sample_image_400x250.jpg", renditions.get(1).getUrl(), "rendition.mediaUrl.2"); - assertEquals(RATIO, renditions.get(1).getMediaFormat()); + assertEquals(RATIO_16_10, renditions.get(1).getMediaFormat()); } @Test @@ -781,14 +777,14 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() ModifiableValueMap props = mediaInlineSampleImageResource_16_10.adaptTo(ModifiableValueMap.class); props.put(PN_MEDIA_CROP, "0,0,320,152"); - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO, RATIO2, EDITORIAL_1COL) + MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(SHOWROOM_CONTROLS, RATIO_16_10, RATIO_4_3, EDITORIAL_1COL) .autoCrop(true); MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); Media media = mediaHandler.get(mediaInlineSampleImageResource_16_10).args(mediaArgs).build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(4, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals( "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.84.40.0,0,320,152.file/sample_image_400x250.jpg", @@ -798,7 +794,7 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() assertEquals( "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.333.250.34,0,367,250.file/sample_image_400x250.jpg", renditions.get(1).getUrl(), "rendition.mediaUrl.1"); - assertEquals(RATIO2, renditions.get(1).getMediaFormat()); + assertEquals(RATIO_4_3, renditions.get(1).getMediaFormat()); assertEquals( "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.215.102.0,0,320,152.file/sample_image_400x250.jpg", @@ -807,7 +803,7 @@ void testMultipleMandatoryMediaFormatsWithCropping_AlsoMatchOriginal_AutoCrop() assertEquals("/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline./sample_image_400x250.jpg", renditions.get(3).getUrl(), "rendition.mediaUrl.2"); - assertEquals(RATIO, renditions.get(3).getMediaFormat()); + assertEquals(RATIO_16_10, renditions.get(3).getMediaFormat()); } @Test @@ -816,13 +812,13 @@ void testMultipleMediaFormatsWithCropping_PreferCroppingOverFallback() { ModifiableValueMap props = mediaInlineSampleImageResource_16_10.adaptTo(ModifiableValueMap.class); props.put(PN_MEDIA_CROP, "0,0,320,152"); - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(RATIO, SHOWROOM_CONTROLS); + MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(RATIO_16_10, SHOWROOM_CONTROLS); MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); Media media = mediaHandler.get(mediaInlineSampleImageResource_16_10).args(mediaArgs).build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals( "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.84.40.0,0,320,152.file/sample_image_400x250.jpg", @@ -831,7 +827,7 @@ void testMultipleMediaFormatsWithCropping_PreferCroppingOverFallback() { assertEquals("/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline./sample_image_400x250.jpg", renditions.get(1).getUrl(), "rendition.mediaUrl.2"); - assertEquals(RATIO, renditions.get(1).getMediaFormat()); + assertEquals(RATIO_16_10, renditions.get(1).getMediaFormat()); } @Test @@ -871,7 +867,7 @@ void testMultipleMandatoryMediaFormats() { assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage/mediaInline./sample_image_215x102.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -895,7 +891,7 @@ void testMultipleMandatoryMediaFormatsNotAllMatch() { assertEquals(MediaInvalidReason.NOT_ENOUGH_MATCHING_RENDITIONS, media.getMediaInvalidReason()); assertNotNull(media.getAsset(), "asset?"); assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); assertEquals("/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage/mediaInline.image_file.205.97.file/sample_image_215x102.jpg", renditions.get(0).getUrl(), "rendition.mediaUrl.1"); @@ -906,63 +902,19 @@ void testMultipleMandatoryMediaFormatsNotAllMatch() { assertEquals(SHOWROOM_CONTROLS_SCALE1, renditions.get(1).getMediaFormat()); } - @Test - @SuppressWarnings("deprecation") - void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats() { - MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); - MediaArgs mediaArgs = new MediaArgs().mandatoryMediaFormats(new io.wcm.handler.media.format.ResponsiveMediaFormatsBuilder(RATIO) - .breakpoint("B1", 160, 100) - .breakpoint("B2", 320, 200) - .build()); - - Media media = mediaHandler.get(mediaInlineSampleImageResource_16_10, mediaArgs).build(); - assertTrue(media.isValid(), "valid?"); - assertNotNull(media.getAsset(), "asset?"); - assertEquals(2, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); - - Rendition rendition0 = renditions.get(0); - assertEquals( - "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.160.100.file/sample_image_400x250.jpg", - rendition0.getUrl(), "rendition.mediaUrl.1"); - assertEquals(160, rendition0.getWidth()); - assertEquals(100, rendition0.getHeight()); - - MediaFormat mediaFormat0 = renditions.get(0).getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat0.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat0.getRatio(), 0.001d); - assertEquals(160, mediaFormat0.getWidth()); - assertEquals(100, mediaFormat0.getHeight()); - assertEquals("B1", mediaFormat0.getProperties().get(PROP_BREAKPOINT)); - - Rendition rendition1 = renditions.get(1); - assertEquals( - "/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline.image_file.320.200.file/sample_image_400x250.jpg", - rendition1.getUrl(), "rendition.mediaUrl.2"); - assertEquals(320, rendition1.getWidth()); - assertEquals(200, rendition1.getHeight()); - - MediaFormat mediaFormat1 = renditions.get(1).getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat1.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat1.getRatio(), 0.001d); - assertEquals(320, mediaFormat1.getWidth()); - assertEquals(200, mediaFormat1.getHeight()); - assertEquals("B2", mediaFormat1.getProperties().get(PROP_BREAKPOINT)); - } - @Test void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { MediaHandler mediaHandler = AdaptTo.notNull(adaptable(), MediaHandler.class); Media media = mediaHandler.get(mediaInlineSampleImageResource_16_10) - .mediaFormat(RATIO) - .pictureSource(new PictureSource(RATIO).media("media1").widths(160)) - .pictureSource(new PictureSource(RATIO).media("media2").widths(320)) + .mediaFormat(RATIO_16_10) + .pictureSource(new PictureSource(RATIO_16_10).media("media1").widths(160)) + .pictureSource(new PictureSource(RATIO_16_10).media("media2").widths(320)) .build(); assertTrue(media.isValid(), "valid?"); assertNotNull(media.getAsset(), "asset?"); assertEquals(3, media.getRenditions().size(), "renditions"); - List renditions = ImmutableList.copyOf(media.getRenditions()); + List renditions = List.copyOf(media.getRenditions()); Rendition rendition0 = renditions.get(0); assertEquals("/content/unittest/de_test/brand/de/_jcr_content/resourceMediaInlineSampleImage16_10/mediaInline./sample_image_400x250.jpg", @@ -972,7 +924,7 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { assertEquals(160d / 100d, rendition0.getRatio(), 0.0001); MediaFormat mediaFormat0 = rendition0.getMediaFormat(); - assertEquals(RATIO.getName(), mediaFormat0.getName()); + assertEquals(RATIO_16_10.getName(), mediaFormat0.getName()); Rendition rendition1 = renditions.get(1); assertEquals( @@ -983,8 +935,8 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { assertEquals(160d / 100d, rendition1.getRatio(), 0.0001); MediaFormat mediaFormat1 = rendition1.getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat1.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat1.getRatio(), 0.001d); + assertEquals(RATIO_16_10.getLabel(), mediaFormat1.getLabel()); + assertEquals(RATIO_16_10.getRatio(), mediaFormat1.getRatio(), 0.001d); assertEquals(160, mediaFormat1.getWidth()); Rendition rendition2 = renditions.get(2); @@ -995,8 +947,8 @@ void testMultipleMandatoryMediaFormats_OnThyFlyMediaFormats_PictureSources() { assertEquals(200, rendition2.getHeight()); MediaFormat mediaFormat2 = rendition2.getMediaFormat(); - assertEquals(RATIO.getLabel(), mediaFormat2.getLabel()); - assertEquals(RATIO.getRatio(), mediaFormat2.getRatio(), 0.001d); + assertEquals(RATIO_16_10.getLabel(), mediaFormat2.getLabel()); + assertEquals(RATIO_16_10.getRatio(), mediaFormat2.getRatio(), 0.001d); assertEquals(320, mediaFormat2.getWidth()); } diff --git a/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateRenditionTest.java b/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateRenditionTest.java new file mode 100644 index 00000000..44249da9 --- /dev/null +++ b/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateRenditionTest.java @@ -0,0 +1,163 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2022 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.inline; + +import static io.wcm.handler.media.MediaNameConstants.NN_MEDIA_INLINE; +import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_CROP; +import static io.wcm.handler.media.MediaNameConstants.PN_MEDIA_ROTATION; +import static io.wcm.handler.media.UriTemplateType.CROP_CENTER; +import static io.wcm.handler.media.UriTemplateType.SCALE_HEIGHT; +import static io.wcm.handler.media.UriTemplateType.SCALE_WIDTH; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_16_10; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_4_3; +import static io.wcm.handler.media.testcontext.DummyMediaFormats.RATIO_SQUARE; +import static io.wcm.handler.media.testcontext.UriTemplateAssert.assertUriTemplate; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; + +import org.apache.sling.api.resource.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import io.wcm.handler.media.Media; +import io.wcm.handler.media.MediaArgs.PictureSource; +import io.wcm.handler.media.MediaHandler; +import io.wcm.handler.media.Rendition; +import io.wcm.handler.media.testcontext.MediaSourceInlineAppAemContext; +import io.wcm.sling.commons.adapter.AdaptTo; +import io.wcm.testing.mock.aem.junit5.AemContext; +import io.wcm.testing.mock.aem.junit5.AemContextExtension; +import io.wcm.wcm.commons.contenttype.ContentType; + +/** + * Test Inline URI template for renditions. + */ +@ExtendWith(AemContextExtension.class) +class InlineUriTemplateRenditionTest { + + final AemContext context = MediaSourceInlineAppAemContext.newAemContext(); + + private MediaHandler mediaHandler; + private Resource inlineImage; + + @BeforeEach + @SuppressWarnings("null") + void setUp() { + mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); + + // prepare inline media object with real image binary data to test scaling + inlineImage = context.create().resource(context.currentPage(), "inlineImage", + NN_MEDIA_INLINE + "Name", "sample.jpg"); + context.load().binaryResource("/sample_image_400x250.jpg", + inlineImage.getPath() + "/" + NN_MEDIA_INLINE, ContentType.JPEG); + } + + @Test + void testOriginal_CropCenter() { + Media media = mediaHandler.get(inlineImage) + .mediaFormat(RATIO_16_10) + .build(); + Rendition rendition = media.getRendition(); + + // CROP_CENTER not supported for renditions + assertThrows(IllegalArgumentException.class, () -> { + rendition.getUriTemplate(CROP_CENTER); + }); + } + + @Test + void testOriginal() { + Media media = mediaHandler.get(inlineImage) + .mediaFormat(RATIO_16_10) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 400, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 400, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.file/sample.jpg"); + } + + @Test + void test4_3() { + Media media = mediaHandler.get(inlineImage) + .mediaFormat(RATIO_4_3) + .autoCrop(true) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 333, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.34,0,367,250.file/sample.jpg"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 333, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.34,0,367,250.file/sample.jpg"); + } + + @Test + void testMultiple() { + Media media = mediaHandler.get(inlineImage) + .pictureSource(new PictureSource(RATIO_16_10).widths(120, 96)) + .pictureSource(new PictureSource(RATIO_SQUARE).widths(100)) + .pictureSource(new PictureSource(RATIO_4_3).widths(144)) + .autoCrop(true) + .build(); + + List renditions = List.copyOf(media.getRenditions()); + assertEquals(4, renditions.size()); + + assertUriTemplate(renditions.get(0), SCALE_WIDTH, 400, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(renditions.get(1), SCALE_WIDTH, 400, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(renditions.get(2), SCALE_WIDTH, 250, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.75,0,325,250.file/sample.jpg"); + assertUriTemplate(renditions.get(3), SCALE_WIDTH, 333, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.34,0,367,250.file/sample.jpg"); + + assertUriTemplate(renditions.get(0), SCALE_HEIGHT, 400, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.file/sample.jpg"); + assertUriTemplate(renditions.get(1), SCALE_HEIGHT, 400, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.file/sample.jpg"); + assertUriTemplate(renditions.get(2), SCALE_HEIGHT, 250, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.75,0,325,250.file/sample.jpg"); + assertUriTemplate(renditions.get(3), SCALE_HEIGHT, 333, 250, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.34,0,367,250.file/sample.jpg"); + } + + @Test + @SuppressWarnings("null") + void testManualCroppingRotation() { + inlineImage = context.create().resource(context.currentPage(), "inlineImageCropping", + NN_MEDIA_INLINE + "Name", "sample.jpg", + PN_MEDIA_CROP, "5,5,80,55", + PN_MEDIA_ROTATION, 90); + context.load().binaryResource("/sample_image_400x250.jpg", + inlineImage.getPath() + "/" + NN_MEDIA_INLINE, ContentType.JPEG); + + Media media = mediaHandler.get(inlineImage) + .build(); + + assertUriTemplate(media.getRendition(), SCALE_WIDTH, 50, 75, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImageCropping/mediaInline.image_file.{width}.0.5,5,80,55.90.file/sample.jpg"); + assertUriTemplate(media.getRendition(), SCALE_HEIGHT, 50, 75, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImageCropping/mediaInline.image_file.0.{height}.5,5,80,55.90.file/sample.jpg"); + } + +} diff --git a/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateTest.java b/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateTest.java index 2add62e0..f26a131d 100644 --- a/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateTest.java +++ b/src/test/java/io/wcm/handler/mediasource/inline/InlineUriTemplateTest.java @@ -2,7 +2,7 @@ * #%L * wcm.io * %% - * Copyright (C) 2014 wcm.io + * Copyright (C) 2021 wcm.io * %% * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,32 +20,27 @@ package io.wcm.handler.mediasource.inline; import static io.wcm.handler.media.MediaNameConstants.NN_MEDIA_INLINE; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static io.wcm.handler.media.UriTemplateType.CROP_CENTER; +import static io.wcm.handler.media.UriTemplateType.SCALE_HEIGHT; +import static io.wcm.handler.media.UriTemplateType.SCALE_WIDTH; +import static io.wcm.handler.media.testcontext.UriTemplateAssert.assertUriTemplate; -import org.apache.commons.io.FilenameUtils; -import org.apache.sling.api.resource.PersistenceException; import org.apache.sling.api.resource.Resource; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import com.day.cq.dam.api.Asset; -import com.day.cq.dam.scene7.api.constants.Scene7Constants; - import io.wcm.handler.media.Media; import io.wcm.handler.media.MediaHandler; -import io.wcm.handler.media.UriTemplate; -import io.wcm.handler.media.UriTemplateType; import io.wcm.handler.media.testcontext.MediaSourceInlineAppAemContext; import io.wcm.sling.commons.adapter.AdaptTo; -import io.wcm.sling.commons.resource.ImmutableValueMap; import io.wcm.testing.mock.aem.junit5.AemContext; import io.wcm.testing.mock.aem.junit5.AemContextExtension; import io.wcm.wcm.commons.contenttype.ContentType; import io.wcm.wcm.commons.contenttype.FileExtension; /** - * Test Inline URI template + * Test Inline URI template for assets. */ @ExtendWith(AemContextExtension.class) class InlineUriTemplateTest { @@ -57,76 +52,40 @@ class InlineUriTemplateTest { @BeforeEach @SuppressWarnings("null") - void setUp() throws PersistenceException { + void setUp() { mediaHandler = AdaptTo.notNull(context.request(), MediaHandler.class); // prepare inline media object with real image binary data to test scaling - inlineImage = context.resourceResolver().create(context.currentPage().getContentResource(), "inlineImage", - ImmutableValueMap.builder() - .put(NN_MEDIA_INLINE + "Name", "sample_image_215x102.jpg") - .build()); + inlineImage = context.create().resource(context.currentPage(), "inlineImage", + NN_MEDIA_INLINE + "Name", "sample.jpg"); context.load().binaryResource("/sample_image_215x102.jpg", inlineImage.getPath() + "/" + NN_MEDIA_INLINE, ContentType.JPEG); } @Test - void testGetUriTemplate_CropCenter() { + void testGetUriTemplate() { Media media = mediaHandler.get(inlineImage).build(); - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.CROP_CENTER); - assertEquals( - "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.{height}.file/sample_image_215x102.jpg", - uriTemplate.getUriTemplate()); - assertEquals(215, uriTemplate.getMaxWidth()); - assertEquals(102, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.CROP_CENTER, uriTemplate.getType()); + assertUriTemplate(media, CROP_CENTER, 215, 102, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.{height}.file/sample.jpg"); + assertUriTemplate(media, SCALE_WIDTH, 215, 102, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.file/sample.jpg"); + assertUriTemplate(media, SCALE_HEIGHT, 215, 102, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.file/sample.jpg"); } @Test - void testGetUriTemplate_CropCenter_EnforceOutputFileExtension() { + void testGetUriTemplate_EnforceOutputFileExtension() { Media media = mediaHandler.get(inlineImage) .enforceOutputFileExtension(FileExtension.PNG) .build(); - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.CROP_CENTER); - assertEquals( - "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.{height}.file/sample_image_215x102.png", - uriTemplate.getUriTemplate()); - assertEquals(215, uriTemplate.getMaxWidth()); - assertEquals(102, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.CROP_CENTER, uriTemplate.getType()); - } - - @Test - void testGetUriTemplate_ScaleWidth() { - Media media = mediaHandler.get(inlineImage).build(); - - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.SCALE_WIDTH); - assertEquals( - "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.file/sample_image_215x102.jpg", - uriTemplate.getUriTemplate()); - assertEquals(215, uriTemplate.getMaxWidth()); - assertEquals(102, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.SCALE_WIDTH, uriTemplate.getType()); - } - - @Test - void testGetUriTemplate_ScaleHeight() { - Media media = mediaHandler.get(inlineImage).build(); - - UriTemplate uriTemplate = media.getAsset().getUriTemplate(UriTemplateType.SCALE_HEIGHT); - assertEquals( - "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.file/sample_image_215x102.jpg", - uriTemplate.getUriTemplate()); - assertEquals(215, uriTemplate.getMaxWidth()); - assertEquals(102, uriTemplate.getMaxHeight()); - assertEquals(UriTemplateType.SCALE_HEIGHT, uriTemplate.getType()); - } - - Asset createSampleAsset(String classpathResource, String contentType) { - String fileName = FilenameUtils.getName(classpathResource); - return context.create().asset("/content/dam/" + fileName, classpathResource, contentType, - Scene7Constants.PN_S7_FILE, "DummyFolder/" + fileName); + assertUriTemplate(media, CROP_CENTER, 215, 102, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.{height}.file/sample.png"); + assertUriTemplate(media, SCALE_WIDTH, 215, 102, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.{width}.0.file/sample.png"); + assertUriTemplate(media, SCALE_HEIGHT, 215, 102, + "/content/unittest/de_test/brand/de/_jcr_content/inlineImage/mediaInline.image_file.0.{height}.file/sample.png"); } }