For one of my Lucee CFML projects I needed to programmatically access Google Analytics reporting data and display it on web page. Lucee runs on Java and the Google Analytics API v4 has a Java library (SDK) so I knew it was possible. It turned out to be more invovled than I expected but I was able to grind it out.

The first thing you need to do is setup an API project in the Google Developer Console and enable access to the Google Analytics Reporting API. Then you need to create a "Sevice Account" and add the service account to your Google Analytics account. This process can be a bit confusing but the two links below, plus instructions should get you going.

Setup your API Project & Google Service Account

Hello Analytics Reporting API v4; Java quickstart for service accounts

Google Service Accounts

Create a new credential by selecting the "Service account key". This will create a JSON key file that you can download your computer.

google-api-service-account-01

The JSON key file will have a "client_email" that you'll need to add as a new user in Google Analytics.

google-json-key

Login to your Google Analytics account and setup the "Service Account" user.

ga-account-01-b

ga-account-02

Also: Click on "View Settings" and make a note of your Google Analtyics "View ID". You'll need it later when making requests to the API.

ga-viewid

Install the Google Analytics v4 Java Client SDK

Google Analytics Reporting API Client Library for Java

After downloading, unzip the archive and you'll see lots of JARS. You'll need to install these JARS in your Lucee (Tomcat) server.

ga-java-jars

My Lucee install path is /opt/lucee. Your install path may vary.

Create a folder in tomcat/lib to hold the Google Jars. For example.

/opt/lucee/tomcat/lib/google_api

After some trial and error I figured out which JARS are needed. I don't think all of them are required (like the android or appengine ones) but those don't seem to cause any problems.

ga-java-jars-02

Restart Lucee after copying the Jars.

/opt/lucee/lucee_ctl restart

Putting it all together

Ok, finally, now we can write some code. Remember that JSON key file from above. Put that somewhere secure on your server because you'll need it to make requests to the Google API.

Here's a quick and dirty googleanalytics.cfc

