Announcement: You can find the guides for Commerce 7.5 and later on the new Elastic Path Documentation site. This Developer Center contains the guides for Commerce 6.13.0 through 7.4.1.Visit new site

This version of Elastic Path Commerce is no longer supported or maintained. To upgrade to the latest version, contact your Elastic Path representative.

Writing Your First Resource

Writing Your First Resource

Warning: Before you begin

Make sure your development environment has been properly set up as described in the Setting up your Developer Environment.

The goal of this tutorial is to learn how to implement a simple resource and include it in the Cortex web application. In this tutorial you will:
  • Create a resource that returns a terms and conditions message, which can be displayed to a shopper
  • Write a unit test that will help you troubleshoot basic problems with your resource
  • Add this resource to your Cortex web application and verify that everything is working correctly

In subsequent tutorials, you will enhance this resource by incorporating links and ensure that the terms and conditions for an order have been accepted by the shopper before completing a purchase.

When this tutorial is complete, you will be able to retrieve the terms and conditions message by sending the following request to Cortex with Studio or another REST client:

Request:
Content-type: application/json
Authorization: Bearer c7326d79-9273-4820-b45d-587f90d1dc9b
GET http://localhost/cortex/terms/12345=
Response:
{
  "self": {
    "type": "elasticpath.terms.terms-and-conditions",
    "uri": "/commerce-legacy/terms/12345=",
    "href": "http://localhost/cortex/terms/12345=",
    "max-age": 0
  },
  "links": [],
  "message": "An example terms and conditions string."
}

Defining the API

In this tutorial, you will create a resource family named terms that contains a single entity named terms-and-conditions. The terms-and-conditions entity will have an id field, which will store a unique identifier, and a message field, which will store the full text of the terms and conditions.

In this section, you will create a new definition project for the terms resource family using the resource-api-archetype. A definition project contains an XML description of a resource family and it's entities and is required to create a new Cortex resource.

  1. Open a command line and navigate to the extensions/cortex/resources-api directory.
  2. Run the following command to create a Cortex resource API definition project:
    mvn archetype:generate -DarchetypeArtifactId=resource-api-archetype -DarchetypeGroupId=com.elasticpath.rest.archetype -DarchetypeVersion=1.10.100.20140827092242
  3. Enter the following information when prompted:
    Table 1. Maven Parameters
    Variable Value
    groupId com.elasticpath.rest.example
    artifactId terms-and-conditions-api
    version Use the default value (1.0-SNAPSHOT)
    package Use the default value (com.elasticpath.rest.example)
  4. Change to the terms-and-conditions-api directory
  5. Rename example.xml, located under src/main/resources/META-INF/rest-definitions/, to terms-and-conditions.xml
  6. In terms-and-conditions.xml, replace the resource-family element with the following XML:
    src/main/resources/templates/rest-tutorials/terms-and-conditions-api/src/main/resources/META-INF/rest-definitions/terms-and-conditions.xml
    	<resource-family>
    		<name>terms</name>
    		<description><![CDATA[Example terms and conditions resource.]]></description>
    
    		<entity>
    			<name>terms-and-conditions</name>
    			<description><![CDATA[Defines the TermsAndConditions Entity.]]></description>
    
    			<property>
    				<name>terms-id</name>
    				<description><![CDATA[The identifier representing each individual entity.]]></description>
    				<internal/>
    				<string/>
    			</property>
    
    			<property>
    				<name>message</name>
    				<description><![CDATA[Defines the terms and conditions message.]]></description>
    				<string/>
    			</property>
    		</entity>
    	</resource-family>
    
                	

    The XML above defines a terms and conditions resource family, which contains a single entity named terms-and-conditions. The terms-and-conditions entity has an ID and a message. You'll see later that this entity definition is used to produce the initial source code you will use to implement of the terms and conditions resource.

Generating the API Jar

Now that you've written your API definition, you can use Maven to create a jar containing the classes required to finish implementing the terms and conditions resource.

  1. Open a command line and navigate to the terms-and-conditions-api project
  2. Run the following command to build the API definition:
    mvn clean install
    This maven build command will use the API generator maven plugin to generate the API source code that you will use to implement the resource.
  3. The generated source files are placed in the sub-directory:target/generated-sources/api-generator. Verify that you can access these files from your IDE. For example, try openingTermsAndConditionsEntity.java, as you will use this class later in the tutorial.

Implementing the Resource

