Technical Blog

13 Posts authored by: Paul Monk

Just wanted to draw attention to a new document we've made available on our documentation site: the Elastic Path 6.2 database guide.

 

Follow the link and you'll see the complete list of tables in the 6.2 EP db schema. Click any table to see column descriptions and a graphical representation of the table and its foreign key relations. Click tables in the graphic to traverse the relations. Each table also includes a link to the javadoc of the corresponding domain class. Hours of fun for the whole family! Hopefully this will be more manageable than the giant diagram we provided in previous releases.

 

db-guide.png

0 Comments Permalink

We often get asked whether Elastic Path can be integrated with a CMS. We always answer, "yes, of course!" but I suspect at least a few of us wonder how much effort is really involved. I know I did. Well, now I can say that the effort is minimal (no coding required!), at least if you're using Alfresco. Alfresco is a production-ready, open source content management system, and in this post, we'll see just how easy it is to install Alfresco, import your assets, and set up Elastic Path to pull assets from Alfresco.

 

First, you'll need to get Alfresco on your machine. Alfresco comes in two flavors:

  • Enterprise Edition, which includes full commercial support
  • Community Edition, which is unsupported and intended for developers who just want to play around with it.

 

Download the Community Edition (at the time of writing, the current version of the Community Edition is 3.2). Note that although the Community Edition is unsupported, you can still ask questions in the Alfresco forums. The Alfresco wiki is also an excellent source information.

 

When you run the Alfresco setup, it will ask you to specify the setup type. To keep things simple, choose typical. This installs all the required components including Java, Tomcat, and MySQL. If you already have Tomcat installed, you may need to change the Server and Connector ports to avoid conflicts. You can do this after the installation is complete by editing tomcat\conf\server.xml under your Alfresco installation directory.

 

After it's installed and running, go to the login screen (http://localhost/alfresco) and enter the administrator username and password (admin/admin).

 

After logging in, go to the Company Home area and create a space to store the assets for your Elastic Path deployment. Under Browse Spaces, click Sites. (You can choose Web Projects instead if you prefer.)

alfresco_browse_spaces.png

Click Create->Create Space.

alfresco_create_space.png

Enter a name and title for the space and click Create Space. Your space now appears under Browse Spaces.

alfresco_new_space.png

Now you need to add your Elastic Path assets to your space. The fastest way to do this is to zip your entire assets directory and import the zip file into the space.

 

First click the space name. Then click More Actions->Import.

alfresco_import.png

Browse to the assets zip file, select it, and click OK. After the zip file is imported, the contents are extracted automatically and the directory structure is recreated within the space. It might take a few minutes to complete, so the folder may not appear in Alfresco right away. (You can delete the zip file after the assets folder is created.)

 

Now we need to get the SMB/CIFS path that we can use in Windows Explorer to access the assets. Click the View Details icon under the assets folder.

alfresco_view_details.png

If you're using Internet Explorer, click View in CIFS. (Because it's using a UNC path, it won't work in other browsers.)

alfresco_view_in_cifs.png

In the Community Edition, all the supported repository access protocols are enabled by default, including FTP and SMB/CIFS. Unless you've done something strange, you should see a Windows Explorer window showing the contents of the assets folder in Alfresco. (If you're not using Internet Explorer, copy the CIFS link from your browser and paste it into Windows Explorer.)

alfresco_explorer.png

Take note of the path in the address box. It should start with the Alfresco server name followed by an "a". That "a" is automatically added by Alfresco, but it can be configured by changing the cifs.serverName property in tomcat\webapps\alfresco\WEB-INF\classes\alfresco\subsystems\fileServers\default\file-servers.properties , located under your Alfresco installation directory. For more information, see http://wiki.alfresco.com/wiki/File_Server_Subsystem.

 

The next step is to map that path to a Windows drive letter. (The COMMERCE/SYSTEM/assetLocation setting in Elastic Path must be set to a valid file system path; UNC paths are not supported.) In Windows Explorer, click Tools->Map Network Drive. Select an unused drive letter and enter the path in the folder field. Make sure Reconnect at logon is selected. Then click Connect using a different user name. Enter the Alfresco administrator user name and password. Click Finish.

alfresco_map_net_drive.png

The final step is to log on to the Commerce Manager client and configure the COMMERCE/SYSTEM/assetLocation setting to point to the assets folder on the mapped drive. After you do this, restart your app server. Your storefront will now be pulling its theme files, images, etc. from Alfresco. You'll also need to point the different VFS settings in Elastic Path to use Alfresco's FTP so that the CM client's asset manager is able to retrieve the assets from Alfresco as well.

 

Once your assets are in Alfresco, you'll be able to take advantage of its sophisticated content approval workflow and collaboration features.

11 Comments Permalink

Give them an inch, they take a mile. Any time you give users the ability to enter free form text, there's a potential for something to go wrong. Thankfully, we've already got a variety of pieces in Elastic Path Commerce to validate user input. As of 6.1.2., the Tagging Framework includes a validation feature, so when users are creating Dynamic Content Delivery rules, the condition builder dialog can provide instant feedback and prevent them from submitting invalid values. To see this in action, fire up the CM client, go to the Store Marketing activity, and edit a Dynamic Content Delivery. When you get to the SHOPPERS screen (Step 4 of 6), add the "have a cart subtotal" tag. The tag requires a numeric value. If you try to enter alphabetical or special characters, the text field turns red. If you click the red x next to the field, you'll see an explanation of the error.

tag-validation.png

 

If you're creating your own tag definitions, you'll want to learn how to incorporate tag validation. Since I like to learn by doing, I gave it a try, and in this article, I'll walk you through the steps I followed to define a tag, capture values for it, and enable validation on it.

 

Before diving in, I should provide a very brief overview of how tag validation works. When you create a tag definition, you associate it with a tag value type. You can think of a tag value type as high-level data type. For example, in 6.1.2, there are over a dozen out-of-the-box tag value types, including time, age, gender, text, money, city, product category. Each tag value type has its own set of validation constraints, which determine whether a given value qualifies as a valid instance of that type. For example, the default validation constraints for the age tag value type require the value to be a number between 0 and 120. The validation constraints are used most notably in the Dynamic Content Delivery wizard, to ensure that users enter sensible values when creating SHOPPER segment conditions. Behind the scenes, we use Spring Validation (or Valang) to interpret the validation constraints. We'll look a bit more at this later on, since this is probably the most interesting part of it.

 

The first thing I needed to do was to decide what kind of tag I wanted to create. For the purpose of testing validation, I figured a phone number tag might be a good one to try. I created a tag value type in the database (identified by the GUID phone_number) and specified the operators it needed to support.

insert into TTAGVALUETYPE(uidpk, guid, java_type)
     values(17, 'phone_number', 'java.lang.String');

insert into TTAGVALUETYPEOPERATOR(tagvaluetype_guid, tagoperator_guid)
     values('phone_number', 'includes'),
          ('phone_number', 'greaterThan'),
         ('phone_number', 'lessThan'),
          ('phone_number', 'notIncludes');

Then, I created a basic validation constraint.

insert into TVALIDATIONCONSTRAINTS(uidpk, object_uid, error_message_key, validation_constraint, type)
     values(17, 17,     'validationTagPhoneNumberError', 
     '{ condition : isValidConditionType(this) is true AND match(\'[0-9]\\\\d{2}-\\\\d{3}-\\\\d{4}\', tagValue) is true : \'value must be a valid phone number (only numbers)\' }',
     'TagValueType');

Okay, that might look a bit scary. The object_uid and type columns together identify the tag value type (the uidpk in the TAGVALUETYPE table). The validation_constraint column contains the interesting part. The format of the validation constraint is as follows:

{ condition: <constraint> : <error message> }

The first part of the constraint, isValidConditionType(this), checks to make sure the value is not null, that its Java type matches the tag value type's Java type, and a few other checks. As a rule, you should always include this in your constraints.

 

The second part is where it gets interesting. If you remove some of the escape sequences, you're left with a condition that includes a simple regular expression to match North American telephone numbers.

