Search code examples
javaaemcloudinary

Integrating Cloudinary with Adobe AEM


I am trying to integrate Adobe AEM 6.3 (running on Java 1.8) with Cloudinary SDK. I have done the following but, keep hitting an exception that I am not able to resolve. Has anyone integrated Cloudinary with AEM and run into similar issues?

  1. Add the dependency in the pom.xml for compiling the code.
<dependency>
  <groupId>com.cloudinary</groupId>
  <artifactId>cloudinary-core</artifactId>
  <version>1.24.0</version>  
</dependency>  
<dependency>
  <groupId>com.cloudinary</groupId>
  <artifactId>cloudinary-http44</artifactId>
  <version>1.24.0</version>         
</dependency>
  1. Build an OSGI plugin to ensure AEM gets the right jar files. For this purpose, I followed the steps to create a third party RESTful service example. To build the bundle, I had to explicitly download the following jar files: cloudinary-1.0.14.jar, cloudinary-core-1.21.0.jar, cloudinary-http44-1.21.0.jar, commons-codec-1.10.jar, commons-collections-3.2.2.jar, commons-lang3-3.1.jar, commons-logging-1.2.jar, httpclient-4.4.jar, httpmime-4.4.jar, jsp-api-2.0.jar

  2. Despite creating a bundle that has httpclient, I get the following exception when trying to upload an image to Cloudinary. Here's code and the exception.

Code snippet

import com.cloudinary.*;
..
Cloudinary cloudinary = new Cloudinary("<<credentials>>");
...

File toUpload = new File("/Users/akshayranganath/Downloads/background-2633962_1280.jpg");

try {
  Map uploadResult = cloudinary.uploader().upload(toUpload, ObjectUtils.emptyMap());
} catch (IOException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

Exception

Caused by: java.lang.NoClassDefFoundError: javax/net/ssl/HostnameVerifier
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.defineClass(BundleWiringImpl.java:2370)
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.findClass(BundleWiringImpl.java:2154)
    at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1542)
    at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:79)
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:2018)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    at org.apache.http.impl.conn.SchemeRegistryFactory.createDefault(SchemeRegistryFactory.java:52)
    at org.apache.http.impl.client.AbstractHttpClient.createClientConnectionManager(AbstractHttpClient.java:321)
    at org.apache.http.impl.client.AbstractHttpClient.getConnectionManager(AbstractHttpClient.java:484)
    at org.apache.http.impl.client.AbstractHttpClient.createHttpContext(AbstractHttpClient.java:301)
    at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:818)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:55)
    at com.cloudinary.Uploader.callApi(Uploader.java:317)
    at com.cloudinary.Uploader.upload(Uploader.java:57)
    at com.aem.community.core.models.HelloWorldModel.init(HelloWorldModel.java:59)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.apache.sling.models.impl.ModelAdapterFactory.invokePostConstruct(ModelAdapterFactory.java:792)
    at org.apache.sling.models.impl.ModelAdapterFactory.createObject(ModelAdapterFactory.java:607)
    ... 211 common frames omitted
Caused by: java.lang.ClassNotFoundException: javax.net.ssl.HostnameVerifier not found by MyBundle [550]
    at org.apache.felix.framework.BundleWiringImpl.findClassOrResourceByDelegation(BundleWiringImpl.java:1574)
    at org.apache.felix.framework.BundleWiringImpl.access$400(BundleWiringImpl.java:79)
    at org.apache.felix.framework.BundleWiringImpl$BundleClassLoader.loadClass(BundleWiringImpl.java:2018)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
    ... 236 common frames omitted

This is the first time I am working with AEM and I may not be following the right steps. Please let me know if anyone has been able to get past this issue.

Update

Based on Alexander's suggestion and a pointer from another source, I added the following code to the parent pom.xml file.

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>3.5.0</version>
    <configuration>
        <instructions>
        <Embed-Dependency>*;scope=compile|runtime</Embed-Dependency>
        <Embed-Directory>OSGI-INF/lib</Embed-Directory>
        <Embed-Transitive>true</Embed-Transitive>
        </instructions>
    </configuration>
</plugin>

After making this change, the cloudinary libraries were being added to the bundle. Here's the output from AEM: http://localhost:4502/system/console/bundles

Embedded-Artifacts: OSGI-INF/lib/cloudinary-http44-1.21.0.jar; g="com.cloudinary"; a="cloudinary-http44"; v="1.21.0", OSGI-INF/lib/commons-lang3-3.1.jar; g="org.apache.commons"; a="commons-lang3"; v="3.1", OSGI-INF/lib/httpclient-4.4.jar; g="org.apache.httpcomponents"; a="httpclient"; v="4.4", OSGI-INF/lib/httpcore-4.4.jar; g="org.apache.httpcomponents"; a="httpcore"; v="4.4", OSGI-INF/lib/commons-logging-1.2.jar; g="commons-logging"; a="commons-logging"; v="1.2", OSGI-INF/lib/commons-codec-1.9.jar; g="commons-codec"; a="commons-codec"; v="1.9", OSGI-INF/lib/httpmime-4.4.jar; g="org.apache.httpcomponents"; a="httpmime"; v="4.4", OSGI-INF/lib/cloudinary-core-1.21.0.jar; g="com.cloudinary"; a="cloudinary-core"; v="1.21.0"