You will now use a Maven archetype to generate a template resource that you will later modify into the terms and conditions resource. By default, the template resource simply returns a 200 READ OK response when a GET is performed on /. You will need to enhance the resource to handle GET requests for the/terms/<terms-id> URI pattern.

  1. Open a command line and navigate to theextensions/cortex/resources directory.
  2. Run the following command to create a Cortex resource project:
    mvn archetype:generate -DarchetypeArtifactId=rest-resource-extension-archetype -DarchetypeGroupId=com.elasticpath.rest.archetype -DarchetypeVersion=1.10.100.20140827092242
  3. Enter the following information when prompted:
    Table 2. Maven Parameters
    Variable Value
    groupId com.elasticpath.rest.example
    artifactId terms-and-conditions-resource
    version Use the default value (1.0-SNAPSHOT)
    package Use the default value (com.elasticpath.rest.example)
  4. Change to the terms-and-conditions-resource directory
  5. Open pom.xml and add a <dependency> on the terms-and-conditions-api artifact. This dependency will add the TermsAndConditionsEntity to the classpath of your resource.
    src/main/resources/templates/rest-tutorials/terms-and-conditions-simple/pom.xml
    	<dependencies>
    		<dependency>
    			<groupId>com.elasticpath.rest.example</groupId>
    			<artifactId>terms-and-conditions-api</artifactId>
    			<version>1.0-SNAPSHOT</version>
    		</dependency>
    	</dependencies>
    
                
  6. Rename the ExampleResourceOperatorImpl class, located under src/main/java/com/elasticpath/rest/example/rest/resource/example/impl, to TermsResourceOperatorImpl.
  7. In TermsResourceOperatorImpl, change the @Named annotation from @Named("exampleResourceOperator") to @Named("termsResourceOperator").
  8. Add a method to TermsResourceOperatorImpl that will process read requests on /terms/<terms-id>.

    You will use the TermsAndConditionsEntity class in this method. Use the Builder class found within TermsAndConditionsEntity to create a TermsAndConditionsEntity instance. Afterward, encapsulate the entity in a ResourceState so that the entity can be serialized and presented to the client as JSON.

    Once you've made these changes, verify that the TermsResourceOperatorImpl looks the same as the following code snippet.
    src/main/resources/templates/rest-tutorials/terms-and-conditions-simple/src/main/java/com/elasticpath/example/TermsResourceOperatorImpl.java
    import static com.elasticpath.rest.OperationResultFactory.createReadOK;
    import static com.elasticpath.rest.schema.SelfFactory.createSelf;
    
    import javax.inject.Named;
    import javax.inject.Singleton;
    
    import com.elasticpath.rest.Operation;
    import com.elasticpath.rest.OperationResult;
    import com.elasticpath.rest.ResourceOperation;
    import com.elasticpath.rest.definition.terms.TermsAndConditionsEntity;
    import com.elasticpath.rest.resource.dispatch.operator.ResourceOperator;
    import com.elasticpath.rest.resource.dispatch.operator.annotation.OperationType;
    import com.elasticpath.rest.resource.dispatch.operator.annotation.Path;
    import com.elasticpath.rest.resource.dispatch.operator.annotation.ResourceId;
    import com.elasticpath.rest.resource.dispatch.operator.annotation.ResourceName;
    import com.elasticpath.rest.schema.ResourceState;
    
    /**
     * Handles REST operations on the terms resource.
     */
    @Singleton
    @Named("termsResourceOperator")
    @Path(ResourceName.PATH_PART)
    public class TermsResourceOperatorImpl implements ResourceOperator {
    
    	/**
    	 * Handles the READ operations.
    	 *
    	 * @param termsId   item id
    	 * @param operation the Resource Operation.
    	 * @return the result
    	 */
    	@Path({ResourceId.PATH_PART})
    	@OperationType(Operation.READ)
    	public OperationResult processRead(
    			@ResourceId
    			final String termsId,
    			final ResourceOperation operation) {
    
    		String message = "An example terms and conditions string.";
    
    		TermsAndConditionsEntity termsEntity = TermsAndConditionsEntity.Builder.builder()
    				.withTermsId(termsId)
    				.withMessage(message)
    				.build();
    		ResourceState<TermsAndConditionsEntity> termsState = ResourceState.Builder
    				.create(termsEntity)
    				.withSelf(createSelf(operation.getUri()))
    				.build();
    
    		return createReadOK(termsState, operation);
    	}
    }
    			
  9. Rename example-blueprint.xml and applicationContext-example-resource.xml, located under src/main/resources/OSGI-INF/blueprint/, to terms-blueprint.xml and applicationContext-terms-resource.xml respectively.
  10. In example-blueprint.xml, change <service ref="exampleResourceOperator" auto-export="interfaces"/commerce-legacy/> to <service ref="termsResourceOperator" auto-export="interfaces"/commerce-legacy/> so that the TermsResourceOperator implementation is exported as an OSGI service.
  11. In applicationContext-terms-resource.xml, change the resource server name from "example" to "terms".
    The TermsResourceOperatorImpl class uses the @Path(ResourceName.PATH_PART) annotation to to route requests of the form /terms/<sub-uri> to the termsResourceOperator.
    					src/main/resources/templates/rest-tutorials/terms-and-conditions-simple/src/main/resources/OSGI-INF/blueprint/applicationContext-terms-resource.xml
    	<bean name="resourceServerName" class="java.lang.String">
    		<constructor-arg value="terms"/commerce-legacy/>
    	</bean>
    
    				
  12. In applicationContext-terms-resource.xml, change the persistent-id in the overrideRolePermissionsProvider bean to termsSimpleRolePermission.

    Permissions are used to control access to certains paths in a resource based on user roles and are identified by a persistent-id. Permissions for a resource can also be changed via the OSGI web console.

    					src/main/resources/templates/rest-tutorials/terms-and-conditions-simple/src/main/resources/OSGI-INF/blueprint/applicationContext-terms-resource.xml
    	<bean id="overrideRolePermissionsProvider" class="com.elasticpath.rest.authorization.rolepermissions.ConfigAdminRolePermissionsProvider">
    		<constructor-arg ref="resourceServerName"/commerce-legacy/>
    		<constructor-arg ref="permissionsTransformer"/commerce-legacy/>
    		<compendium:managed-properties persistent-id="termsRolePermissions" update-method="updateProperties"/commerce-legacy/>
    	</bean>
    
    				
    Also in the applicationContext-terms-resource.xml, you'll see component scanning for spring beans in the bundle. The archetype will generate component scans for the project package entered during generation and com.elasticpath.rest for EP specific beans.
    					src/main/resources/templates/rest-tutorials/terms-and-conditions-simple/src/main/resources/OSGI-INF/blueprint/applicationContext-terms-resource.xml
    	<context:component-scan base-package="com.elasticpath.rest" scope-resolver="org.springframework.context.annotation.Jsr330ScopeMetadataResolver"/commerce-legacy/>
    	<context:component-scan base-package="com.elasticpath.rest.example" scope-resolver="org.springframework.context.annotation.Jsr330ScopeMetadataResolver"/commerce-legacy/>
    
             		
  13. Write the unit test for this resource.

    You will test that requests to /terms/<terms-id> route to the termsResourceOperator and verify the TermsAndConditionsEntity ID and message.

    Start by renaming ExampleResourceOperatorImplTest, located under src/test/java/com/elasticpath/rest/example/rest/resource/example/impl, to TermsResourceOperatorImplTest. The existing test for the example resource uses a different URI form and does not expect a ResourceState in the response so you should update the test to use the correct URI and expect a result of type ResourceState<TermsAndConditionsEntity>.

    src/main/resources/templates/rest-tutorials/terms-and-conditions-simple/src/test/java/com/elasticpath/example/TermsResourceOperatorImplTest.java
    import static org.junit.Assert.assertEquals;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.runners.MockitoJUnitRunner;
    
    import com.elasticpath.rest.OperationResult;
    import com.elasticpath.rest.ResourceOperation;
    import com.elasticpath.rest.schema.ResourceState;
    import com.elasticpath.rest.definition.terms.TermsAndConditionsEntity;
    
    /**
     * Tests URI-related annotations on {@link com.elasticpath.rest.example.rest.resource.example.impl.TermsResourceOperatorImpl}.
     */
    @RunWith(MockitoJUnitRunner.class)
    @SuppressWarnings("unchecked")
    public class TermsResourceOperatorImplTest {
    
    	@Mock
    	private ResourceOperation resourceOperation;
    
    	@InjectMocks
    	private TermsResourceOperatorImpl resourceOperator;
    
    	private static final String TERMS_ID = "termsId";
    
    	@Test
    	public void shouldBuildTermsStateWithId() {
    
    		OperationResult result = resourceOperator.processRead(TERMS_ID, resourceOperation);
    
    		ResourceState<TermsAndConditionsEntity> termsState = (ResourceState<TermsAndConditionsEntity>) result.getRepresentation();
    		assertEquals(TERMS_ID, termsState.getEntity()
    				.getTermsId());
    	}
    
    	@Test
    	public void shouldBuildTermsStateWithMessage() {
    		String message = "An example terms and conditions string.";
    
    		OperationResult result = resourceOperator.processRead(TERMS_ID, resourceOperation);
    
    		ResourceState<TermsAndConditionsEntity> termsState = (ResourceState<TermsAndConditionsEntity>) result.getRepresentation();
    		assertEquals(message, termsState.getEntity()
    				.getMessage());
    	}
    }
  14. Run the following command to build the terms and conditions resource JAR file and execute the unit tests:
    mvn clean install
    Verify that the build was successful.