match('[0-9]\\d{2}-\\d{3}-\\d{4}', tagValue) is true

match is a Valang function that returns true if the string in the second argument matches the regular expression in the first argument. The tagValue is a placeholder for the value that's being evaluated. There are a number of other functions available in Valang and you'll want to get familiar with them by reading the Valang documentation, but the syntax is pretty straightforward.

 

The last step for setting up the validation constraint is to create a localized error message for it.

insert into TLOCALIZEDPROPERTIES(uidpk, object_uid, localized_property_key, value, type)
     values(100174, 17, 'validationTagPhoneNumberError_en',
        'The phone number must match the format XXX-XXX-XXXX',
        'TagValueConstraint');

 

With this in place, I could define tags and set their tag value type to phone_number. To demonstrate that it worked, I created a customer telephone number tag, attached it to the SHOPPER tag dictionary, and then went into the Dynamic Content Delivery dialog to try it out.

invalid-phone-number.png

When I tried to enter letters or special characters other than the dash, the field background turns red and I could click the x to find out what I'd done wrong.

 

This is just a simple example. A quick look at the Valang documentation will reveal how flexible and powerful it is. I was also really surprised at how easy it was to set up a tag value type and associate it with validation constraints. In an upcoming article, I'll take things one step further and show how to replace the tag value's free-form text box with a drop-down list or combo box.

0 Comments Permalink

In 6.1.2, we added a slick condition builder widget to the Dynamic Content Delivery wizard. If you're thinking of using the Tagging Framework for your own purposes, you too can take advantage of this widget. In this article, we'll look at how to create a custom tag dictionary and a custom condition builder dialog to allow users to build conditions using the tags in a custom tag dictionary.

 

Before we get into coding, let's take a quick look at the new Dynamic Content Delivery dialog to see how the widget actually works.

 

How it works

Designing an intuitive UI for building conditions was a challenge for the design team. Most business users don't have a lot of experience with software applications that let them design complex rules for things like dynamic content and promotions. Usually, that falls in the domain of software programmers. So how do you give users almost code-level flexibility without completely scaring them away with its complexity? We banged our collective head against the wall for a long time over the course of multiple design sessions until finally we'd come up with something everyone was pleased with.

 

condbuilder_with_callouts1.png

As you can see, there are a lot of different parts here. The basic building block of a condition is a statement. A statement consists of

  • the tag
  • the tag operator
  • the tag value.

A condition must contain at least one statement. Creating a condition with a single statement is pretty straightforward. For example, assume you want to build a condition for shoppers who are 60 or over. You'd click add statement, expand the tag group, and select the tag, which in this case is "are of age". (Note that the "are of age" tag is located in the "Customer Profile" group. Tag groups were added in 6.1.2 as a way to organize tags in the condition builder.)

1add_age_tag.png

Next, you'd select the tag operator. In this case, we want "greater than or equal to".

2select_operator.png

Finally, you set a tag value of 60, which means you only want to match shoppers whose "are of age" tag contains a value that is greater than or equal to 60.

3enter_tag_value.png

That's a simple example, but we can create much more complicated conditions by combining multiple statements in a statement block.

 

A statement block can contain one or more statements. In the previous example, we had a default statement block containing a single statement. We can add additional statements by clicking the add statement link. The statements within a statement block are combined by either the AND or the OR operator. Only one operator can be used within a statement block. If you need to mix AND and OR, you can add additional statement blocks.

 

Creating the tag dictionary, tag group, and tags

 

First, we create a custom tag dictionary and some custom tags. Here's the SQL (mind the hard-coded primary keys).

/* Create a tag dictionary: */     
     insert into TTAGDICTIONARY(uidpk, guid, name, purpose)
          values(4, 'CUSTOM', 'Custom', 'Used to store custom tags and conditions');

/* Create a tag group: */
     insert into ttaggroup(uidpk, guid)
          values(5, 'CUSTOM_GROUP');


/* Create custom tags: */
     insert into TTAGDEFINITION(uidpk, guid, name, description, tagvaluetype_guid, taggroup_uid)
          values(22, 'CUSTOM_TAG_1', 'Custom 1', 'A custom tag', 'text', 5),
          (23, 'CUSTOM_TAG_2', 'Custom 2', 'A custom tag', 'text', 5),
          (24, 'CUSTOM_TAG_3', 'Custom 3', 'A custom tag', 'text', 5),
          (25, 'CUSTOM_TAG_4', 'Custom 4', 'A custom tag', 'text', 5);


/* Map custom tags to custom dictionary: */
     insert into TTAGDICTIONARYTAGDEFINITION(tagdictionary_guid, tagdefinition_guid)
          values('CUSTOM', 'CUSTOM_TAG_1'),
          ('CUSTOM', 'CUSTOM_TAG_2'),
          ('CUSTOM', 'CUSTOM_TAG_3'),
          ('CUSTOM', 'CUSTOM_TAG_4');


/* Create language resources for the tags and tag group: */
     insert into TLOCALIZEDPROPERTIES (UIDPK,LOCALIZED_PROPERTY_KEY,VALUE,TYPE,OBJECT_UID)
          values (50062,'tagDefinitionDisplayName_en','Custom tag 1','TagDefinition',22),
          (50063,'tagDefinitionDisplayName_en','Custom tag 2','TagDefinition',23),
          (50064,'tagDefinitionDisplayName_en','Custom tag 3','TagDefinition',24),
          (50065,'tagDefinitionDisplayName_en','Custom tag 4','TagDefinition',25),
          (50066,'tagGroupDisplayName_en','Custom group','TagGroup',5);

 

Creating the condition builder UI

Now, you want to create a CM client plugin with a dialog that will allow users to create and edit conditions using only the tags in this dictionary. I won't get into the details of creating a CM client plugin; you can look at the attached source code to see how I did it. The part we're interested in is the dialog that extends AbstractEpDialog. In my createEpDialogContent implementation, I add a text box to contain the name of the condition, a label for the text box, and the code that displays the condition builder composite.

protected void createEpDialogContent(final IEpLayoutComposite dialogComposite) {
          
     /* 
     * Set the color on the top level composite. This recursively sets the color of  
     * the condition builder's constituent controls.
     */     
     final Shell shell = this.getShell();
     final RGB rgb = new RGB(255, 255, 255);
     final Color color = new Color(shell.getDisplay(), rgb);           
     shell.setBackground(color);
     color.dispose();
                  
     // Add the condition name field label
     final IEpLayoutData labelData = dialogComposite.createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL, true, true, 2, 1);
     dialogComposite.addLabelBoldRequired(MyConditionBuilderMessages.ConditionName, EpState.EDITABLE, labelData);
          
     // Add the condition name field
     final IEpLayoutData fieldData = dialogComposite.createLayoutData(IEpLayoutData.FILL, IEpLayoutData.FILL, true, false, 2, 1);
     conditionNameText = dialogComposite.addTextField(EpState.EDITABLE, fieldData);
     conditionNameText.setTextLimit(CONDITION_NAME_TEXT_LIMIT);
 
     // Add the condition builder composite
     final Composite composite = displayConditionBuilder(shell);
     composite.setParent(dialogComposite.getSwtComposite());
}

 

The actual implementation of displayConditionBuilder is where the real work happens.

