9

Is there a way to add third party jars to Azure functions using JAVA. I would need to have the json-simple jar and jackson-databind jars to be available for the function at run time. Right now, My code throws a runtime exception(ClassNotFound Exception) as the function is not able to reference the jar during runtime because it is unavailable.

I tried using maven-shade-plugin. It does create an executable jar including the external jars but the deployment still takes the original jar.

Please suggest.

Thanks.

POM.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sce.api.learning</groupId>
    <artifactId>myApi</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>Azure Java Functions</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <functionAppName>mckapi-http-nov2</functionAppName>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>azure-functions-java-core</artifactId>
            <version>1.0.0-beta-1</version>
        </dependency>

        <!-- Adding GSON dependancy -->
        <dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>1.4</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.6.3</version>
</dependency>

<dependency>
    <groupId>com.googlecode.json-simple</groupId>
    <artifactId>json-simple</artifactId>
    <version>1.1.1</version>
</dependency>
<dependency>
    <groupId>org.json</groupId>
    <artifactId>json</artifactId>
    <version>20171018</version>
</dependency>

        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <groupId>com.microsoft.azure</groupId>
                    <artifactId>azure-functions-maven-plugin</artifactId>
                    <version>0.1.4</version>
                </plugin>
            </plugins>
        </pluginManagement>

        <plugins>
            <plugin>
                <groupId>com.microsoft.azure</groupId>
                <artifactId>azure-functions-maven-plugin</artifactId>
                <configuration>
                    <resourceGroup>java-functions-group</resourceGroup>
                    <appName>${functionAppName}</appName>
                    <region>westus2</region>
                    <appSettings>
                        <property>
                            <name>FUNCTIONS_EXTENSION_VERSION</name>
                            <value>beta</value>
                        </property>
                    </appSettings>
                </configuration>
                <executions>
                    <execution>
                        <id>package-functions</id>
                        <goals>
                            <goal>package</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-resources-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-resources</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-resources</goal>
                        </goals>
                        <configuration>
                            <overwrite>true</overwrite>
                            <outputDirectory>${project.build.directory}/azure-functions/${functionAppName}
                            </outputDirectory>
                            <resources>
                                <resource>
                                    <directory>${project.basedir}</directory>
                                    <includes>
                                        <include>host.json</include>
                                        <include>local.settings.json</include>
                                        **<include>**/*.jar</include>**<!-- This includes the jar files in the target/lib folder -->
                                    </includes>
                                </resource>
                            </resources>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                        <overwrite>true</overwrite>
                            <outputDirectory>${project.build.directory}/azure-functions/${functionAppName}
                            </outputDirectory>
              <shadedArtifactAttached>false</shadedArtifactAttached>
            </configuration>
                    </execution>
                </executions>
                <configuration>
                    <finalName>${artifactId}-${version}</finalName>
                </configuration>
            </plugin>

        </plugins>

    </build>

</project>
iakko
  • 508
  • 2
  • 5
  • 18
mack
  • 345
  • 5
  • 18

2 Answers2

5

I had the same problem and I figured out how to arrange a solution.

First of all, start from a brand new Maven project following the straightforward guide at this link.

Assume <project_root_path> as the folder where you will create the project.

Once you have generated your maven project, just add this maven-assembly-plugin plugin on your <project_root_path>/pom.xml within <build><plugins>...</plugins></build>:

<plugin>
   <artifactId>maven-assembly-plugin</artifactId>
   <configuration>
      <outputDirectory>${project.build.directory}/azure-functions/${functionAppName}</outputDirectory>
      <appendAssemblyId>false</appendAssemblyId>
      <descriptorRefs>
         <descriptorRef>jar-with-dependencies</descriptorRef>
      </descriptorRefs>
      <archive />
   </configuration>
   <executions>
      <execution>
         <id>make-assembly</id>
         <phase>package</phase>
         <goals>
            <goal>assembly</goal>
         </goals>
      </execution>
   </executions>
</plugin>

Compiling and packaging the Azure Function with command mvn clean compile package will produce a jar on path <project_root_path>/target/<project_name>.jar containing all the external libraries listed under the <dependencies></dependencies> of the pom.xml.

Note 1: if you didn't modify the standard pom.xml, <project_name> will be generated according to <artifactId>_<version>.jar.

Note 2: if you don't use the <appendAssemblyId>false</appendAssemblyId> directive on the above snippet, the <project_name> will be <artifactId>_<version>-<descriptorRef>.jar. Consider this for the following instructions.

So, now you should have your complete <project_root_path>/target/<project_name>.jar, but before using it you have to copy it under <project_root_path>/target/azure-functions/<azure_function_name>/ where <azure_function_name> is the name you gave to your function during the creation progress documented on the above link.

Now you can test it using the Azure Maven plugins:

  1. Running the Azure Function on your machine locally: mvn azure-functions:run
  2. Deploying the Azure Function on your subscription: mvn azure-functions:deploy

At the end, the key point is moving the jar generated with maven-assembly-plugin into the right place where the Azure Maven plugin will look during the run/deploy process. I wish I could find a more automatic way using standard Maven commands.

Hope this helps.

Ciao IP

Edit (17/11/17)

Adding <outputDirectory>${project.build.directory}/azure-functions/${functionAppName}</outputDirectory> within the <configuration> POM tag and changing <goal> tag to assembly, it makes Maven to automatically copy the final JAR in the Azure Function staging directory. This allows the mvn azure-functions:run and mvn azure-functions:deploy commands to directly use the correct JAR file containing all dependency. No manual actions are requested anymore.

The above POM have been updated accordingly.

Edit (21/11/17)

In case you want to use the Maven Shade Plugin instead of the Maven Assembly Plugin, replace the above XML snippet with this one:

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-shade-plugin</artifactId>
   <version>3.1.0</version>
   <configuration>
      <shadedArtifactAttached>false</shadedArtifactAttached>
      <outputFile>${project.build.directory}/azure-functions/${functionAppName}/${project.artifactId}-${project.version}.jar</outputFile>
      <filters>
         <filter>
            <artifact>*:*</artifact>
            <excludes>
               <exclude>META-INF/*.SF</exclude>
               <exclude>META-INF/*.DSA</exclude>
               <exclude>META-INF/*.RSA</exclude>
            </excludes>
         </filter>
      </filters>
   </configuration>
   <executions>
      <execution>
         <phase>package</phase>
         <goals>
            <goal>shade</goal>
         </goals>
      </execution>
   </executions>
</plugin>

It will work using the same standard Maven commands mentioned before.

iakko
  • 508
  • 2
  • 5
  • 18
  • +1 - This is exactly what I have did. and Yes! the added work of copying the jar to the target/azure-functions// is the overhead and I am looking for an automation. The recommended way from Microsoft is to build a FAT jar though!!! – mack Nov 16 '17 at 18:41
  • Well, I'm not surprised about the need of creating a jar containing all the dependencies, this is a common approach also for standard old-fashioned scenarios like deploying on containers (think about what you do with Tomcat, using war containing external jar libraries). – iakko Nov 16 '17 at 18:59
  • @mack: I've found a solution for automatizing the JAR copy without additional commands. Please, let me know if it works for your case. – iakko Nov 17 '17 at 11:50
  • Thank you! I have modifed the script so that I can build the FAT jar as per recommendation from MS. I tried using your suggestion and I could see that the custom JAR getting replaced at the ${project.build.directory}/azure-functions//target but not pushed into the ${project.build.directory}/azure-functions/${functionAppName}. Ideally, as per my understanding, the built jar is placed in 2 locations - one in the target folder and then the same is pushed to the ${project.build.directory}/azure-functions/${functionAppName} location. – mack Nov 20 '17 at 17:42
  • @mack: have you tried my entire pom.xml? It puts the final "FAT" jar in the `${project.build.directory}/azure-functions/${functionAppName}` where the `functionAppName` it the name of your Azure Function. I've changed nothing from the default pom.xml autogenerated during the setup process. – iakko Nov 20 '17 at 17:54
  • Your change is working fine. But I can stil see the original jar in the target folder. But YES! the custom jar with external dependency is pushed into the azure-functions/${functionAppName} folder. Also WHen I tried using the same logic in the Shade plugin, it is not working. I want to use a shade plugin to generate the FAT jar as it is the recommended way as per MS. – mack Nov 20 '17 at 19:42
  • I am looking for the same functionality in the maven shade plugin. That is the exact solution i am working for! – mack Nov 20 '17 at 19:45
  • @mack: I think I've found a transparent solution also using Maven Shade Plugin. See edit on 21/11/17. – iakko Nov 21 '17 at 17:53
  • GREAT THANKS A LOT ! GRAZIE MILLE – javierZanetti Jan 10 '18 at 10:35
  • I'm having the same issue, and can't seem to get this working, even with this shade-plugin. Any chance any of you have this code in a github repository you could share? – SnoopDougg Feb 23 '18 at 20:42
1

The latest version of pom.xml generated by the azure-functions-archetype that you use to prepare the Azure Function project (see this link for more details) seems to already include the plugin to copy dependencies.

My pom.xml includes the following plugin by default, and it seems to copy the dependencies that I have specified into the ${stagingDirectory}/lib automatically.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-dependencies</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>copy-dependencies</goal>
            </goals>
            <configuration>
                <outputDirectory>${stagingDirectory}/lib</outputDirectory>
                <overWriteReleases>false</overWriteReleases>
                <overWriteSnapshots>false</overWriteSnapshots>
                <overWriteIfNewer>true</overWriteIfNewer>
                <includeScope>runtime</includeScope>
                <excludeArtifactIds>azure-functions-java-library</excludeArtifactIds>
            </configuration>
        </execution>
    </executions>
</plugin>
Vijayendra Vasu
  • 271
  • 3
  • 7