Adding the Resource to the Cortex Web Application

In this section you will learn how to include a new resource and its definition in your Cortex web application.

  1. Open a command line and navigate to the extensions/cortex directory.
  2. Look-up the bundle symbolic name for the terms-and-conditions-resource project, found in the OSGi manifest located in terms-and-conditions-resource/target/classes/META-INF/MANIFEST.MF.
  3. Add the symbolic name for the terms-and-conditions-resource bundle to the extension resource configuration's blueprint configuration, located in cortex/ext-cortex-resource-configuration/src/main/resources/OSGI-INF/blueprint/resource-configuration-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">
    
    	<!-- export services -->
    	<service id="requiredResourceServerBundles" interface="java.util.List">
    		<bean class="java.util.ArrayList">
    			<argument>
    				<array value-type="java.lang.String">
    					...		
    					<value>com.elasticpath.rest.example.terms-and-conditions-resource</value> 
    					...
    				</array>
    			</argument>
    		</bean>
    	</service>
    </blueprint>		
  4. Change to the root directory of the resource configuration project and execute the following command to rebuild it with your latest changes:
    mvn clean install
    Verify that the build was successful.
  5. Open the Cortex web application POM file: ext-cortex-webapp/pom.xml.
  6. In the Cortex web application POM file, add a <dependency> on the terms-and-conditions-api artifact and the terms-and-conditions-resource artifact.
    	<dependency>
    		<groupId>com.elasticpath.rest.example</groupId>
    		<artifactId>terms-and-conditions-api</artifactId>
    		<version>1.0-SNAPSHOT</version>
    		<scope>provided</scope>
    	</dependency>
    	<dependency>
    		<groupId>com.elasticpath.rest.example</groupId>
    		<artifactId>terms-and-conditions-resource</artifactId>
    		<version>1.0-SNAPSHOT</version>
    		<scope>provided</scope>
    	</dependency>
  7. In the Cortex web application POM file, go to the configuration section of the maven-dependency-plugin build plugin and add an <includeGroupIds> section. In the <includeGroupIds>, section, add an entry for the groupId of the terms-and-conditions-api and terms-and-conditions-resource artifacts.
    	<includeGroupIds>
    		com.elasticpath.rest.example
    	</includeGroupIds>
  8. Change to the Cortex web application's directory and execute the following command to rebuild it with your latest changes and to pull in the project's dependencies:
    mvn clean install -Pwith-oauth2-resource
    Verify that the build was successful.

Testing the Resource

At this point you've created all the necessary components for the terms and conditions resource, and the final result can be verified.

  1. Open a command line and navigate to the directory containing your Cortex web application
  2. Run the following command to start the Cortex web application:
    mvn tomcat7:run-war
  3. Using Cortex Studio, retrieve the terms by sending the following request:
    Request:
    Content-type: application/json
    Authorization: Bearer c7326d79-9273-4820-b45d-587f90d1dc9b
    GET http://localhost/cortex/terms/12345=
    Response:
    {
      "self": {
        "type": "elasticpath.terms.terms-and-conditions",
        "uri": "/commerce-legacy/terms/12345=",
        "href": "http://localhost/cortex/terms/12345=",
        "max-age": 0
      },
      "links": [],
      "message": "An example terms and conditions string."
    }
    Congratulations, you’ve just built your first Contex resource. Next, in Adding Links to Your Resource, you will learn how to link the orders resource to your resource so a client can view the terms and conditions from an order.