private Composite displayConditionBuilder(final Composite parentComposite) {
     
     ElasticPath elasticPath = Application.getInstance().getElasticPath();
 
     // get the tag operator service
     TagOperatorService tagOpService = (TagOperatorService)
               elasticPath.getBean(ContextIdNames.TAG_OPERATOR_SERVICE);
     
     // get tag definitions from the custom dictionary
     TagDictionaryService dictionaryService = (TagDictionaryService)
               elasticPath.getBean(ContextIdNames.TAG_DICTIONARY_SERVICE);
     TagDictionary tagDictionary = dictionaryService.findByGuid(CUSTOM_DICTIONARY);
     Set<TagDefinition> tagDefinitions = tagDictionary.getTagDefinitions();
     
     // get the tag groups for those tag definitions
     TagGroupService tagGroupService = (TagGroupService)
               elasticPath.getBean(ContextIdNames.TAG_GROUP_SERVICE);                    
     Set<TagGroup> tagGroups = new HashSet<TagGroup>();          
     for (TagDefinition tagDefinition : tagDefinitions) {
          if (tagDefinition.getGroup() == null) { continue; }
          tagGroups.add(tagGroupService.findByGuid(tagDefinition.getGroup().getGuid()));
     }
     
     ConditionBuilderFactoryImpl factory = new ConditionBuilderFactoryImpl();
     factory.setDataBindingContext(this.dataBindingCtx);
     factory.setLocale(CorePlugin.getDefault().getDefaultLocale());
     factory.setTagOperatorService(tagOpService);
     factory.setConditionBuilderTitle(""); //$NON-NLS-1$
     factory.setTagGroupsList(new ArrayList<TagGroup>(tagGroups));
     
     factory.getResourceAdapterFactory().setResourceAdapterForLogicalOperator(
               new ResourceAdapter<LogicalOperatorType>() {
                    public String getLocalizedResource(final LogicalOperatorType object) {
                         return AdminGCMessages.getMessage(object.getMessageKey());
                    } });
 
     factory.getResourceAdapterFactory().setResourceAdapterForUiElements(
               new ResourceAdapter<String>() {
                    public String getLocalizedResource(final String object) {
                         return AdminGCMessages.getMessage(object);
                    } });
 
     factory.setListenerForRefreshParentComposite(
               new ActionEventListener<Object>() {
                    public void onEvent(final Object object) {
                         parentComposite.pack();
                         parentComposite.layout();
                    } });
     
     Composite composite = factory.createFullUiFromModel(parentComposite, SWT.FLAT, this.logicalOperator);
     return composite;
}

That looks like a lot of code, but we'll break it down by sections so we can make more sense of it.

 

In the first section, we get references to some of the tag-related services. The TagOperatorService is used internally by the condition builder factory.

     TagOperatorService tagOpService = (TagOperatorService)
               elasticPath.getBean(ContextIdNames.TAG_OPERATOR_SERVICE);

Next, we use the TagDictionaryService to get the tags from the custom dictionary.

     TagDictionaryService dictionaryService = (TagDictionaryService)
               elasticPath.getBean(ContextIdNames.TAG_DICTIONARY_SERVICE);
     TagDictionary tagDictionary = dictionaryService.findByGuid(CUSTOM_DICTIONARY);
     Set<TagDefinition> tagDefinitions = tagDictionary.getTagDefinitions();

 

Then, we use the TagGroupService to get the groups associated with those tags. In order to include a tag in the condition builder, it must be associated with a group.

     TagGroupService tagGroupService = (TagGroupService)
               elasticPath.getBean(ContextIdNames.TAG_GROUP_SERVICE);                    
     Set<TagGroup> tagGroups = new HashSet<TagGroup>();          
     for (TagDefinition tagDefinition : tagDefinitions) {
          if (tagDefinition.getGroup() == null) { continue; }
          tagGroups.add(tagGroupService.findByGuid(tagDefinition.getGroup().getGuid()));
     }

The next step is to instantiate the condition builder factory and configure it with the TagOperatorService and the list of tag groups.

// configure the condition builder factory
ConditionBuilderFactoryImpl factory = new ConditionBuilderFactoryImpl();
factory.setDataBindingContext(this.dataBindingCtx);
factory.setLocale(CorePlugin.getDefault().getDefaultLocale());
factory.setTagOperatorService(tagOpService);
factory.setConditionBuilderTitle(""); //$NON-NLS-1$
factory.setTagGroupsList(new ArrayList<TagGroup>(tagGroups));

We need to specify how the condition builder gets the language strings for its UI elements and labels. We do this by configuring generic resource adapters on the condition builder factory.

factory.getResourceAdapterFactory().setResourceAdapterForLogicalOperator(
          new ResourceAdapter<LogicalOperatorType>() {
               public String getLocalizedResource(final LogicalOperatorType object) {
                    return AdminGCMessages.getMessage(object.getMessageKey());
               } });
 
// configure the resource adapter that provides the UI labels for the other UI components
factory.getResourceAdapterFactory().setResourceAdapterForUiElements(
          new ResourceAdapter<String>() {
               public String getLocalizedResource(final String object) {
                    return AdminGCMessages.getMessage(object);
               } });

The resource adapters delegate to the AdminGCMessages utility class, which defines constants for the language resources. The actual language strings are stored in com.elasticpath.cmclient.admin.gc.AdminGCPluginResources.properties.

public final class AdminGCMessages {
 
     /* Property file binding. */
     private static final String BUNDLE_NAME = "com.elasticpath.cmclient.admin.gc.AdminGCPluginResources"; //$NON-NLS-1$
 
...
 
     /* Generic condition builder UI Labels */
     public static String LogicalOperator_AND;
     public static String LogicalOperator_OR;
     
     public static String ConditionBuilder_Title;
     public static String ConditionBuilder_AddConditionButton;
     
     public static String ConditionBuilder_Add_Rule_label;
     public static String ConditionBuilder_Remove_Rule_label;
 
     
     static {
          NLS.initializeMessages(BUNDLE_NAME, AdminGCMessages.class);
     }
     
...
     public static String getMessage(final String messageKey) {
          try {
               final Field field = AdminGCMessages.class.getField(messageKey);
               return (String) field.get(null);
               
          } catch (final Exception e) {
               return messageKey;
          }
     }     
}

We also need to configure a listener to ensure that the parent composite gets resized whenever statements and statement blocks are added or removed from the widget.

factory.setListenerForRefreshParentComposite(
          new ActionEventListener<Object>() {
               public void onEvent(final Object object) {
                    parentComposite.pack();
                    parentComposite.layout();
               } });

The last step is to call the factory's createFullUiFromModel method and return the Composite that contains the condition builder.

Composite composite = factory.createFullUiFromModel(parentComposite, SWT.FLAT, this.logicalOperator);
return composite;

The first argument to createFullUiFromModel  is the Composite object that will contain the condition builder. The third argument is a LogicalOperator object that represents the condition model.

 

Note that conditions are persisted as ConditionalExpression objects. If you look at the attached source code for the dialog, you'll see that the dialog constructor takes a ConditionalExpression object and creates a LogicalOperator from it.

public GCDialog(final Shell parentShell, final ConditionalExpression expression, final String title, final Image image) {
     super(parentShell, 2, false);
     this.title = title;
     this.titleImage = image;
     this.expression = expression;
     this.dataBindingCtx = new DataBindingContext();
     
     // convert the expression to a DSL string and populate the logical operator
     ConditionDSLBuilder dslBuilder = (ConditionDSLBuilder) 
               Application.getInstance().getElasticPath().getBean(ContextIdNames.TAG_CONDITION_DSL_BUILDER);
               
     String conditionString = this.expression.getConditionString();
     
     if (conditionString == null) {
          this.logicalOperator = new LogicalOperator(LogicalOperatorType.AND);
          this.logicalOperator.addLogicalOperator(new LogicalOperator(LogicalOperatorType.OR));                    
     } else {
          this.logicalOperator = dslBuilder.getLogicalOperationTree(conditionString);
     }          
                              
     setAuthorized();                    
}

A ConditionalExpression object is passed in by the action that creates the dialog. In the constructor, we use the expression's getConditionString method to return the condition as a string value. (You'll see some neat Groovy code if you take a look at it.) Then, we use the ConditionDSLBuilder service's getLogicalOperationTree method to convert that string to a LogicalOperator object.

 

To save changes made by the user, we convert the LogicalOperator back to a condition string and update the ConditionalExpression object. You can see this in the okPressed method of the dialog:

protected void okPressed() {
     dataBindingCtx.updateModels();          
     
     ElasticPath elasticPath = Application.getInstance().getElasticPath();
     
     // convert the logical operator to a condition string so it can be persisted
     ConditionDSLBuilder dslBuilder = elasticPath.getBean(ContextIdNames.TAG_CONDITION_DSL_BUILDER);               
     String conditionString;
     try {
          conditionString = dslBuilder.getConditionalDSLString(this.logicalOperator);
          this.expression.setConditionString(conditionString);
          
          // set the condition name
          this.expression.setName(this.conditionNameText.getText());
          
          // set the dictionary name
          this.expression.setTagDictionaryGuid(CUSTOM_DICTIONARY);
 
          // make sure it's saved as a named condition
          this.expression.setNamed(true);
          
          super.okPressed();
          
     } catch (InvalidConditionTreeException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
     }
}

 

The actual persistence code is in the Action code that opened the dialog (the EditGCAction and CreateGCAction classes):

public void run() {
     ElasticPath elasticPath = Application.getInstance().getElasticPath();  
     ConditionalExpression expression = (ConditionalExpression) elasticPath.getBean(ContextIdNames.CONDITIONAL_EXPRESSION);                    
     boolean dialogOk = GCDialog.openCreateDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), 
               expression);
          
     if (dialogOk) {               
          TagConditionService conditionService = (TagConditionService) elasticPath.getBean(ContextIdNames.TAG_CONDITION_SERVICE);          
          conditionService.saveOrUpdate(expression);          
          listView.refreshViewerInput();               
     }
}

There's definitely a lot going on in several different places and I've tried to touch as many of the key areas as possible. Hopefully, the attached code will answer some of the outstanding questions, but I'm sure there are others. Please post any questions you have here.

0 Comments Permalink

6.1.2 brings bug fixes and some enhancements to the Elastic Path Commerce platform, mainly in the areas of the Tagging Framework and CM client usability (list sorting and searching). At a glance:

  • Geo IP tags: 6.1.1 introduced Dynamic Contentand the Tagging Framework, which gave marketers the ability to personalize the shopping experience based on who the customer is (age, referring site, search engine terms). With the addition of Geo IP tags in 6.1.2, it's now possible to target content based on the shopper's geographic location (city, state, country, region, etc.).
  • Visited categories tag: a new tag in the Tagging Framework that keeps track of the categories that the the shopper has visited.
  • Cart subtotal tag: tracks the shopping cart subtotal.
  • In-site search terms tag: contains the search terms entered in the storefront's search box.
  • Generic condition builder: 6.1.1 included a simple UI for building Dynamic Content Delivery conditions. 6.1.2 expands on that with a more flexible, intuitive UI. (If you've worked with Google Analytics' advanced segments feature, it should look familiar.) This new UI was developed as an RCP UI widget, so it can easily be leveraged for other scenarios that require a condition builder to configure tag evaluation.
  • Tag value types and validation constraints: you can now configure critieria for validating the tag values entered in the Dynamic Content Delivery dialog.
  • Selectable values for tags: you can use drop-down lists to set tag values in the Dynamic Content Delivery dialog.
  • Saved conditions: it is now possible to create Dynamic Content Delivery conditions and save them for use in other Dynamic Content Deliveries.
  • Tag groups: tags can now be assigned to tag groups, to provide better visual organization in the Dynamic Content Delivery dialog.
  • Tag localization: tags, tag groups, and validation constraints now support localization through the TLOCALIZEDPROPERTIES table.

 

There'll be some blog posts to go in depth on some of these topics, so keep watching this space.

1 Comments Permalink

In a recent post on the Get Elastic blog, Linda talked about personalizing content based on the customer's sorting behavior. For example, price-conscious shoppers might sort categories and search results by price from lowest to highest, so you want to show them promotional banners that highlight inexpensive or discounted items. Or maybe you have shoppers who place a higher value on popularity. These shoppers tend to sort by top sellers, and you want to target specific content to them.

 

But are there any ecommerce tools that can do this type of personalization out of the box? None that I know of. However, Elastic Path Commerce 6.1.1 has two new features that give us almost everything we need. In this article, we'll look at how to add sort-by personalization. And it's really quite easy, I promise.

 

Before we get started, be sure to read my previous posts on Dynamic Content and the Tagging Framework (Dynamic Content 101, Tagging Framework 101, and Targeting Dynamic Content for Mobile Device Users). For more in-depth technical information, you can check out the 6.1.1 Developer Guide or see the Commerce Manager User Guide for the marketing user perspective.

The Solution

When a visitor to the storefront selects one of the sorting options from the drop-down list, a request is sent to Elastic Path with a sorter parameter in the URL. For example, if the shopper is browsing a category and sorts by price from lowest to highest, the URL will look like the following:

 

http://www.elasticpath.com/storefront/browse.ep?cID=100009&filters=c90000003&sorter=price-asc

 

The sorter parameter indicates which sorting option was selected by the shopper. Elastic Path 6.1.1 includes a TARGET_URL tag, which contains the query string, but this value is only captured at the time the shopper's session is created. So, we'll need to do a few things:

  • Create a QUERY_STRING tag and map it to the "who" tag dictionary.

  • Create a tag event listener interface and a tagger implementation to put the query string in a tag and add it to the shopper's tag set.

  • Create a filter to intercept the HTTP request, invoke the tag event listener, and pass it the request and customer session information.

  • Update the storefront request filter chain to include the new filter.

Once this is done, marketing users will be able to configure Dynamic Content Delivery to display specific content based on whether the QUERY_STRING tag includes the sorter parameter and what value it contains.

Create the QUERY_STRING Tag

To create the QUERY_STRING tag, you'll need to add it to the ttagdefinition table in your database:

 

insert into ttagdefinition(uidpk, guid, name, description, data_type)
    values(10, 'QUERY_STRING', 'QUERY_STRING',
    'The parameters in the request URL.', 'java.lang.String');

 

Next, you need to add the new tag to the "who" tag library:

 

insert into ttagdictionarytagdefinition(tagdictionary_guid, tagdefinition_guid)
    values('WHO', 'QUERY_STRING');

 

Now, marketing can use the QUERY_STRING tag when they're building rules in the Commerce Manager for displaying Dynamic Content. However, we still need to tell Elastic Path how to get the query string and put it in the shopper's tag set. That's coming next...

Create the Tag Event Listener and Tagger

The Tagging Framework uses a lightweight event listener model. It doesn't require you to implement any particular interfaces, but it's a good idea from a design perspective. There are two tag event listener interfaces defined in the Elastic Path Commerce core library, under com.elasticpath.commons.listeners:

  • NewHttpSessionEventListener, which receives notifications when the shopper arrives at the storefront.

  • CustomerLoginEventListener, which receives notifications when the shopper signs in to their store account.

Both interfaces expose an execute method that takes a CustomerSession and an HttpServletRequest as parameters.

 

For this customization, we'll follow the model of these existing listeners. Because we need to listen for all HTTP requests, not just when it's a new session or when the customer logs in, you'll create an interface named HttpRequestEventListener. Like the other tag event listener interfaces, it only needs an execute method that takes the customer session and HTTP request objects as parameters:

 

public interface HttpRequestEventListener {
    public void execute(final CustomerSession session, final HttpServletRequest request);
}

 

In the storefront web application, the com.elasticpath.sfweb.listeners package contains several Tagger classes that implement one or both of the listener interfaces. These classes are responsible for populating the tags with the data they need. We need to create our own tagger class that implements the new interface, takes the query string from the request, and puts it in the QUERY_STRING tag in the customer's tag set:

 

public class QueryStringTagger implements HttpRequestEventListener {
    private static final String QUERY_STRING = "QUERY_STRING";
    private static final Logger LOG = Logger.getLogger(QueryStringTagger.class);    

    public void execute(final CustomerSession session,
final HttpServletRequest request) {
        String queryString = request.getQueryString();
        if (LOG.isDebugEnabled()) {
            LOG.debug("Populating customer session with the query string: " + queryString);

        }
        TagSet tagSet = session.getCustomerTagSet();
        tagSet.addTag(QUERY_STRING, new Tag(queryString));
    }
}

 

Keep in mind that the QueryStringTagger is going to get called on every request, so you want to avoid doing too much processing in here. The key things to note:

