Article summary

Summary

Discusses how to create a custom Adobe Experience Manager commerce provider using the commerce provider APIs.

For information about this use case with Experience Manager 6.2 - see Creating custom Adobe Experience Manager 6.2 ecommerce providers.

Digital Marketing Solution(s) Adobe Experience Manager (Adobe CQ)
Audience
Developer (intermediate)
Required Skills
Java, Maven, XML
Version Adobe Experience Manager 5.6

Introduction

An Adobe Experience Manager shopping cart component is used within an Experience Manager online retail site and provides functionality such as:

  • displays an overview of selected items
  • links to the individual product pages
  • updates to quantity
  • removes an item

A shopping cart component lets visitors to your Experience Manager online retail site purchase items, as shown in the following illustration.

ShoppingCart

This development article guides you through building an Experience Manager custom commerce provider that uses the Experience Manager Commerce API (this API belong to the com.adobe.cq.commerce.api package). A shopping cart component uses a commerce provider to obtain product data. Included in the custom commerce provider developed in this article is an OSGi bundle that exposes operations that the Geometrix Outdoors web application invokes.

By following along with this walk through, you learn how to setup, develop, and configure an Experience Manager custom commerce provider. 

You use Maven to create a custom commerce provider. You can also configue a Maven POM file to automatically deploy the commerce provider package to Experience Manager. That is, the custom commerce provider is deployed to Experience Manager when you build the package by using a Maven command from the command prompt.  

Note:

You can watch this related video: AEM (CQ) eCommerce Integration Framework.

Setup Maven in your development environment

The first step to create a custom commerce provider is to setup Maven in your development environment. You can download Maven 3 from the following URL:

http://maven.apache.org/download.html

After you download and extract Maven, create an environment variable named M3_HOME. Assign the Maven install location to this environment variable. For example:

C:\Programs\Apache\apache-maven-3.0.4

You can test Maven to determine whether it is properly setup by entering the following command into a command prompt:

%M3_HOME%\bin\mvn -version

This command provides Maven and Java install details and resembles the following message.

Apache Maven 3.0.4 (r1232337; 2012-01-17 03:44:56-0500)
Maven home: C:\Programs\Apache\Maven\apache-maven-3.0.4
Java version: 1.6.0_31, vendor: Sun Microsystems Inc.
Java home: C:\Programs\Java64-6\jre
Default locale: en_US, platform encoding: Cp1252
OS name: “windows 7”, version: “6.1”, arch: “amd64”, family: “windows”

Note:

For more information about setting up Maven and the Home variable, see: Maven in 5 Minutes.

Next, copy the Maven configuration file named settings.xml from [install location]\apache-maven-3.0.4\conf\ to your user profile. For example, C:\Users\scottm\.m2\.

You have to configure your settings.xml file to use Adobe’s public repository. For information, see Adobe Public Maven Repository at http://repo.adobe.com/.

Create an Experience Manager archetype project

You can create an Experience Manager archetype project (the basis of the custom commerce provider) by using the Maven archetype plugin. In this example, assume that the working directory is C:\AdobeCQ.

To create an Experience Manager archetype project, perform these steps:

1. Open the command prompt and go to your working directory (for example, C:\AdobeCQ).
2. Run the following Maven command:

mvn archetype:generate -DarchetypeRepository=https://repo.adobe.com/nexus/content/groups/public/ -DarchetypeGroupId=com.day.jcr.vault -DarchetypeArtifactId=multimodule-content-package-archetype -DarchetypeVersion=1.0.2 -DgroupId=com.adobe.training -DartifactId=commerce-training -Dversion=1.0-SNAPSHOT -Dpackage=com.adobe.training -DappsFolderName=myproject -DartifactName="My Project" -DcqVersion="5.6.1" -DpackageGroup="My Company"

3. When prompted for additional information, specify Y. 

4. Once done, you will see a message like:

[INFO] BUILD SUCCESS
[[INFO] Total time: 14:46.131s
[INFO] Finished at: Wed Mar 27 13:38:58 EDT 2013
[INFO] Final Memory: 10M/184M

5. Change the command prompt to the generated project. For example: C:\AdobeCQ\commerce-training. Run the following Maven command:

mvn eclipse:eclipse

After you run this command, you can import the project into Eclipse as discussed in the next section. 
 

Add Java files to the Maven project using Eclipse

To make it easier to work with the Maven generated project, import it into the Eclipse development environment, as shown in the following illustration.

Eclipse

The next step is to add Java files to the com.adobe.training package. The Java files that you create in this section use the Experience Manager Commerce API. For information about the  Commerce API, see Package com.adobe.cq.commerce.api.

SimpleDSComponent file

Add a new class to the com.adobe.training package named SimpleDSComponent. This class inherits from the Runnable class, contains @Component and @Service annotations, and exposes these methods:

  • run – invoked when the service is started. 
  • activate – invoked when the service is activated. 
  • deactivate – invoked when the service is deactived. 

The following Java code represents the SimpleDSComponent class. 

package com.adobe.training;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * A basic DS Component
 */
@Component(metatype=true)
@Service
public class SimpleDSComponent implements Runnable {
    
    private Logger logger = LoggerFactory.getLogger(this.getClass());
    private BundleContext bundleContext;
    
    public void run() {
        logger.info("Running...");
    }
    
    protected void activate(ComponentContext ctx) {
        this.bundleContext = ctx.getBundleContext();
    }
    protected void deactivate(ComponentContext ctx) {
        this.bundleContext = null;
    }
}

TrainingCommerceServiceFactory file

The TrainingCommerceServiceFactory class is the service factory for the custom commerce provider. You use this service factory to create a CommerceService instance, which is the main class when using the Experience Manager commerce API. For example, you can invoke the CommerceService.getProduct method to get a product instance. For information about CommerceService, see http://docs.adobe.com/docs/en/cq/current/javadoc/com/adobe/cq/commerce/api/CommerceService.html

The following code represents the TrainingCommerceServiceFactory Java file. 

package com.adobe.training;

import org.apache.sling.api.resource.Resource;

import com.adobe.cq.commerce.api.CommerceService;
import com.adobe.cq.commerce.api.CommerceServiceFactory;
import com.adobe.cq.commerce.common.AbstractJcrCommerceServiceFactory;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;

/**
 * A simple new (for training) implementation for the {@link CommerceServiceFactory} interface.
 */
@Component(metatype = true, label = "Adobe CQ Commerce Factory for Training")
@Service
@Properties(value = {
        @Property(name = "service.description", value = "Factory for training commerce service"),
        @Property(name = "commerceProvider", value = "training")
})

public class TrainingCommerceServiceFactory extends AbstractJcrCommerceServiceFactory implements CommerceServiceFactory {

	/**
     * Create a new <code>TrainingCommerceServiceImpl</code>.
     */
	public CommerceService getCommerceService(Resource res) {
		return new TrainingCommerceServiceImpl(getServices(), res);
	}

}

Notice this class uses a @Property annotation that specifies the commerce provider name:

@Property(name = "commerceProvider", value = "training")

This property is important because its informs Experience Manager to use this custom commerce provider (this is shown later in this development article). 
The getCommerceService method creates a CommerceService instance that is used by the custom commerce provider.

TrainingCommerceServiceImpl file

The following file represents the TrainingCommerceServiceImpl class.

package com.adobe.training;

import java.util.List;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;

import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceQuery;
import com.adobe.cq.commerce.api.CommerceResult;
import com.adobe.cq.commerce.api.CommerceService;
import com.adobe.cq.commerce.api.CommerceSession;
import com.adobe.cq.commerce.api.Product;
import com.adobe.cq.commerce.api.promotion.Voucher;
import com.adobe.cq.commerce.common.AbstractJcrCommerceService;
import com.adobe.cq.commerce.common.AbstractJcrCommerceServiceFactory;
import com.adobe.cq.commerce.common.AbstractJcrProduct;

public class TrainingCommerceServiceImpl extends AbstractJcrCommerceService implements CommerceService {
    
	private Resource resource;
    private ResourceResolver resolver;
    
    public TrainingCommerceServiceImpl(AbstractJcrCommerceServiceFactory.Services services, Resource res) {
        super(services);
        this.resource = res;
        this.resolver = res.getResourceResolver();
    }

	public CommerceSession login(SlingHttpServletRequest request,
			SlingHttpServletResponse response) throws CommerceException {
		return new TrainingCommerceSessionImpl(this, request, response, resource);
	}

    public Product getProduct(final String path) throws CommerceException {
        Resource resource = resolver.getResource(path);
        if (resource != null && resource.isResourceType(AbstractJcrProduct.RESOURCE_TYPE_PRODUCT)) {
            return new TrainingProductImpl(resource);
        }
        return null;
    }

	public List<String> getCountries() throws CommerceException {
		// TODO Auto-generated method stub
		return null;
	}

	public List<String> getCreditCardTypes() throws CommerceException {
		// TODO Auto-generated method stub
		return null;
	}

	public Voucher getVoucher(String arg0) throws CommerceException {
		// TODO Auto-generated method stub
		return null;
	}

	public CommerceResult search(CommerceQuery arg0) throws CommerceException {
		// TODO Auto-generated method stub
		return null;
	}
	
}

TrainingCommerceSessionImpl file

The following file represents the TrainingCommerceSessionImpl file. 

package com.adobe.training;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.jcr.Value;



import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;

import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.common.AbstractJcrCommerceService;
import com.adobe.cq.commerce.common.AbstractJcrCommerceSession;

import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;


import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.user.Authorizable;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import com.adobe.cq.commerce.api.CommerceConstants;
import com.adobe.cq.commerce.api.CommerceException;
import com.adobe.cq.commerce.api.CommerceSession;
import com.adobe.cq.commerce.api.PriceInfo;
import com.adobe.cq.commerce.api.Product;
import com.adobe.cq.commerce.api.promotion.Promotion;
import com.adobe.cq.commerce.api.promotion.PromotionHandler;
import com.adobe.cq.commerce.api.promotion.PromotionInfo;
import com.adobe.cq.commerce.api.promotion.PromotionInfo.PromotionStatus;
import com.adobe.cq.commerce.api.promotion.PromotionManager;
import com.adobe.cq.commerce.api.promotion.Voucher;
import com.adobe.cq.commerce.common.promotion.AbstractJcrVoucher;
import com.adobe.granite.security.user.UserProperties;
import com.adobe.granite.security.user.UserPropertiesManager;
import com.day.cq.commons.Language;
import com.day.cq.commons.jcr.JcrUtil;
import com.day.cq.i18n.I18n;
import com.day.cq.personalization.ContextSessionPersistence;
import com.day.cq.personalization.UserPropertiesUtil;
import com.adobe.cq.commerce.api.CommerceSession.CartEntry;

public class TrainingCommerceSessionImpl extends AbstractJcrCommerceSession {
	
	protected SlingHttpServletRequest request;
    protected SlingHttpServletResponse response;
    protected Resource resource;
    protected ResourceResolver resolver;
    protected AbstractJcrCommerceService commerceService;
    
    protected List<CartEntry> cart = new ArrayList<CartEntry>();
    protected List<AbstractJcrVoucher> vouchers = new ArrayList<AbstractJcrVoucher>();
    protected List<String> activePromotions = new ArrayList<String>();

	public TrainingCommerceSessionImpl(
			AbstractJcrCommerceService commerceService,
			SlingHttpServletRequest request, SlingHttpServletResponse response,
			Resource resource) throws CommerceException {
		super(commerceService, request, response, resource);
	
		 this.request = request;
	     this.response = response;
	     this.resource = resource;
	     this.resolver = resource.getResourceResolver();
	     this.commerceService = commerceService;
		
	}
}

TrainingProductImpl file

The following Java code represents the TrainingProductImpl class. Notice the getSKU method. When Experience Manager is configured to use this custom commerce provider, you will see the return value of this method displayed witin the Geometrix Outdoors web application (this is shown later in this development article).  

package com.adobe.training;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;

import com.adobe.cq.commerce.common.AbstractJcrProduct;
import com.day.cq.wcm.api.Page;
import com.day.cq.wcm.api.PageManager;

public class TrainingProductImpl extends AbstractJcrProduct {
    public static final String PN_IDENTIFIER = "identifier";
    public static final String PN_PRICE = "price";

    protected final ResourceResolver resourceResolver;
    protected final PageManager pageManager;
    protected final Page productPage;
    protected String brand = null;

    public TrainingProductImpl(Resource resource) {
        super(resource);

        resourceResolver = resource.getResourceResolver();
        pageManager = resourceResolver.adaptTo(PageManager.class);
        productPage = pageManager.getContainingPage(resource);
    }

	public String getSKU() {
		// TODO Auto-generated method stub
		return "todo-get-sku-method";
	}
}

Modify Maven POM files

Modify two POM files that are included in the Maven generated project. One POM file is located in the root of the Maven-generated project and the second POM file is located in the bundle folder. 


Modify the POM file located in the root of the Maven project

You add a new profile element that automatically installs the package to Experience Manager when you build the Maven project using the command line. In the following example, the id of the profile element is installPackage. This id value is specified in the Maven command when you build the package (this is shown in a later step). Once installed, the OSGi bundle that is part of the package is also deployed and is in an Active state.

Copy the following XML to the POM file located in the root of the Maven generated project (for example, C:\AdobeCQ\commerce-training).

<?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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- ====================================================================== -->
    <!-- P A R E N T P R O J E C T D E S C R I P T I O N -->
    <!-- ====================================================================== -->

    <groupId>com.adobe.training</groupId>
    <artifactId>commerce-training</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <name>Commerce Training Bundle - Reactor Project</name>
    <description>Maven Multimodule project for Commerce Training Bundle.</description>

    <!-- ====================================================================== -->
    <!-- P R O P E R T I E S -->
    <!-- ====================================================================== -->
    <properties>
        <crx.host>localhost</crx.host>
        <crx.port>4502</crx.port>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-scr-plugin</artifactId>
                    <version>1.7.4</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.felix</groupId>
                    <artifactId>maven-bundle-plugin</artifactId>
                    <version>2.3.6</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <!-- use version 2.3.2 to have java 1.5 as the default -->
                    <version>2.3.2</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>2.5</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.sling</groupId>
                    <artifactId>maven-sling-plugin</artifactId>
                    <version>2.0.6</version>
                </plugin>
                <plugin>
                    <groupId>com.day.jcr.vault</groupId>
                    <artifactId>content-package-maven-plugin</artifactId>
                    <version>0.0.13</version>
                </plugin>
                <!--This plugin's configuration is used to store Eclipse
                    m2e settings only. It has no influence on the Maven build itself. -->
                <plugin>
                    <groupId>org.eclipse.m2e</groupId>
                    <artifactId>lifecycle-mapping</artifactId>
                    <version>1.0.0</version>
                    <configuration>
                        <lifecycleMappingMetadata>
                            <pluginExecutions>
                                <pluginExecution>
                                    <pluginExecutionFilter>
                                        <groupId>
                                            org.apache.felix
                                        </groupId>
                                        <artifactId>
                                            maven-scr-plugin
                                        </artifactId>
                                        <versionRange>
                                            [1.0.0,)
                                        </versionRange>
                                        <goals>
                                            <goal>scr</goal>
                                        </goals>
                                    </pluginExecutionFilter>
                                    <action>
                                        <ignore/>
                                    </action>
                                </pluginExecution>
                            </pluginExecutions>
                        </lifecycleMappingMetadata>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>org.osgi.core</artifactId>
                <version>4.2.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.osgi</groupId>
                <artifactId>org.osgi.compendium</artifactId>
                <version>4.2.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.apache.felix</groupId>
                <artifactId>org.apache.felix.scr.annotations</artifactId>
                <version>1.6.0</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>1.5.10</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.8.1</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <profiles>
        <profile>
            <id>installPackage</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <build>
                <pluginManagement>
                    <plugins>
                        <plugin>
                            <groupId>com.day.jcr.vault</groupId>
                            <artifactId>content-package-maven-plugin</artifactId>
                            <executions>
                                <execution>
                                    <id>content-package-install</id>
                                    <phase>package</phase>
                                    <goals>
                                        <goal>install</goal>
                                    </goals>
                                    <configuration>
                                        <userId>admin</userId>
                                        <password>admin</password>
                                        <targetURL>http://${crx.host}:${crx.port}/crx/packmgr/service.jsp</targetURL>
                                        <verbose>true</verbose>
                                        <failOnError>true</failOnError>
                                    </configuration>
                                </execution>
                            </executions>
                        </plugin>
                    </plugins>
                </pluginManagement>
            </build>
        </profile>        
        <profile>
            <id>autoInstallBundle</id>
            <build>
                <plugins>
                    <plugin>
                        <groupId>org.apache.sling</groupId>
                        <artifactId>maven-sling-plugin</artifactId>
                        <executions>
                            <execution>
                                <id>install-bundle</id>
                                <goals>
                                    <goal>install</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
    </profiles>
  <modules>
    <module>bundle</module>
    <module>content</module>
  </modules>
</project>

Modify the POM file located in the bundle folder

Modify the POM file located in the bundle folder that is part of the Maven generated project. It is necessary to add dependencies to the POM file so that the package builds successfully. Copy the following XML to the POM file located in the bundle folder (for example, C:\AdobeCQ\commerce-training\bundle).

<?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/maven-v4_0_0.xsd ">
    <modelVersion>4.0.0</modelVersion>
    <!-- ====================================================================== -->
    <!-- P A R E N T P R O J E C T D E S C R I P T I O N -->
    <!-- ====================================================================== -->
    <parent>
        <groupId>com.adobe.training</groupId>
        <artifactId>commerce-training</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <!-- ====================================================================== -->
    <!-- P R O J E C T D E S C R I P T I O N -->
    <!-- ====================================================================== -->

    <artifactId>commerce-training-bundle</artifactId>
    <packaging>bundle</packaging>
    <name>Commerce Training Bundle Bundle</name>

    <!-- ====================================================================== -->
    <!-- B U I L D D E F I N I T I O N -->
    <!-- ====================================================================== -->
    <build>

        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-scr-plugin</artifactId>
                <executions>
                    <execution>
                        <id>generate-scr-descriptor</id>
                        <goals>
                            <goal>scr</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-SymbolicName>com.adobe.training.commerce-training-bundle</Bundle-SymbolicName>
                    </instructions>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.sling</groupId>
                <artifactId>maven-sling-plugin</artifactId>
                <configuration>
                    <slingUrl>http://${crx.host}:${crx.port}/apps/commerce-training/install</slingUrl>
                    <usePut>true</usePut>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.compendium</artifactId>
        </dependency>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>org.osgi.core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.scr.annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.jcr</groupId>
            <artifactId>jcr</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>servlet-api</artifactId>
            <version>6.0.36</version>
        </dependency>    
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.jackrabbit</groupId>
            <artifactId>jackrabbit-api</artifactId>
            <version>2.5.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.api</artifactId>
            <version>2.2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.settings</artifactId>
            <version>1.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.jcr.api</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.sling</groupId>
            <artifactId>org.apache.sling.adapter</artifactId>
            <version>2.0.4</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.granite</groupId>
            <artifactId>com.adobe.granite.security.user</artifactId>
            <version>0.0.20</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.day.cq.wcm</groupId>
            <artifactId>cq-wcm-api</artifactId>
            <version>5.6.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.day.cq</groupId>
            <artifactId>cq-commons</artifactId>
            <version>5.6.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.cq.commerce</groupId>
            <artifactId>cq-commerce-core</artifactId>
            <version>5.6.8</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.adobe.cq.wcm</groupId>
            <artifactId>cq-geometrixx-commons-core</artifactId>
            <version>1.0.10</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.day.cq</groupId>
            <artifactId>cq-personalization</artifactId>
            <version>5.6.2</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
</project>

Build the custom commerce package using Maven

Build the custom commerce package by using Maven. After you build the package, the OSGi bundle that is part of the package is deployed and is in an Active state.

To build the custom commerce package, perform these steps.

1. From within your command prompt, go to the root of the Maven generated project (for example, C:\AdobeCQ\commerce-training).

2. Enter the following Maven command (notice that you specify the Id of the profile element that you added to the POM file in the previous step).

mvn clean install -P installPackage 

3. Once done, you will see a message like:

[INFO] BUILD SUCCESS
[[INFO] Total time: 14:46.131s
[INFO] Finished at: Wed Mar 27 13:38:58 EDT 2013
[INFO] Final Memory: 10M/184M

Verify that the custom commerce provider is installed

After you build the package using Maven, you can verify whether it is installed by viewing Package Manager, as shown in the following illustration. 

packageManager

To view the custom commerce package within Package Manager, perform these steps:

  1. To view the welcome page, enter the URL http://[host name]:[port] into a web browser. For example, http://localhost:4502.
  2. Click Tools from the left menu.
  3. Select Packages under the Granite Operations section.  

Viewing the OSGi bundle that is part of the custom commerce package

In addition to viewing the package in Package Manager, you can view the OSGi bundle in the Apache Felix Web Conole.

OSGi

View the custom commerce OSGi bundle by performing these steps:

  1. Login to Apache Felix Web Console at http://server:port/system/console/bundles (default admin user = admin with password= admin).
  2. Under the OSGI menu, select Bundles.
  3. Sort the bundle list by Id, and note the Id of the last bundle.
  4. Your new bundle should now be listed with the status Active. If the status is not Active, check the CQ error.log for exceptions.

Configure Experience Manager to use the custom commerce provider

Configure Experience Manager to use the custom commerce provider instead of the geomatrix implementation. To perform this task, modify the cq:commerceProvider property that belongs to the following node:

 /content/geometrixx-outdoors/en/jcr:content

Set this property to training. This value corresponds to the Property annotation value that you defined in the TrainingCommerceServiceFactory class:

@Property(name = "commerceProvider", value = "training")

Once you modify the cq:commerceProvider property, you can view the results of the custom commerce provider. For example, consider the getSKU method defined in the TrainingProductImpl class:

public String getSKU() {
		// Define a basic return value that will be displayed in 
                //a product page
		return "todo-get-sku-method";
	}

You can see the the return value of the getSKU method by viewing a product page, as shown in the following illustration.

product

You can view this product page by entering the following URL into a web browser:

http://[server]:[port]/cf#/content/geometrixx-outdoors/en/equipment/skiing/banff-snow.html#todo-get-sku-method

Override the LoadCart and SaveCart methods

The final task to perform in this development article is to override the commerce provider's LoadCart and SaveCart methods. By overriding these methods, you can implement functionality that meets your business requirements. For example, by overriding the savecart method, you can save product data in the Experience Manager JCR. 

Overriding the SaveCart method

To override the savecart method, add a method named savecart to the TrainingCommerceSessionImpl class. In this example, Java application logic saves product data to the Experience Manager JCR.

Assume, for example, the user is logged in as an administrator and selects Geometrix outdoors product items. Java application logic in the savecart method stores product data in the following user-specific JCR location:

home/users/a/admin/commerce/cart

In this example, because the user is logged in as an administrator, product data is stored under home/users/a/admin.

The following Java code represents the savecart method. 

protected void saveCart() throws CommerceException {
// Save cart to a cookie.

try {
    Map<String, String> cartStore = new HashMap<String, String>();
    for (CartEntry entry : cart) {
        cartStore.put("product" + entry.getEntryIndex(), entry.getProduct().getPath());
        cartStore.put("quantity" + entry.getEntryIndex(), "" + entry.getQuantity());
        }

    cartStore.put("entryCount", "" + getCartEntryCount());
 
    for (int i = 0; i < vouchers.size(); i++) {
            cartStore.put("voucher" + i, vouchers.get(i).getPath());
        }

    cartStore.put("voucherCount", String.valueOf(vouchers.size()));

     for (int i = 0; i < activePromotions.size(); i++) {
            cartStore.put("promotion" + i, activePromotions.get(i));
    }
    
    cartStore.put("promotionCount", String.valueOf(activePromotions.size()));
 
    Map<String, Map<String, String>> stores = new HashMap<String, Map<String,String>>();
        stores.put("CART", cartStore);
        stores.put("ORDER", orderDetails);
 
        ContextSessionPersistence.putStores(request, response, stores, CommerceConstants.COMMERCE_COOKIE_NAME);
 
        } catch (Exception e) {
                throw new CommerceException("Failed to save cart to cookie: ", e);
        }
}
 
        
 // One might also chose to store cart data (for non-anonymous   //shoppers) in their home directory.
 // That would look something like:
         
 
try {
        Session userSession = resolver.adaptTo(Session.class);
        final UserProperties userProperties = request.adaptTo(UserProperties.class);

        if (userProperties != null && !UserPropertiesUtil.isAnonymous(userProperties)) {
                     
    	UserManager um = ((JackrabbitSession) userSession).getUserManager();
        Authorizable authorizable =  um.getAuthorizable(userProperties.getAuthorizableID());
 
        Node cartNode =  JcrUtil.createPath(authorizable.getPath() + "/commerce/cart",  "nt:unstructured", userSession);
 
        List<String> entries = new ArrayList<String>();
                
        for (CartEntry entry : cart) {
        	entries.add(entry.getProduct().getPath() + ";" + entry.getQuantity());
        }
                
        cartNode.setProperty("items", entries.toArray(new  String[entries.size()]));
        List<String> details = new ArrayList<String>();
                    
        for (String key : orderDetails.keySet()) {
        	details.add(key + "=" + orderDetails.get(key));
        }
        
        cartNode.setProperty("details", details.toArray(new  String[details.size()]));
        userSession.save();
            }
    } catch (Exception e) {
                throw new CommerceException("Failed to save cart to user's home: ", e);
            }
    } 	
}

