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.wcmio.wcm.parent_toplevel
- 2.1.0
+ 2.3.0-SNAPSHOTio.wcmio.wcm.handler.media
- 1.14.5-SNAPSHOT
+ 2.0.0-SNAPSHOTjarMedia Handler
@@ -49,7 +49,7 @@
handler/media
- 2022-06-16T17:48:16Z
+ 2023-12-18T11:21:09Z
@@ -63,13 +63,13 @@
io.wcmio.wcm.handler.commons
- 1.4.4
+ 2.0.0-SNAPSHOTcompileio.wcmio.wcm.handler.url
- 1.5.0
+ 2.0.0-SNAPSHOTcompile
@@ -82,19 +82,26 @@
io.wcmio.wcm.wcm.commons
- 1.9.0
+ 1.10.0compileio.wcmio.wcm.sling.commons
- 1.4.0
+ 1.6.4compileio.wcmio.wcm.wcm.ui.granite
- 1.8.0
+ 1.10.0
+ compile
+
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ 3.1.8compile
@@ -131,7 +138,7 @@
io.wcmio.wcm.testing.aem-mock.junit5
- 5.0.0
+ 5.4.4test
@@ -143,13 +150,13 @@
org.apache.slingorg.apache.sling.testing.caconfig-mock-plugin
- 1.4.0
+ 1.5.4testio.wcmio.wcm.testing.wcm-io-mock.sling
- 1.1.0
+ 1.2.0test
@@ -161,7 +168,7 @@
io.wcmio.wcm.testing.wcm-io-mock.caconfig
- 1.1.0
+ 1.2.0test
@@ -180,7 +187,24 @@
org.skyscreamerjsonassert
- 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.0test
@@ -188,20 +212,20 @@
com.twelvemonkeys.imageioimageio-tiff
- 3.4.1
+ 3.10.1testcom.twelvemonkeys.imageioimageio-batik
- 3.4.1
+ 3.10.1testorg.apache.xmlgraphicsbatik-transcoder
- 1.9.1
+ 1.17test
@@ -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.pluginsi18n-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 extends MediaMarkupBuilder> 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 extends MediaMarkupBuilder> 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 extends MediaSource> 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.
+ *