Creating a Resource Integration
Creating a Resource Integration
This class explains how to create a resource integration and swap it with another resource integration.
Suppose you are tasked with creating a new assets resource integration to communicate with your company's backend system—a backend system that is not the Elastic Path Commerce Engine. You'll want to do this without having to create a resource integration from scratch, perform extensive customizations in Cortex, read tons of technical documents, and so on. This lesson takes you through, step by step, how to quickly create an assets resource integration project, code it, and then swap it with the out-of-the-box assets integration resource.
What you should already know:
- Cortex Architecture: Architecture Overview.
- The assetsresource and its methods
- Your backend system (Our tutorial code does not talk to a backend system. You can infer from this tutorial how to code a resource integration to talk to your backend system).
- DTOs and Interface Strategies (Don't worry, you don't need to know these inside out, just be aware of how they work).
- Cortex Web Application POMs
Generate a New Resource Integration Project
Creating a new Resource Integration project is the first lesson in this class. Elastic Path provides a Maven Archetype to simplify the task to create a new resource integration. Archetypes are project templates that contain some of the standard components you'll need for your project.
- Open a command prompt and navigate to your Extensions Directory.
- Run the following command to create a Cortex Resource Integration project:
mvn archetype:generate -DarchetypeArtifactId=ep-cortex-resource-integration-archetype -DarchetypeGroupId=com.elasticpath.cortex.dce -DarchetypeVersion=<your-artifact-version>
Tip: Stack Trace TipYou may see a stack trace preceded by a warning similar to the following:
[WARNING] Error reading archetype catalog http://repo1.maven.org/maven2 org.apache.maven.wagon.TransferFailedException: Error transferring file: Connection timed out: connect
This can occur if you are operating Maven in online mode. It can be safely ignored.
- Enter the following information when prompted:
- Change to the newly created project directory and execute the following command to build the resource integration JAR file:
mvn install
Import the New Resource Integration into Eclipse
Next, you need to import your Resource Integration project into Eclipse as a Maven project.
Add the Resource Integration's Dependency
Once our base project is set up, we need to add the appropriate rest resource dependency to it. Because we are building an assets resource integration, we'll add the assets rest resource dependency. If you were building a resource integration for another rest resource, you would add a different dependency. For example, if you were creating a carts resource integration, you would add the carts rest resource as a dependency. We recommend having one rest integration per rest resource, as this will simplify development and keep in line with Java's single responsibility principle.
Why do you need this dependency?
To create our assets resource integration, we need to implement the assets rest resource's lookup strategy interface. Therefore, we need to add the assets resource as a dependency to our project so we can implement the interface.
- In Eclipse, open your example-assets-resource-integration project's pom.xml.
- Add the following code beneath the <description> element:
<description>Elastic Path REST - ResourceName Resource Commerce Engine Integration</description> ... <dependencies> <dependency> <groupId>com.elasticpath.rest.resource</groupId> <artifactId>ep-resource-assets</artifactId> <version>0.1.0-SNAPSHOT</version> </dependency> </dependencies>
- Save the pom.xml.
- Right-click the example-assets-resource-integration project and select Maven -> Update Project Configuration, to update your project's dependencies and rebuild.
For a list of rest resource dependencies, see your cortext-dce\webapp-parent\pom.xml.
Implement the Lookup Strategy Interface
Now that we've created the project and added the appropriate dependency, we're going to implement the assets' lookup strategy interface. But first, let's get an overview of the Integration Layer so we can better understand how and why we are coding this interface.
How does the Integration Layer work?
Your backend system could be anything, Elastic Path's Commerce Engine, Aria's Subscription Billing Service, and so on. The Integration Layer, the layer where you are building your resource integration, is designed so you can customize it to talk to any backend system. Take a look at the Architecture Call Stack and notice how the resource integration returns a DTO to the rest resource. As long as the resource integration returns the proper DTO for the rest resource to consume, the resource integration could be communicating with any backend system to construct that DTO, not just the Elastic Path Commerce Engine.
What do I need to code?
Rest resources have lookup and writer strategy interfaces that define the data they need to complete an operation. What you are coding in your resource integration is the rest resource's interface. For this tutorial, the code you need to implement is shown in the pseudo UML diagram below. As you can see, you'll create a class calledAssetLookupStrategyImpl that implements the AssetLookupStrategy. The AssetsLookupStrategy interface has two methods that your AssetLookupStrategyImpl class will implement. We will explain these methods later in this lesson.
If you are coding something other than an assets resource integration, you'll need to implement different interface methods. These are not covered in this tutorial. To code these methods, take a look at the Rest Resources Java Docs, particularly the rest resource's interface javadoc, to get an understanding of what you'll need to code.
Create the LookupStrategyImpl Class
Create the look up strategy's implementation class. In this tutorial, we are creating an assets resource integration, so you'll need to create a class that implements the assets rest resource interface. If you were creating a resource integration for another rest resource, you would create a class that implements the lookup and writer strategies for that resource. For example, if you were coding a lookup implementation for the cart resource, you would implement the CartLookupStrategy and code it's methods: getCart() and isCartOwnedByUser().
- Create a new class with the following details:
- Source Folder: example-assets-resource-integration/src/main/java
- Package: com.elasticpath.tutorials
- Name: AssetLookupStrategyImpl
-
Interface: com.elasticpath.rest.resource.assets.integration.AssetLookupStrategy
Code the Strategy Implementation
The rest resource's lookup and writer strategy interfaces define what you'll need to code in your resource integration. Our tutorial uses the assets rest resource as an example, which has a Lookup strategy with two methods. Below, we explain how to code these two methods; however, these are just examples. We don't actually create a backend system to talk to, we hard-code the asset's details in our examples. If you are coding a different resource integration, you need to implement different methods. Use the descriptions below as a guide for how to code the methods in your resource integration.
In Cortex, we use Javax.Inject to name our Java Beans, so you'll need to declare your bean's name above your class declaration:
@Named("assetLookupStrategy") ... public class AssetLookupStrategyImpl implements AssetLookupStrategy {
The first method we'll implement is public ExecutionResult<AssetDto> getAsset(String scope, String decodedAssetId). This method's purpose is to find the asset identified in the decodedAssetId parameter and return it as an AssetDto. On Cortex client application side, this method satisfies the GET - an asset HTTP request.
- String decodedItemId - Contains the assets's ID This could be a UID, GUID, or something else your backend system understands. Notice the method uses the asset's decoded ID Once the ID goes up the Cortex stack, it gets encoded so client applications don't see real ids. For more on this, see Id Encoding.
- String scope - Identifies the store's domain (not used in this tutorial).
The following snippet creates an AssetDto, defines the DTO's values, sets the operation' status, and returns an ExecutionResult to the assets rest resource.
@Override public ExecutionResult<AssetDto> getAsset(String scope, String decodedAssetId) { AssetDto assetDto = ResourceTypeFactory.createResourceEntity(AssetDto.class); assetDto.setContentLocation("http://mobee.elasticpath.com/avatar.jpg"); assetDto.setDisplayName("Avatar"); assetDto.setName("A12345"); assetDto.setRelativePath("avatar.jpg"); ExecutionResult<AssetDto> result = ExecutionResultFactory.createReadOK(assetDto); return result; }
Notice how the code flows here (most lookup strategy implementations follow this structure):
1. Create a DTO - We use the ResourceTypeFactory to instantiate the AssetDTO. This factory design pattern makes it easier to transform the DTO into a representation later on when the request percolates up the Cortex stack. You don't need to worry about the representation for what you are coding here in the integration layer. Just be aware that your DTO eventually gets converted into a representation, which then surfaces back to the client application.
AssetDto assetDto = ResourceTypeFactory.createResourceEntity(AssetDto.class);
2. Get the asset data - We don't actually get the asset data from a backend system in this sample, we hard code it here. However, if you were coding your integration resource to talk to your backend system, you would use the decodedAssetId to retrieve the asset from your backend system and then convert whatever is returned by your backend system into an assetDto. The tutorial's source code, which you can download from the table of contents, uses a service class to retrieve the asset details from a CSV file. Keep this in mind when you are coding your resource integration because you may need to use a service like when communicating with your backend system.
3. Set the Operation's Status - Set the operation's status using the ExecutionResultFactory to indicate the operation succeeded or failed. In our tutorial, we set it to ExecutionResultFactory.createReadOK(). If we couldn't find the asset, we would set the status to ExecutionResultFactory.createNotFound(). See the class for the available statuses.
ExecutionResult<AssetDto> result = ExecutionResultFactory.createReadOK(assetDto);
4. Return the Result - Lastly, return the AssetDto in an ExecutionResult. We wrap the assetDto in an ExecutionResult because we do not throw exceptions in the Cortex. Instead of throwing an exception, we return an ExecutionResult with a ResourceStatus to indicate the operation's success or failure. Indicating the operation's status this way, instead of throwing an exception with a convoluted stack trace, allows the Cortex to fail gracefully.
The second method to implement in this lesson is public ExecutionResult<Collection<String>> getItemDefinitionAssetIds(String scope, String decodedItemId). This method's purpose is to find all the assets related to the decodedItemId and return them in a collection of strings. On Cortex client application side, this method satisfies the GET - an item's assets HTTP Request.
- String decodedItemId - Contains the item's ID This could be a UID, GUID, or something else your backend system understands. Notice the method uses the item's decoded ID. Once the ID goes up the Cortex stack, it gets encoded so client applications don't see real IDs. For more information, see ID Encoding.
- String scope - Identifies the store's domain (not used in this tutorial).
@Override public ExecutionResult<Collection<String>> getItemDefinitionAssetIds( String scope, String decodedItemId) { ExecutionResult<Collection<String>> result; Collection<String> assetIds = new ArrayList<String>(); assetIds.add("1"); assetIds.add("2"); assetIds.add("3"); result = ExecutionResultFactory.createReadOK(assetIds); return result; }
Here's how the code flows in the snippet:
1. Get the list of assets - We don't actually get the list of asset I.Ds from a backend system in this sample, we hard-code the IDs and pass them back. If you were coding your integration resource talk to your backend system, you would use the decodedItemId String to retrieve the list from your backend system and add the results to the assetIds String collection.
2. Set the Operation's Status - Set the operation's status using the ExecutionResultFactory. In our tutorial , we are setting it to ExecutionResultFactory.createReadOK(). If the operation failed or something else happened, you would set a different status here.
ExecutionResult<AssetDto> result = ExecutionResultFactory.createReadOK(assetIds);
3. Return the Result - Lastly, return the collection in an ExecutionResult. We wrap the collection in an ExecutionResult because we do not throw exceptions in the Cortex. Instead of throwing an exception, we return an ExecutionResult with a ResourceStatus to indicate the operation's success or failure. Indicating the operation's status this way, instead of throwing an exception with a convoluted stack trace, allows the Cortex to fail gracefully.
Export your Code for OSGi to Consume
You need to export your code as a service so OSGi can consume it.
Why do we need OSGi to consume our code?
Inside theCortex's Web App, Cortex runs in an OSGi framework. We need to export our code in a way OSGi can be aware of it and consume it. To export your code for OSGi:
Declare the following in your project's OSGI-INF/blueprint/example-integration-blueprint.xml:
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> ... <service ref="assetLookupStrategy" interface="com.elasticpath.rest.resource.assets.integration.AssetLookupStrategy" /> </blueprint>
This exports the bean named assetLookupStrategy along with interface com.elasticpath.rest.resource.assets.integration.AssetLookupStrategy as a service OSGi can consume.
Next, add the following declaration to your project's spring/applicationContext-example-integration.xml:
<beans> ... <context:component-scan base-package="com.elasticpath.tutorials" scope-resolver="org.springframework.context.annotation.Jsr330ScopeMetadataResolver"/commerce-legacy/> </beans>
This declaration tells Spring to scan your resource integration for annotation-based injections. For more information on Spring classpath scanning, see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/beans.html#beans-classpath-scanning.
After you've made these changes, you'll build your resource integration project and install it in your local Maven repository. From a command line, change directories to the root of your integration project and execute:
mvn install
Override the Old Resource Integration with the New Resource Integration
This lesson teaches you how to override the old resource integration with the new one you created in this tutorial. To make this change, you'll need to shut down your Cortex web application, modify the web application's POM file, and then restart the web application.
In this lesson, you are modifying the following XML elements in the Cortex web application's POM file.
- <dependency> - Tells Maven to include your integration project's jar as a dependency during build.
- <execution> - Execution groups configure the goals for the plugin. In this tutorial, we are going to configure the Cortex API Web App POM to override the out-of-the-box resource asset's <execution> group. For more information on POMs, see Cortex Web Application POMs.
To override the out-of-the-box assets resource integration:
- With a text editor, open your Cortex web application's POM file.
- Add your example-assets-resource-integration as a <dependency> and configure the :copy-assets-resource-and-integration-OSGi-bundles <execution> group following the example below.
... <dependencies> <!-- Add Extension resources or Integration modules as maven dependencies here. --> <dependency> <groupId>com.elasticpath.tutorials</groupId> <artifactId>example-assets-resource-integration</artifactId> <version>${project.version}</version> <scope>provided</scope> </dependency> </dependencies> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <!-- Override Assets CE Integration and use an example integration instead. --> <execution> <id>copy-assets-resource-and-integration-OSGi-bundles</id> <phase>prepare-package</phase> <goals> <goal>copy</goal> </goals> <configuration> <outputDirectory>${war-bundle-directory}</outputDirectory> <artifactItems> <artifactItem> <groupId>com.elasticpath.rest.resource</groupId> <artifactId>ep-resource-assets</artifactId> </artifactItem> <artifactItem> <groupId>com.elasticpath.tutorials</groupId> <artifactId>example-assets-resource-integration</artifactId> </artifactItem> </artifactItems> </configuration> </execution>
- From a command line, change directories to your Cortex Web Application folder and execute:
mvn clean install -DskipAllTests
Run and Test the New Resource Integration
Out of the box, the Avatar product doesn't have an asset. Now that we've coded the new assets resource integration to return an asset for Avatar, you should see the following JSON objects when you get Avatar's assets.
The search we perform in the second step is only necessary if you haven't started up your Cortex web application before. We need Search to index our product item IDs, or we will be unable to retrieve products.
To run and test the new resource integration:
- Start up your Cortex web application.
- Using a REST client, log into the demo store by following the instructions in Authenticate a Customer and using these credentials:
- Username: oliver.harris@elasticpath.com
- Password: password
- Using your REST client, create a search by sending the following request:
- Now make the following call to retrieve the list of assets for the avatar product.Response
Table 3. HTTP Request Method URL Header GET https://localhost:8443/cortex/assets/itemdefinitions/mobee/mvsdazbtheytgnjyhe3tizrygq2wcmzrmnrdsnrvmq2dgyrxhe2dkn3cmy2tindd Content-type: Application/json { "self": { "type": "elasticpath.collections.links", "uri": "/commerce-legacy/assets/itemdefinitions/mobee/mvsdazbtheytgnjyhe3tizrygq2wcmzrmnrdsnrvmq2dgyrxhe2dkn3cmy2tindd", "href": "http://localhost:8080/cortex/assets/itemdefinitions/mobee/mvsdazbtheytgnjyhe3tizrygq2wcmzrmnrdsnrvmq2dgyrxhe2dkn3cmy2tindd", "max-age": 600 }, "links": [ { "type": "elasticpath.assets.asset", "rel": "element", "href": "http://localhost:8080/cortex/assets/mobee/ge=", "uri": "/commerce-legacy/assets/mobee/ge=" }, { "type": "elasticpath.assets.asset", "rel": "element", "href": "http://localhost:8080/cortex/assets/mobee/gi=", "uri": "/commerce-legacy/assets/mobee/gi=" }, { "type": "elasticpath.assets.asset", "rel": "element", "href": "http://localhost:8080/cortex/assets/mobee/gm=", "uri": "/commerce-legacy/assets/mobee/gm=" }, { "type": "elasticpath.itemdefinitions.item-definition", "rel": "definition", "rev": "assets", "href": "http://localhost:8080/cortex/itemdefinitions/mobee/mvsdazbtheytgnjyhe3tizrygq2wcmzrmnrdsnrvmq2dgyrxhe2dkn3cmy2tindd", "uri": "/commerce-legacy/itemdefinitions/mobee/mvsdazbtheytgnjyhe3tizrygq2wcmzrmnrdsnrvmq2dgyrxhe2dkn3cmy2tindd" } ] }
- Now make the following call to retrieve the asset for the avatar product.Response
Table 4. HTTP Request Method URL Header GET https://localhost:8443/cortex/assets/mobee/ge= Content-type: Application/json { "self": { "type": "elasticpath.assets.asset", "uri": "/commerce-legacy/assets/mobee/ge=", "href": "http://localhost:8080/cortex/assets/mobee/ge=", "max-age": 600 }, "links": [], "content-location": "http://mobee.elasticpath.com/avatar.jpg", "display-name": "avatar", "name": "A12345", "relative-location": "avatar.jpg" }
Troubleshooting
A number of issues can occur when running this tutorial. The list below shows some of the more common errors and suggestions for solving them.
Webapp Startup Error
Cortex Web App fails to start up with the following error:
RELOS: 6392 [rThread-25] ERROR pendencyWaiterApplicationContextExecutor - Unable to create application context for [com.elasticpath.tutorials.example-assets-resource-integration], unsatisfied dependencies: none org.springframework.beans.factory.BeanCreationException: Error creating bean with name '.org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean#0': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Cannot locate bean named 'assetLookupStrategy' inside the running bean factory. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1422) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:518) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:455) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:293) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:290) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:192) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:567) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895) ~[spring-context-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.eclipse.gemini.blueprint.context.support.AbstractDelegatedExecutionApplicationContext.access$1600(AbstractDelegatedExecutionApplicationContext.java:60) ~[na:na] at org.eclipse.gemini.blueprint.context.support.AbstractDelegatedExecutionApplicationContext$4.run(AbstractDelegatedExecutionApplicationContext.java:325) ~[na:na] at org.eclipse.gemini.blueprint.util.internal.PrivilegedUtils.executeWithCustomTCCL(PrivilegedUtils.java:85) ~[na:na] at org.eclipse.gemini.blueprint.context.support.AbstractDelegatedExecutionApplicationContext.completeRefresh(AbstractDelegatedExecutionApplicationContext.java:290) ~[na:na] at org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.DependencyWaiterApplicationContextExecutor$CompleteRefreshTask.run(DependencyWaiterApplicationContextExecutor.java:137) ~[na:na] at java.lang.Thread.run(Thread.java:662) [na:1.6.0_30] Caused by: java.lang.IllegalArgumentException: Cannot locate bean named 'assetLookupStrategy' inside the running bean factory. at org.springframework.util.Assert.isTrue(Assert.java:65) ~[spring-core-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.eclipse.gemini.blueprint.service.exporter.support.OsgiServiceFactoryBean.afterPropertiesSet(OsgiServiceFactoryBean.java:190) ~[na:na] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1479) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1419) ~[spring-beans-3.0.7.RELEASE.jar:3.0.7.RELEASE] ... 14 common frames omitted
This error could be caused by one of the following:
- The base-package in the applicationContext-example-integration.xml isn't set properly for OSGi. For more information, see Export your Code for OSGi to Consume.
- The bean annotation @Named("assetLookupStrategy") may not be defined for your AssetLookupStrategyImpl class.