I'm sure this code could be simpler but it gets the job done. Please let me know in the comments if there's an easier way to tell Lucee to pick up the Google API Jars (so I don't need the long array of variables.dependencies = [...]).

<cfcomponent output="yes" displayname="Google Analytics API v4">

    <cfscript>
        init();
    </cfscript>

    <cffunction name="init" returntype="googleanalytics" access="private" output="yes">

        <cfscript>
            variables.jarDir = application.utils.getTomcatRootDirectory() & "/lib/google_api";
            variables.serverRootDir = application.utils.getServerRootDirectory();
            variables.keyPath = "/path/to/google-keys.json";

            variables.dependencies = [
                "#jarDir#/google-api-services-analyticsreporting-v4-rev115-1.22.0.jar",
                "#jarDir#/commons-logging-1.1.1.jar",
                "#jarDir#/google-api-client-1.22.0.jar",
                "#jarDir#/google-api-client-gson-1.22.0.jar",
                "#jarDir#/google-api-client-jackson2-1.22.0.jar",
                "#jarDir#/google-api-client-java6-1.22.0.jar",
                "#jarDir#/google-api-client-protobuf-1.22.0.jar",
                "#jarDir#/google-api-client-servlet-1.22.0.jar",
                "#jarDir#/google-api-client-xml-1.22.0.jar",
                "#jarDir#/google-http-client-1.22.0.jar",
                "#jarDir#/google-http-client-android-1.22.0.jar",
                "#jarDir#/google-http-client-appengine-1.22.0.jar",
                "#jarDir#/google-http-client-gson-1.22.0.jar",
                "#jarDir#/google-http-client-jackson-1.22.0.jar",
                "#jarDir#/google-http-client-jackson2-1.22.0.jar",
                "#jarDir#/google-http-client-jdo-1.22.0.jar",
                "#jarDir#/google-http-client-protobuf-1.22.0.jar",
                "#jarDir#/google-http-client-xml-1.22.0.jar",
                "#jarDir#/google-oauth-client-1.22.0.jar",
                "#jarDir#/google-oauth-client-appengine-1.22.0.jar",
                "#jarDir#/google-oauth-client-java6-1.22.0.jar",
                "#jarDir#/google-oauth-client-jetty-1.22.0.jar",
                "#jarDir#/google-oauth-client-servlet-1.22.0.jar",
                "#jarDir#/gson-2.1.jar",
                "#jarDir#/guava-jdk5-17.0.jar",
                "#jarDir#/httpclient-4.0.1.jar",
                "#jarDir#/httpcore-4.0.1.jar",
                "#jarDir#/jackson-core-2.1.3.jar",
                "#jarDir#/jackson-core-asl-1.9.11.jar",
                "#jarDir#/jdo2-api-2.3-eb.jar",
                "#jarDir#/jetty-6.1.26.jar",
                "#jarDir#/jetty-util-6.1.26.jar",
                "#jarDir#/jsr305-1.3.9.jar",
                "#jarDir#/protobuf-java-2.6.1.jar",
                "#jarDir#/transaction-api-1.1.jar",
                "#jarDir#/xpp3-1.1.4c.jar"
            ]
        </cfscript>

        <cfreturn this>

    </cffunction>

    <cffunction name="fetchReport" returntype="any" access="public" output="yes">

        <cfargument name="batchRequest" type="array" required="true">
        
        <cfscript>
            var httpTransport = createObject("java","com.google.api.client.googleapis.javanet.GoogleNetHttpTransport",dependencies).newTrustedTransport();

            var fileOjb = createObject("java","java.io.FileInputStream").init("#variables.keyPath#");
            var gaScopes = createObject("java","com.google.api.services.analyticsreporting.v4.AnalyticsReportingScopes",dependencies).all();
            var credential = createObject("java","com.google.api.client.googleapis.auth.oauth2.GoogleCredential",dependencies).fromStream(fileOjb).createScoped(gaScopes);

            var JSON_FACTORY = createObject("java","com.google.api.client.json.gson.GsonFactory",dependencies).getDefaultInstance();
           
            var gaReporting = createObject("java","com.google.api.services.analyticsreporting.v4.AnalyticsReporting$Builder",dependencies).init(httpTransport,JSON_FACTORY,credential);
            
            var service = gaReporting.setApplicationName("My API Project").build();
            
            var reportRequests = [];

            arguments.batchRequest.each(function(report,idx,arr) {
                
                var dateRange = createObject("java","com.google.api.services.analyticsreporting.v4.model.DateRange",dependencies);
                dateRange.setStartDate("#report.from#");
                dateRange.setEndDate("#report.to#");

                var reportRequest = createObject("java","com.google.api.services.analyticsreporting.v4.model.ReportRequest",dependencies);
                reportRequest.setViewId("#report.viewid#");
                reportRequest.setDateRanges([dateRange]);

                if (isdefined("report.filter")) {
                    reportRequest.setFiltersExpression(report.filter);
                }
                
                var mArray = [];
                for (metric in report.metrics) {
                    arrayAppend(mArray,createObject("java","com.google.api.services.analyticsreporting.v4.model.Metric",dependencies).setExpression("#metric#"));
                }
                //metric = createObject("java","com.google.api.services.analyticsreporting.v4.model.Metric",dependencies);
                //metric.setExpression("ga:sessions").setAlias("sessions");
                reportRequest.setMetrics(mArray);

                var dimArray = [];
                for (dim in report.dimensions) {
                    arrayAppend(dimArray,createObject("java","com.google.api.services.analyticsreporting.v4.model.Dimension",dependencies).setName("#dim#"));
                }
                reportRequest.setDimensions(dimArray);

                arrayAppend(reportRequests,reportRequest);

            });

            var getReportsRequest = createObject("java","com.google.api.services.analyticsreporting.v4.model.GetReportsRequest",dependencies);
            getReportsRequest.setReportRequests(reportRequests);

            var gaResponse = service.reports().batchGet(getReportsRequest).execute();

            //dump(serializeJson(gaResponse));

            return fixData(gaResponse); 

        </cfscript>

    </cffunction>

    <cffunction name="fixData" returntype="array" access="public" output="yes">

        <cfargument name="reportObj" type="struct" required="true">

        <cfscript>
            
            var arrResults = [];

            if (isdefined("arguments.reportObj.reports") and isarray(arguments.reportObj.reports)) {

                arguments.reportObj.reports.each(function(report,idx,arr) {

                    dim_cols = arraytolist(report.columnHeader.dimensions);
                    dim_cols = replacenocase(dim_cols,"ga:","","all");

                    metric_cols = "";
                    report.columnHeader.metricHeader.metricHeaderEntries.each(function(m,i,a) {
                        metric_cols = listAppend(metric_cols,m.name);
                    });
                    metric_cols = replacenocase(metric_cols,"ga:","","all");

                    cols = "#dim_cols#,#metric_cols#";
                    arrResults[idx] = querynew(cols);

                    if (isdefined("report.data.rows") and isarray(report.data.rows) and arraylen(report.data.rows)) {

                        report.data.rows.each(function(row,j,a) {
                            queryAddRow(arrResults[idx]);

                            row.dimensions.each(function(dim,k,a) {
                                col = listgetat(dim_cols,k);
                                querySetCell(arrResults[idx],col,dim);
                            });

                            row.metrics.each(function(metric,l,a) {

                                metric.values.each(function(m,o,a) {
                                    col = listgetat(metric_cols,o);
                                    querySetCell(arrResults[idx],col,m);
                                });
                                
                            });             

                            
                        });
                    }

                });

            }

            return arrResults;

        </cfscript>

    </cffunction>

</cfcomponent>

This sample code fetches a few typical Google Analytics reports that you might need in a single batch.

I created an application variable to hold my Google Analytics "View ID". See the screen shot above on where to find your view id.

The date range variables should be in the format: yyyy-mm-dd

Visit the Reporting API v4 Dimensions & Metrics Explorer to see all the possible metrics and dimensions you can plugin to your batch requests.

<cfscript>
    batchRequest = [
        {
            "viewid": application.ga_viewid,
            "from": session.daterange.from, 
            "to": session.daterange.to, 
            "dimensions": "ga:pagePath",
            "metrics": "ga:users"
        },
        {
           "viewid": application.ga_viewid,
            "from": session.daterange.from, 
            "to": session.daterange.to, 
            "dimensions": "ga:sourceMedium",
            "metrics": "ga:users"  
        },
        {
           "viewid": application.ga_viewid,
            "from": session.daterange.from, 
            "to": session.daterange.to, 
            "dimensions": "ga:city,ga:region,ga:country",
            "metrics": "ga:users"  
        },
        {
           "viewid": application.ga_viewid,
            "from": session.daterange.from, 
            "to": session.daterange.to, 
            "dimensions": "ga:deviceCategory",
            "metrics": "ga:users"  
        }

    ];

    ga = createObject("component","components.googleanalytics");
    ga_report = ga.fetchReport(batchRequest=batchRequest);
    dump(ga_report);

</cfscript>

Happy Coding.