However, I now get an error with this message:

org.apache.avalon.framework.logger -- Cannot be resolved
org.apache.log -- Cannot be resolved

I am able to resolve the org.apache.avalon.framework.logger error by adding a dependency Avalon framework. But, I am not able to get over the org.apache.log issue. It looks like there is a version conflict that is causing the problem.

This new error starts when I include the Cloudinary http44 library. This library doesn't appear to directly reference logging (see here for dependencies). Due to this error, the application still fails to go from Installed to Active state.


Solution

  • Cloudinary-libs are available as Maven artifacts. Such JAR-files can be put in your bundle as private libraries with the maven-bundle-plugin.

    The following sample works for me (even with Cloudinary test account)

    ...
    <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <extensions>true</extensions>
        <executions>
            <execution>
                <!-- Create the bundle late in the compile-phase instead of the package-phase.
                     So the generated OSGi meta-data is available during JUnit tests. -->
                <id>run-before-tests</id>
                <phase>process-classes</phase>
                <goals>
                    <goal>bundle</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <instructions>
                <Bundle-Name>Test Bundle</Bundle-Name>
                <Embed-Dependency>*;groupId=com.cloudinary;scope=compile|runtime</Embed-Dependency>
                <Embed-Directory>OSGI-INF/lib</Embed-Directory> <!-- not needed, but nice -->
                <Embed-Transitive>true</Embed-Transitive>
            </instructions>
        </configuration>
    </plugin>
    ...
    
    <dependencies>
        <dependency>
            <groupId>com.cloudinary</groupId>
            <artifactId>cloudinary-core</artifactId>
            <version>1.24.0</version>
        </dependency>
        <dependency>
            <groupId>com.cloudinary</groupId>
            <artifactId>cloudinary-http44</artifactId>
            <version>1.24.0</version>
        </dependency>
        ...
    

    In general embedding an external library can be from simple, cumbersome to impossible. It depends on the dependencies of the imported artifacts.

    Check the dependency tree manually! (e.g. https://mvnrepository.com/)

    You have to fiddle with 3 instructions:

    Embed-Dependency

    This are the libraries, that are put in your bundle. Be careful with the asterisk operator, otherwise you may include way too many dependencies (in case of AEM easily half of the internet). But do not include too less! Extract the built bundle.jar, to see what is actually included (in case of cloudinary it was easy).

    Import-Package

    Often the libs have way too many dependencies, especially if libs come an other ecosystem (like Spring or JEE containers), or have a lot of semi-optional dependencies. With this setting you can tell OSGi, that a bundle can be activated, even if certain dependencies are not available.

    This is a real world example :

    <Import-Package>
        !com.sun.msv.*,
        !org.apache.log4j.jmx.*,
        !sun.misc.*,
        !org.jboss.logging.*,
        !org.apache.zookeeper.*,
        *
    </Import-Package>
    

    Export-Package

    Normally the library should be bundle-private. But sometimes you have to import differently, or the lib does something automatically. So you should always check in the system console, what your bundle is exporting. In case it is not right, you have to manually fiddle with this setting:

    Here is an example:

    <Export-Package>
        !*.internal,
        !*.internal.*,
        !*.impl,
        !*.impl.*,
        com.mycompany.myproject.mybundle.*
    </Export-Package>
    

    By default all packages * are exported, except they are named impl or internal. Also their child packages are private (the !*.impl.* rule). If the default doesn't work, then export with this instruction only what you need.

    Whatever you export goes to the global OSGi space. As also the AEM- and Sling-Bundles are not perfect nor 100%-bugfree, please make sure

    • the startup/shutdown order of out-of-the-box AEM bundles should not be changed
    • a deployment, re-deployment or un-deployment of your code should not start/stop any out-of-the-box AEM bundles.

    If you don't ensure this, you might experience strange deployment issues - that are very difficult to find/solve.

    So the best is, NOT to export anything that is imported by any AEM out-of-the-box bundle. Everything else is for Experts-only. And even they overestimate themselves, and underestimate the long-term costs of patching AEM manually.

    PS: the _removeheaders instruction could remove all osgi-instructions that are not needed for runtime. But only do this, if you want to provide a bundle to the public and make it totally shiny. I would leave it in, as it is some kind of documentation.