You get the tag set from the CustomerSession object by calling getCustomerTagSet.

  • You create a tag and set its value by calling the Tag constructor and passing it the value you want to assign.

  • You use the addTag method on the TagSet object to add the tag to the shopper's tag set.

We now have a tagger class that can receive notification of HTTP requests and put the query string in the shopper's tag set. The next step is to create the filter that will notify the tagger class and pass it the customer session and HTTP request information.

Create the Filter

We'll create the QueryStringFilter in the storefront web app in the com.elasticpath.sfweb.filters package. The filter needs to include a HttpRequestEventListener collection. We'll define the filter and the tagger bean definitions in the storefront web app's filter-config.xml:

 

<bean id="queryStringFilter" class="com.elasticpath.sfweb.filters.QueryStringFilter">

<property name="requestHelper">

<ref bean="requestHelper" />

</property>

<property name="httpRequestEventListeners">

<list>

<ref bean="queryStringTagger"/>

</list>

</property>

</bean>

<bean id="queryStringTagger" class="com.elasticpath.sfweb.listeners.QueryStringTagger"/>

 

Note the QueryStringTagger object in the httpRequestEventListeners collection. In the QueryStringFilter's doFilter method, we'll call the execute method of each event listener in the collection:

 

public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain)

throws IOException, ServletException {

 

   if (!(request instanceof HttpServletRequest)) {
       filterChain.doFilter(request, response);
       return;
    }

 

    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    CustomerSession customerSession = getCustomerSession(httpServletRequest);

 

    // Notify the request event listeners

    Collection<HttpRequestEventListener> listeners = getHttpRequestEventListeners();

    for (HttpRequestEventListener listener : listeners) {

        listener.execute(customerSession, httpServletRequest);

    }

    filterChain.doFilter(request, response);

}

 

CustomerSession getCustomerSession(final HttpServletRequest request) {

     return requestHelper.getShoppingCart(request).getCustomerSession();

}

Update the Filter Chain

Finally, we need to add the new filter to the filter chain in acegi.xml.vm and acegi.xml:

 

            <value>

                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

                \A/google-callback.ep.*\Z=channelProcessingFilter, basicProcessingFilter, basicExceptionTranslationFilter, basicFilterInvocationInterceptor

                \A/.*\Z=channelProcessingFilter, httpSessionContextIntegrationFilter, logoutFilter, logoutCustomerSessionFilter, onePageLogoutFilter, authenticationProcessingFilter, queryStringFilter, exceptionTranslationFilter, filterInvocationInterceptor

            </value>

 

Note the location of the queryStringFilter. Make sure you add it to the chain at some point after the customer session has been created, which generally occurs in the authenticationProcessingFilter.

 

Now that we've created the QUERY_STRING tag and the query string is getting stored in each visitor's tag set, it's time to let the marketing team test it out.

 

Using the example mentioned earlier, if you have a piece of Dynamic Content that you want to display  when shoppers sort a category or search results on price from lowest to highest, you can add a condition in the Dynamic Content Delivery like this:

dcd_query_string_tag_circled.png

Now, assuming you've got the proper Content Spaces in your category and search Velocity templates, the Dynamic Content will appear whenever your visitors sort by price from lowest to highest. And you can easily add others for different sorting habits, such as top sellers.

 

As you can see, a lot can be accomplished with just a small amount of initial coding. Be sure to download the attached source code and try it out for yourself.

0 Comments Permalink

What if your online store could identify shoppers with mobile devices and deliver content based on the device they're using? With the arrival of Dynamic Content and the Tagging Framework in Elastic Path Commerce 6.1.1, you can do just that.

 

This article takes a practical approach to extending the Tagging Framework. We'll look at how a developer can add a tag to identify mobile device users and how to create a tagging event listener to capture the information we need to store in that tag. And it's really quite easy, I promise. (Before diving in, you'll need a basic understanding of the Tagging Framework and Dynamic Content, so you'll want to take a look at my earlier posts and check out the 6.1.1 documentation.)

 

Solution Overview

When a visitor views a page on your Elastic Path storefront, the HTTP request includes a User-Agent header. The User-Agent header contains information about the application that's requesting
the page. This could be an automated web crawler, such as Googlebot or, if there's a person using that application to view web pages, a web browser. In the case of requests coming from mobile devices, like the iPhone, the user agent header also includes the name of the device.


We need to store this information in a tag and attach it to the shopper so that it can be accessed later on when we need to determine what Dynamic Content to display. Elastic Path Commerce doesn't have a tag that contains this information by default, so we need to do three things:

  • define a USER_AGENT tag in the "who" tag dictionary
  • create a tagging event listener (a tagger) class to get the User-Agent header from the request and store it in a USER_AGENT tag in the shopper's tag set
  • add the listener to the collection of event listeners that are invoked each time an HTTP session is created in Elastic Path Commerce.

 

Once this is done, marketing users will be able to target Dynamic Content based on the type of mobile device the shopper is using.

Defining the USER_AGENT Tag

First, we need to define the USER_AGENT tag. To do this, we need to insert a row into the TTAGDEFINITION table of the Elastic Path Commerce database:

insert into ttagdefinition(uidpk, guid, name, description, data_type)
values(10, 'USER_AGENT', 'USER_AGENT', 'The User-Agent request header.', 'java.lang.String');

Next, we need to add the tag to one of the three tag libraries ("who", "when", and "where"). The user agent is a piece of information that helps us identify the shopper, so we need to add it to the "who"
library. To do this, we need to insert a row into the TTAGDICTIONARYTAGDEFINITION table:

insert into ttagdictionarytagdefinition(tagdictionary_guid, tagdefinition_guid)
values('WHO', 'USER_AGENT');

Now, marketing users can log in to the Commerce Manager client, create Dynamic Content, and configure the Dynamic Content Delivery to evaluate the USER_AGENT tag, but at this point that wouldn't be very useful, because we haven't yet set up the tagging event listener to get that information and store it in the shopper's tag set.

Creating the Tagger

There are two tagging event listener interfaces defined in the Elastic Path Commerce core library, under com.elasticpath.commons.listeners:

  • NewHttpSessionEventListener, which is fired when the visitor arrives at the storefront and an HTTP session is created.
  • CustomerLoginEventListener, which is fired when the visitor signs in to their store account.

 

Both interfaces expose an execute method that takes a CustomerSession and an HttpServletRequest as parameters.

 

In the storefront web application, the com.elasticpath.sfweb.listeners package contains several Tagger classes that implement one or both of the listener interfaces. These classes are responsible for populating the tags with the data they need. We can get the User-Agent header at the same time as the session is created, so we'll create a UserAgentTagger class that extends NewHttpSessionEventListener. The interface defines only one method: execute. We'll implement the execute method to capture the user agent header string and put it in a tag:

package com.elasticpath.sfweb.listeners; 
 
 
import org.apache.log4j.Logger;
import  javax.servlet.http.HttpServletRequest;
import com.elasticpath.commons.listeners.NewHttpSessionEventListener;
import com.elasticpath.domain.customer.CustomerSession;
import com.elasticpath.tags.Tag;
import com.elasticpath.tags.TagSet;
 
public class UserAgentTagger implements NewHttpSessionEventListener {
     private static final String USER_AGENT = "USER_AGENT";
     private static final Logger LOG = Logger.getLogger(UserAgentTagger.class);
 
     public void execute(CustomerSession session, HttpServletRequest request) {
 
          // get the user agent header string
          String userAgent = request.getHeader("User-Agent");
          
          if (LOG.isDebugEnabled()) {
               LOG.debug("User-Agent header contents: " + userAgent);
          }
 
          // get the shopper's tag set
          TagSet tagSet = session.getCustomerTagSet();
 
          // set the user agent in the shopper's tag set
 
          Tag userAgentTag = new Tag();
          userAgentTag.setValue(userAgent);
          tagSet.addTag(USER_AGENT, userAgentTag);
     }
}

Adding the Tagger to the Event Listeners Collection

When an HTTP session is created, the handleFilterRequest method of webCustomerSessionService calls each listener's execute method. So, we need to add the user agent tagger to the newHttpSessionEventListeners collection on the webCustomerSessionService bean. Open the storefront web app's serviceSF.xml file (located in WEB-INF/conf/spring/service) and add a bean definition for the user agent tagger:

 

<bean id="userAgentTagger" class="com.elasticpath.sfweb.listeners.UserAgentTagger"/>

 

Next, find the webCustomerSessionService bean definition. Inside bean definition, find the newHttpSessionEventListeners property and add a reference to the userAgentTagger bean:

 

 

We can now test this by enabling debug logging on the storefront and then accessing the storefront with various browsers. Each time a new customer session is created, a debug message is logged with the complete user agent header string. For example, when I use my iPod Touch to access my storefront, the following message is logged:

2009-04-22 17:48:26,246 [http-8080-Processor23] DEBUG com.elasticpath.sfweb.listeners.UserAgentTagger - User-Agent header contents: Mozilla/5.0 (iPod; U; CPU iPhone OS 2_2_1 like Mac OS X;
en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11 Safari/525.20

There's a lot more information in there than we really need, but we see that it contains the word iPhone. So, if we want to target content at shoppers who are using an iPhone or an iPod, we can now do that.

Now, when we're creating the Dynamic Content Delivery, we can select the USER_AGENT tag and use its value as one of the criteria for displaying content. For example, if you're configuring Dynamic Content Delivery to show a different banner to iPhone users, add the USER_AGENT tag with the operator set to includes and the value set to iPhone.

dcd-user-agent.png

This is just one way you can extend the Tagging Framework. You could also create a tag to get information from a cookie or a specific request parameter. And you're not limited to information in the HTTP request; for example, you could tag customers with products and categories they've viewed. Going forward, we'll be building more tags into the framework, so if you've got more ideas, we'd love to hear about them.

 

I know I've probably glossed over a few details, so if you have any questions or comments, please don't hesitate to post them here.

0 Comments Permalink

Tagging Framework 101

Posted by Paul Monk May 11, 2009

The Tagging Framework is a new feature in 6.1.1 that allows you to track information about objects in Elastic Path Commerce. Objects can have tag sets, which can contain tags. Each tag contains a piece of information about the object. The values in the tags can be evaluated against a set of rules (called conditional expressions). The outcome of the evaluation can then be used to make decisions about application behavior.

 

In 6.1.1, the Tagging Framework is used by Dynamic Content Delivery conditions to determine which Dynamic Content is displayed to the shopper, but there are many other potential applications for it. This article looks at some of the main parts of the Tagging Framework. Aside from the javadoc (and this article), the inner workings of the Tagging Framework are currently undocumented, but hopefully, this article will give you a bit of a start.

Main Classes

The classes and interfaces of the Tagging Framework are located in the core library (com.elasticpath.core) under the com.elasticpath.core.tags package.

  • Tag: a piece of information about an object

  • TagSet: a set of tags associated with an object

  • TagDefinition: defines a piece of information that can be stored and tracked by the application

  • TagDictionary: a set of tag definitions

  • Condition: a rule that can be used to evaluate tag values

  • ConditionalExpression: a group of Conditions, joined by a logical operator (AND, OR, or NOT) and evaluated as a single unit

  • ConditionDSLBuilder: a service used to convert conditional expressions to strings and vice versa. The strings contain the expression in Elastic Path's domain specific language (DSL) for tag evaluation.

  • ConditionEvaluatorService: a service that evaluates a set of tags against a conditional expression and returns true or false.

The service beans are defined in WEB-INF/conf/spring/service/service.xml.

Database Tables

The following database tables are used by the Tagging Framework:

  • TTAGALLOWEDVALUE: can be used to restrict the values allowed for a specific setting

    • TTAGCONDITION: contains conditional expressions

  • TTAGDEFINITION: contains the definitions of the tags supported by the application

    • TTAGDICTIONARY: contains the available tag dictionaries ("who", "when", and "where")

  • TTAGDICTIONARYTAGDEFINITION: associates tags with tag dictionaries.

In addition, the TSELLINGCONTEXTCONDITION table is used to associate conditional expressions stored in TTAGCONDITION with Dynamic Content Delivery.

Defining a Tag

Before you can use a tag, you must define it and add it to a tag dictionary. To define a tag, insert a row into the TTAGDEFINITION table of the Elastic Path Commerce database:

insert into ttagdefinition(uidpk, guid, name, description, data_type)

values(10, 'USER_AGENT', 'USER_AGENT', 'The User-Agent request header.',

'java.lang.String');

The data_type field must contain a valid, fully-qualified Java class name (java.lang.String, java.lang.Integer, etc.).

Adding a Tag to a Tag Dictionary

After a tag is defined, you need to add it to at least one of the three tag dictionaries ("who", "when", and "where"). The "who"  tag dictionary defines tags that describe a shopper. The "where" tag dictionary defines tags that identify the selling channel (which stores). The "when" tag dictionary defines tags that indicate the time. To add a tag to the "who" tag dictionary, insert a row into the TTAGDICTIONARYDEFINITION table:

insert into
ttagdictionarytagdefinition(tagdictionary_guid, tagdefinition_guid)
values('WHO', 'USER_AGENT');

 

Adding a Tag to a Tag Set

The tag set associated with a shopper can be retrieved from the customer session object. The following code retrieves the tag set, creates a tag, and adds the new tag to the tag set.

import com.elasticpath.tags.TagSet;

import com.elasticpath.tags.Tag;

...

CustomerSession session = shoppingCart.getCustomerSession();

TagSet tagSet = session.getCustomerTagSet();

Tag newTag = new Tag();

newTag.setValue("A value");

tagSet.addTag("NEW_TAG", newTag);

 

Usually, similar code would be executed inside a tagging event listener.

Implementing a Tagging Event Listener

The Tagging Framework uses an event listener model to populate tag values. Listeners are invoked by the application when specific events occur, such as when the shopper logs on to their store account. To populate a tag, you create a listener and add it to the appropriate collection.

Two types of listeners are supported by Elastic Path Commerce out of the box:

  • NewHttpSessionEventListener: these listeners are called when the visitor arrives at the storefront and an HTTP session is created.

  • CustomerLoginEventListener: these listeners are called when the visitor signs in to their store account.

Both listener interfaces expose an execute method that takes a CustomerSession and an HttpServletRequest as parameters.

To add a new tag, you first need to determine how the value of the tag will be set. If the value of the tag can be retrieved from the HTTP request, your listener class should implement NewHttpSessionEventListener. If the value of the tag is only available after the customer has logged on to their Elastic Path account, then create a listener class that implements CustomerLoginEventListener. The class must implement the execute method to set the tag value and add it to the tag set.

package com.elasticpath.sfweb.listeners;
 
import javax.servlet.http.HttpServletRequest;
import com.elasticpath.commons.listeners.NewHttpSessionEventListener;
import com.elasticpath.domain.customer.CustomerSession;
import com.elasticpath.tags.Tag;
import com.elasticpath.tags.TagSet;
 
public class UserAgentTagger implements NewHttpSessionEventListener {
 
     private static final String USER_AGENT = "USER_AGENT";
     
     public void execute(CustomerSession session, HttpServletRequest request) {
          // get the user agent header string
          String userAgent = request.getHeader("User-Agent");
          
          // get the shopper's tag set
          TagSet tagSet = session.getCustomerTagSet();
          
          // set the user agent in the shopper's tag set
          Tag userAgentTag = new Tag();
          userAgentTag.setValue(userAgent);
          tagSet.addTag(USER_AGENT, userAgentTag);
     }
}

I'll be following up a little later this week with an example of a potential real-world use of the Tagging Framework and Dynamic Content.

1 Comments Permalink

Dynamic Content 101

Posted by Paul Monk May 6, 2009

Dynamic Content is an exciting new feature in Elastic Path 6.1.1. The idea behind it is to give the Marketing team more control over site content without having to make changes to code (Java or HTML). There is still some initial work for the web developer to enable Dynamic Content in the storefront, and if your organization has special requirements, it's definitely possible for developers to extend the Dynamic Content framework (more on this in a future post), but control over Dynamic Content is mainly in the hands of Marketing. In a nutshell, here's how it works:

 

  • The web developer sets up some locations (called Content Spaces) within the web page for displaying Dynamic Content.
  • The EP administrator registers the Content Space in the Commerce Manager.
  • The Marketing person selects some content (a banner image, a Flash object) to display in the storefront.
  • The Marketing person chooses the Content Space where the content will appear and sets the conditions for displaying it (the who, when, and where).

 

And Elastic Path Commerce takes care of the rest. Let's take a closer look at how this works in practice.

 

Setting up a Content Space

The web developer creates the HTML page layout of the storefront in the Velocity templates. Within the HTML, the web developer can include the contentspace macro. This looks something like the following:

#contentspace('contentSpaceID', '<span>Alternative Content</span>')

This defines a Content Space, which can be used to display Dynamic Content. The first parameter is the Content Space ID, which will need to be registered before it can be used. The second parameter is the static content to display if there's no Dynamic Content mapped to the space.

 

If you have 6.1.1 installed and you've set up the demo store data, you can see some examples of Content Spaces in the storefront home page template (themes\electronics\default\templates\velocity\index.vm under your assets directory). For more information on setting up Content Spaces, see the Developer Guide.

 

Registering a Content Space

The EP administrator registers the Content Space in the Commerce Manager so that it can be used as a target for Dynamic Content. To do this, log in to Commerce Manager and choose Activity->Configuration. Then click Content Spaces under Targeted Selling. Click the Create Content Space button. Set the name to the Content Space ID used in the Velocity template. You should also enter a meaningful description, something that will help users determine where the Content Space is, its preferred dimensions, etc. For more details, see the Developer Guide or the Commerce Manager User Guide.

 

Creating Dynamic Content

Next, Marketing selects the type of content they want to display in the storefront. Out of the box, Elastic Path Commerce supports several types. For example, there are types for displaying images and Flash objects. There's also a type for displaying HTML. And developers can easily create more types. These types, also called Content Wrappers, aren't simply static content. They can include Groovy code to do just about anything you can imagine. And you don't need to restart the storefront to make changes to them. (You can find out more about custom Content Wrappers in the Content Wrappers section in the Developer Guide.)

dc_adding_dc4.png

After selecting a type, there's usually some configuration required. For example, for a clickable image type, you'd need to select an image file and set the URL where shoppers will be redirected when they click the image.

 

There's a tutorial that will walk you through the process of creating Dynamic Content. (For the tutorial, you'll need to either install the Trial Installer or to set up the demo store data in your dev environment.)

 

Setting up the Dynamic Content Delivery

The last step is to set up the conditions for displaying the content. We want to make sure that the content will be displayed in the right place (which Content Space), to the right shoppers, at the right time, and in the right store (for example, display this content to shoppers who followed a link from Google after searching for the keyword "clogs").

dc_adding_dcd6.png

The details of configuring Dynamic Content Delivery are covered in the tutorial on creating Dynamic Content and in the Commerce Manager User Guide.

 

Behind the scenes, Dynamic Content Delivery relies heavily on the Tagging Framework to get information about the shopper and decide which pieces of content to display. In future posts, we'll dig a bit deeper into the customizable aspects of both Dynamic Content and the Tagging Framework. Until then, be sure to check out the 6.1.1 documentation on the Elastic Path doc site for more in-depth coverage of Dynamic Content.

0 Comments Permalink

Elastic Path Commerce 6.1.1 is now available for download. This release includes bug fixes, performance enhancements, and some new features. Here's what's new and noteworthy:

  • Import/Export tool improvements (performance improvements, support for import/export of promotions and configuration settings)
  • Support for load balancing of search server requests
  • Dynamic Content
  • Tagging and tag evaluation (a.k.a., the Tagging Framework).

You can find out more by checking out the 6.1.1 Relase Notes and the 6.1.1 documentation on the Elastic Path documentation site.

 

Over the next few days, we'll be posting articles on the Grep technical blog to highlight some of the cool things you can do, particularly with Dynamic Content and the Tagging Framework. Dynamic Content is about personalizing the shopping experience; Marketers can configure what content to display to which shoppers, where it will appear in the storefront, and the time period during which it will be shown. The Tagging Framework is what enables Dynamic Content to be targeted at specific shoppers. When a shopper first arrives at the storefront, Elastic Path Commerce starts collecting information and storing that information in tags, including:

  • the URL of the site where the shopper came from
  • the storefront URL where the shopper arrived
  • the search terms that were entered in the search box (if the shopper came from a search engine page like Google or Yahoo)

And when the customer logs in, the Tagging Framework can pull in other information from the customer's account, such as gender and age. Marketers can create rules in the Commerce Manager to target specific Dynamic Content at specific shoppers based on the information in each shopper's tag set.

 

This is pretty exciting stuff, so be sure to stay tuned for blog posts!

0 Comments Permalink

So, you've imported all your Elastic Path projects into Eclipse. You've set up Tomcat to run your Elastic Path web apps from within Eclipse and you've configured the log4j.properties for each web app. You launch the server. Your web apps don't start. What did you do wrong?

 

You check your web app logs. Buried deep in the stack trace, you see this:

 

Caused by: <openjpa-1.0.1-ep-6.1-1-rsvnversion: invalid option:  nonfatal user error> org.apache.renamed.openjpa.persistence.ArgumentException: [Error while processing persistent field com.elasticpath.domain.order.impl.OrderImpl.status, declared in null. Error details: The accessor for field setStatus in type com.elasticpath.domain.order.impl.OrderImpl is private or package-visible. OpenJPA requires accessors in unenhanced instances to be public or protected. If you do not want to add such an accessor, you must run the OpenJPA enhancer after compilation, or deploy to an environment that supports deploy-time enhancement, such as a Java EE 5 application server.]

 

The clue here is this:

 

you must run the OpenJPA enhancer after compilation

 

OpenJPA does bytecode enhancement on Elastic Path's persistent classes. You can learn the details of why and how in this article on OpenJPA bytecode enhancement, but if you just want to start your web apps, here's what you need to do:

  1. In Eclipse, expand the com.elasticpath.core project.
  2. Select coreAntJpaEnhance.launch.
  3. Right-click and choose Run As -> coreAntJpaEnhance.

 

You usually need to do this when you first set up your Elastic Path dev environment. After that, you need to do it whenever you modify persistent classes (basically the com.elasticpath.domain.**/*Impl classes in the com.elasticpath.core project). The jar target in the com.elasticpath.core project will do the enhancement and install it in your Maven repository, so make sure you use it before rebuilding your web apps.

 

But sometimes, it doesn't work...

There is a workaround:

  1. In the project explorer in Eclipse, select the web app project's root node.
  2. In the Project menu, choose Properties.
  3. Select Maven, and uncheck Resolve dependencies from Workspace projects.

The projects will now use the core jar that was built last time the com.elasticpath.core/build.xml jar target was run.

 

It's still not working!

If you have persistent classes that are not in the com.elasticpath package structure, you need to modify the openjpa enhance task call in the enhance target of openjpa.xml to include your packages:

 

<openjpac>
      <config propertiesFile="${src.main.java.dir}/META-INF/persistence-renamed.xml"/>
      <classpath refid="classpath"/>
      <fileset dir="${target.classes.main.dir}">
   <include name="com/elasticpath/domain/**/impl/*.class"/>
   <include name="com/my/package/**/impl/*.class"/>
      </fileset>
</openjpac>

 

Hopefully, that will get you through most of your OpenJPA related enhance problems :-)

0 Comments Permalink

A lot of people use ant all to build their Elastic Path projects. This is convenient when you're setting up your development environment because it builds the webapps and CM client binaries, and creates projects to import into Eclipse. On the downside, it can take a long time to do all that. As Tom pointed out in a recent blog post ( http://grep.elasticpath.com/community/techblog/blog/2009/01/27/ep-61-development-environment-tips-tricks), you really don't want to run ant all all the time. It's easier and faster to build your projects individually from within Eclipse.

 

Each webapp project (com.elasticpath.sf, com.elasticpath.cm, and com.elasticpath.search) has its own build.xml file in the root of the project directory.

 

eclipse-build-xml.png

If you only need to build one webapp, right-click the build.xml file in Eclipse's Project Explorer and choose Run As->Ant Build... . Then select the target(s) you want to run.

eclipse-targets.png

 

 

For more information on available ant targets, see the Elastic Path 6.1 Developer Guide (http://docs.elasticpath.com/display/EP61DEV/2+-+Using+the+Elastic+Path+Ant+tasks).

0 Comments 0 References Permalink

If you've always dreamed of integrating Facebook/Flickr/WordPress/Mediawiki with Elastic Path, read on. Elastic Path's web services let you access some EP functionality remotely from the client platform of your choice.

First, make sure you've deployed the com.elasticpath.connect web app properly. See the section on deploying Elastic Path web apps in the Elastic Path 6.1 Deployment Guide.

Next, if you don't already know, find out what web services are available in your version of Elastic Path. Take a look at WEB-INF/web.xml in the directory where you deployed the webservices webapp. The <servlet-mapping> elements show you the URL patterns to use to access each of supported web services. For example, in my setup, I have the following:

 

<!-- Servlet mappings -->
   <servlet-mapping>
      <servlet-name>shoppingcart_web_service</servlet-name>
      <url-pattern>/shoppingcartwebservice</url-pattern>
   </servlet-mapping>

   <servlet-mapping>
      <servlet-name>order_web_service</servlet-name>
      <url-pattern>/orderwebservice</url-pattern>
   </servlet-mapping>

   <servlet-mapping>
      <servlet-name>association_web_service</servlet-name>
      <url-pattern>/associationwebservice</url-pattern>
   </servlet-mapping>

    <servlet-mapping>
       <servlet-name>import_web_service</servlet-name>
       <url-pattern>/importwebservice</url-pattern>
    </servlet-mapping>

   <servlet-mapping>
      <servlet-name>inventory_web_service</servlet-name>
      <url-pattern>/inventorywebservice</url-pattern>
   </servlet-mapping>

   <servlet-mapping>
      <servlet-name>catalog_web_service</servlet-name>
      <url-pattern>/catalogwebservice</url-pattern>
   </servlet-mapping>

   <servlet-mapping>
      <servlet-name>customer_web_service</servlet-name>
      <url-pattern>/customerwebservice</url-pattern>
   </servlet-mapping>

So, the available web services are:

  • shoppingcartwebservice (displays shopping cart items)
  • orderwebservice (view, hold, release orders)
  • associationwebservice (get product associations)
  • importwebservice (run import jobs, like from the Commerce Manager)
  • inventorywebservice (view and adjust inventory)
  • catalogwebservice (access the catalog, add products)
  • customerwebservice (view and add customers)

For our purposes, you'll want to disable the secure channel redirect in webservices\WEB-INF\conf\spring\security\acegi.xml:

 

<bean id="channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter">
      <property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
      <property name="filterInvocationDefinitionSource">
            <!-- Comment this out:
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                                \A/.*\Z=REQUIRES_SECURE_CHANNEL
</value>
     -->
            <!-- Add this: -->
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
</value>
</property>
    </bean>

Note that if you want to use SSL, there is an issue with PHP not being able to retrieve the WSDL over SSL. As a workaround, download the WSDL (and all referenced files) to your PHP host and repoint all references to the local versions.

Now is a good time to test if web services are deployed and configured properly on your system. A quick way to do this is to try a web service URL in a browser. For example:

http://localhost:11080/webservices/shoppingcartwebservice?wsdl

Note that I tested this using the Elastic Path trial installer, so you can try this out without having to do a full-blown EP deployment. You should get back an XML document that looks similar to the following.

wsdl.jpg

 

Next, install soapUI. soapUI is a great tool for inspecting WSDLs and testing web services. After it's installed, start it and create a project. (In the File menu, choose New soapUI Project.)

In the dialog, enter a project and enter the URL of the web service.

 

soapui_01.jpg


Click OK. When it's done, you'll see that the project has been added to the navigator pane on the left. Under the project, you can see the methods available in the web service. If you expand one of the methods, you'll see a Request 1 node beneath it.

soapui_02.jpg

 

Select Request 1. In the Request Properties pane (under the navigator), enter the user name and password of the Commerce Manager administrator user (or use the login of a user with web services permissions, if one exists.)

 

soapui_03.jpg


Double-click Request 1. You'll see the XML that would be sent if you called the method from a web service client.

 

soapui_04.jpg

 

You can even call that method by replacing the question marks with actual values and clicking the the green arrow button. The results are displayed in the pane on the right. (Don't forget to set up your environment properly before trying out web service calls. For example, if you want to try the shopping cart service as shown here, you'll need to log a user in to the store front and add items to the cart.)

 

I find this a great way to experiment with a service, without having to worry about the details of setting up your particular client platform. Once you've got a feel for how it works, the next step is to set up PHP and write some code.

  1. Install Apache and PHP 5.1 or later. For convenience, you can install Xampplite, which includes a complete LAMP/WAMP suite (Apache, PHP, Perl, MySQL, ...).
  2. In the php.ini file, do the following:
    1. Set soap.wsdl_cache_enabled to 0. For development purposes, you don't want to cache the WSDLs.
    2. Make sure the PHP SOAP extension is enabled (i.e., you have the following line with no semi-colon (;) at the beginning):
      extension=php_soap.dll
  3. Restart Apache.
  4. Write PHP code. Here's an example that uses the shopping cart web service to display items in a user's shopping cart:
    <html>
    <head><title>Elastic Path Web Services Test</title></head>
    <body>
    <h1>Elastic Path Web Services Test</h1>
    <?php
    // construct a SOAP client
    $wsdl = 'http://ep-ww-pmonk:7001/webservices/shoppingcartwebservice?wsdl';

    // if SSL is used, must download wsdl and xsd and refer to that
    //$wsdl = 'shoppingcartwebservice.wsdl';

    // set client options, including CM admin or web service user name and password
    $options = array(
    'trace' => 1,
    'login' => 'admin',
    'password' => 'password'
    );

    try{
    // create the service proxy
    $cartService = new SoapClient($wsdl, $options);

    // fill an object with the parameters
    $getCartRequestObj->UserId = 'paul.monk@example.org';
    $getCartRequestObj->StoreCode = 'SNAPITUP';

    // wrap that object (container property name must be the xxxRequest type in the WSDL (e.g., GetCartRequest))
    $getCartObj->GetCartRequest = $getCartRequestObj;

    // call the service method
    $result = $cartService->getCart($getCartObj);

    // display the results
    print '<p>Your shopping cart contains these items:</p><table><tr><th>Quantity</th><th colspan="2">Item</th></tr>';
    $cart = $result->return->shoppingCart;
    foreach($cart->CartItems->CartItem as $cartItem){
    $tr ='<tr>';
    $qty = $cartItem->Quantity;
    $tr .= '<td>' . $qty . '</td>';
    $productSku = $cartItem->ProductSku;
    $tr .= '<td>' . $productSku->Code . '</td>';
    $tr .= '<td><img src="http://ep-ww-pmonk.elasticpath.net:7001/storefront/renderImage.image?imageName=' .
    $productSku->Image->FileName . '&width=200&height=200&padding=0"/></td></tr>';
    print $tr;
    }
    print '</table>';

    }
    catch(SoapFault $fault){
    print '<pre>';
    var_dump($fault);
    var_dump(htmlspecialchars($cartService->__getLastRequest()));
    var_dump(htmlspecialchars($cartService->__getLastResponse()));
    print '</pre>';
    }
    ?>
    </body>
    </html>

For more info on PHP's SOAP extension, see the PHP SOAP documentation.

Once you've experimented a bit, you may want to start looking at exposing more of Elastic Path's functionality as web services. You'll want to look at the section on web services architecture in the Elastic Path 6.1 Developer Guide.

0 Comments 0 References Permalink