Override the LoadCart method

To override the loadcart method, add a method named loadcart to the TrainingCommerceSessionImpl class. In this example, Java application logic populates the cart component with product data retrieved from the Adobe JCR.

The following Java code represents the loadcart method.

//Load Cart
protected void loadCart() throws CommerceException {
// Load cart from the cookie:

int cartEntries = 0;
try {
    Map<String, String> cartStore = ContextSessionPersistence.getStore(request, "CART", CommerceConstants.COMMERCE_COOKIE_NAME);

    String entryCountString = cartStore.get("entryCount");
    if (entryCountString != null && entryCountString.length() > 0) {
        int entryCount = Integer.parseInt(entryCountString);
        for (int i = 0; i < entryCount; i++) {
        try {

            Product product = commerceService.getProduct(cartStore.get("product" + i));
            if (product == null) {
                    throw new Exception(); // handle all errors in catch block below
                }
    int quantity = Integer.parseInt(cartStore.get("quantity" + i));
    if (quantity != quantity ) {
                throw new Exception(); // handle all errors in catch block below
    }

    if (quantity > 0) {
            doAddCartEntry(product, quantity);
            cartEntries++;
    }

} catch (Exception e) {

        // Probably due to a cart format change or a product change. While it's not nice
       // to drop cart items, it's even worse to lock the shopper out, so we'll continue
    // bravely on.
    log.error("Unable to load product from cookie: {}, qty: {}",
    cartStore.get("product" + i), cartStore.get("quantity" + i));
                }
            }
    }
   
 String voucherCountString = cartStore.get("voucherCount");

if (voucherCountString != null && voucherCountString.length() > 0) {
        int voucherCount = Integer.parseInt(voucherCountString);
        for (int i = 0; i < voucherCount; i++) {
        try {
            AbstractJcrVoucher voucher = (AbstractJcrVoucher) commerceService.getVoucher(cartStore.get("voucher" + i));

            if (voucher == null) {
                throw new Exception(); // handle all errors in catch block below
                }
        vouchers.add(voucher);
    } catch (Exception e) {
        log.error("Unable to load voucher from ClientContext: {}", cartStore.get("voucher" + i));
            }
    }
}

    String promotionCountString = cartStore.get("promotionCount");
    if (promotionCountString != null && promotionCountString.length() > 0) {
        int promotionCount = Integer.parseInt(promotionCountString);
        for (int i = 0; i < promotionCount; i++) {
                activePromotions.add(cartStore.get("promotion" + i));
            }
    }
    orderDetails =  ContextSessionPersistence.getStore(request, "ORDER",  CommerceConstants.COMMERCE_COOKIE_NAME);
 
    // assign an orderId if we don't already have one
    if (orderDetails.get(PN_ORDER_ID) == null) {
        orderDetails.put(PN_ORDER_ID, UUID.randomUUID().toString());
    }
 
    calcOrder();
    } catch (Exception e) {
 
 }
}
 
  if (cartEntries == 0) {
        
 // One might also chose to store cart data (for non-anonymous shoppers) in their home directory.
 
 try {
      Session userSession = resolver.adaptTo(Session.class);
      final UserProperties userProperties = request.adaptTo(UserProperties.class);
      
  if (userProperties != null && !UserPropertiesUtil.isAnonymous(userProperties)) {
    UserManager um = ((JackrabbitSession) userSession).getUserManager();
    Authorizable authorizable = um.getAuthorizable(userProperties.getAuthorizableID());
    String cartPath = authorizable.getPath() + "/commerce/cart";
    Node cartNode = JcrUtil.createPath(cartPath, "nt:unstructured", "nt:unstructured", userSession, true);

if (cartNode.hasProperty("items")) {
    Value[] items = cartNode.getProperty("items").getValues();
    for (Value item : items) {
                String itemParts[] = item.getString().split(";");
            try {
                    Product product = commerceService.getProduct(itemParts[0]);
                int quantity = Integer.parseInt(itemParts[1]);
                doAddCartEntry(product, quantity);
            } catch (Exception exc) {
                    // Probably due to a cart format change or a product change. While it's not nice to
                    // drop cart items, it's even worse to lock the shopper out.
                        }
            }
    }
     
  if (cartNode.hasProperty("details")) {
    Value[] details = cartNode.getProperty("details").getValues();
    for (Value detail : details) {
        String detailParts[] = detail.getString().split("=");
        if (detailParts.length >= 2) {
                orderDetails.put(detailParts[0], detailParts[1]);
        }
    }
}
calcCart();
    }
} catch (Exception exc) {
                        throw new CommerceException("U is "+u +" Failed to load cart from user's home: ", exc);
                }
        }
 
}

Note:

Once you are done adding these two methods to the TrainingCommerceSessionImpl class, rebuild the custom commerce service by using the Maven command shown earlier in this development environment. Product data is now saved in the Adobe JCR when it is selected from the Outdoors web application. 

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy