Our use of the OpenJPA APIs has typically been limited to the methods of the JPA EntityManager, which we wrap with our persistence engine class, JpaPersistenceEngineImpl.

We also make limited use of the OpenJPAQuery, EntityTransaction and FetchPlan classes.

However, OpenJPA provides some other very useful APIs, which can be used to inspect and/or control the way we load and persist our objects. Classes of particular interest include:

 

PCEnhancerUsed to enhance persistent classes from the metadata.
PCRegistryTracks registered persistence-capable classes.
ReflectionReflection utilities used to support and augment enhancement. Provides access to the fields and getters/setters of a persistent class.
OpenJPAStateManagerEach state manager manages the state of a single persistence capable instance. This can be used to inspect field dirtiness, access low-level fetch and store methods, and much more.
BrokerThe broker is the primary interface into the OpenJPA runtime. This can be used to add listeners for lifecycle-related events.
ClassMetaDataProvides information about the metadata of a class.
FieldMetaDataProvides information about the metadata of a field.
JPAFacadeHelperHelper class for switching between OpenJPA's JPA facade and the underlying Broker kernel. Useful for getting access to the metadata and the broker.
OpenJPAPersistenceHelper methods, including methods for casting to the JPA facades for the entity manager and entity manager factory.

 

In this post, we'll see first-hand the usefulness of these APIs by building an enhanced version of the persistence engine - one that logs JPA loads and saves.

Determining the dirty state of an object

The OpenJPA entity manager can be used to determine the dirtiness of an object as follows:

protected void logDirtyState(final Persistence object) {
  OpenJPAEntityManager openjpaEM = OpenJPAPersistence.cast(getEntityManager());
  if (openjpaEM.isDirty(object)) {
    LOG.info(object.getClass().getName() + " (" + object.getUidPk() + ") is dirty");
  }
}

 

Note that this only works for PersistenceCapable objects, i.e. classes that have been enhanced by OpenJPA. So it can't be used to determine the dirty status of every object that may be involved in a save or merge. For example, if an object has a String field that has been changed, calling isDirty on that field will return false because String is not an enhanced class.

However, you can find out exactly which fields of a PersistenceCapable object are dirty as follows:

protected void logDirtyFields(final PersistenceCapable pcObject) {
  OpenJPAStateManager manager = (OpenJPAStateManager) pcObject.pcGetStateManager();
  ClassMetaData metaData = JPAFacadeHelper.getMetaData(getEntityManager(), pcObject.getClass());
  BitSet dirtySet = manager.getDirty();
  for (int dirtyIndex = dirtySet.nextSetBit(0); dirtyIndex >= 0; dirtyIndex = dirtySet.nextSetBit(dirtyIndex + 1)) {
    FieldMetaData fieldMetaData = metaData.getField(dirtyIndex);
    LOG.info(pcObject.getClass().getName() + " has a dirty field: " + fieldMetaData.getName());
  }
}

 

The state manager's getDirty() method will give us a BitSet indicating the indexes of the fields that are marked as dirty. We use the JPAFacadeHelper to get the metadata for the class, and then we can get the metadata for each of the dirty fields.

Examining the dirty state of an entire object graph

The above demonstrated how to determine whether an object is dirty, and how to find out which fields of that object are dirty. But what if some of those fields are themselves PersistenceCapable objects or collections of objects?

Well, OpenJPA makes it relatively easy to traverse the object graph. Let's change the above code to do a bit more with the dirty fields:

for (int dirtyIndex = dirtySet.nextSetBit(0); dirtyIndex >= 0; dirtyIndex = dirtySet.nextSetBit(dirtyIndex + 1)) {
  FieldMetaData fieldMetaData = metaData.getField(dirtyIndex);
  Method method = Reflection.findGetter(pcObject.getClass(), fieldMetaData.getName(), true);
  logDirtyField(fieldMetaData.getName(), Reflection.get(pcObject, method), (Persistence) pcObject);
}

 

The Reflection class gives us access to the getter for the given field and thus we can traverse further into that field's object, as follows:

protected void logDirtyField(final String fieldName, final Object object, final Persistence parent) {
  if (object instanceof Collection) {
    for (Object member : (Collection<Object>) object) {
      logDirtyField(fieldName + " (member)", member, parent);
    }
    return;
  }
  if (object instanceof Map) {
    for (Map.Entry<Object, Object> entry : ((Map<Object, Object>) object).entrySet()) {
      logDirtyField(fieldName + " (" + entry.getKey().toString() + ")", entry.getValue(), parent);
    }
    return;
  } 
  if (object instanceof PersistenceCapable) {
    logDirtyFields((PersistenceCapable) object);
  } else {
    LOG.info(parent.getClass().getName() + " (" + parent.getUidPk() + ") has dirty field " + fieldName 
                         + " with value " + object.toString());
}

 

As you can see, if we call this on a field that is a collection or map, it will just iterate through the members and pass each resultant member object into the logging method. If the field (or member) is itself a PersistenceCapable object, we call the original method to examine that object's dirty fields. Finally, when the field (or member) is not a collection, map, or PersistenceCapable object, we just log the fact it was found to be dirty.

To demonstrate this behaviour, if we edit a product and make some changes to its fields and attributes, we get a log similar to the below:

com.elasticpath.domain.attribute.impl.ProductAttributeValueImpl (169132) has dirty field shortTextValue with value Nice LCD display
com.elasticpath.domain.attribute.impl.ProductAttributeValueImpl (166706) has dirty field booleanValue with value false
com.elasticpath.domain.catalog.impl.ProductImpl (102808) has dirty field hidden with value true
com.elasticpath.domain.catalog.impl.ProductImpl (102808) has dirty field lastModifiedDate with value Mon Dec 08 18:10:42 PST 2008
com.elasticpath.domain.catalog.impl.ProductLocaleDependantFieldsImpl (105692) has dirty field displayName with value Simon's Kodak EasyShare C310...

Examining the object graph loaded by JPA

When we ask JPA to load an object, we often get more than we bargained for. What gets loaded depends on the relationships the object has to other objects, the annotated FetchType, and any load tuners or fetch groups defined. More often than not we get a sizeable object graph.

OpenJPA provides a convenient way to examine what gets loaded without having to use something like reflection to manually traverse the object graph. Instead, you can use define a LoadListener - just one of the many listeners for lifecycle-related events that JPA allows you to add.

We define our logging load listener as follows:

public class LoggingLoadListener implements LoadListener {
 
  public void afterLoad(final LifecycleEvent event) {
    Persistence sourceObject = (Persistence) event.getSource();
    LOG.log(logLevel, "Loaded " + sourceObject.getClass().getName() + " with UIDPK " + sourceObject.getUidPk());
  }
 
  public void afterRefresh(final LifecycleEvent event) {
    // Do nothing - we don't care about refresh events.
  }
 
}

 

The lifecycle methods get called with a LifecycleEvent object which provides the event's source object.

Note: If you want to listen to multiple kinds of JPA events, you consider subclassing OpenJPA's AbstractLifecycleListener which implements all the lifecycle listeners and delegates them to a single method eventOccurred(LifecycleEvent event).

To tell OpenJPA to call this listener whenever it loads, we use the JPAFacadeHelper to get a hold of the underlying Broker as follows:

private void addLoadListener() {
    OpenJPAEntityManager openjpaEM = OpenJPAPersistence.cast(getEntityManager());
    Broker broker = JPAFacadeHelper.toBroker(openjpaEM);
    broker.addLifecycleListener(new LoggingLoadListener(), null);
}

A logging persistence engine

Now we know how to get this information, how do we seamlessly integrate it into our Elastic Path application? The easiest way is to create a subclass of the main Elastic Path JPA persistence engine, JpaPersistenceEngineImpl. We then override the persistence methods to call our logging functions.

For example:

public class LoggingPersistenceEngineImpl extends JpaPersistenceEngineImpl {
 
  @Override
  public <T extends Persistence> T merge(final T object) throws EpPersistenceException {
    logDirtyState(object);
    return super.merge(object);
  }
 
  @Override
  public void save(final Persistence object) throws EpPersistenceException {
    logDirtyState(object);
    super.save(object);
  }
 
  @Override
  public <T extends Persistence> T get(final Class<T> persistenceClass, final long uidPk) throws EpPersistenceException {
    addLoadListener();
    return super.get(persistenceClass, uidPk);
  }

 

...and similarly for the other methods (retrieve, retrieveByNamedQuery, etc.).

Then we just change the Spring wiring to use our new logging persistence engine as follows (found in conf/spring/dataaccess/openjpa/openjpa.xml)

<bean id="persistenceEngineTarget"
      class="com.elasticpath.persistence.impl.LoggingPersistenceEngineImpl">
  <property name="entityManager" ref="sharedEntityManager"/>
  <property name="sessionFactory" ref="sessionFactory"/>
</bean>

Enhancement - limiting the list of classes to be logged

There may be classes you want to exclude from the logging - such as search index related classes that may result in too much log noise. One easy way of being able to configure the set of classes you want logged is just to define the list through Spring. For example, we add the following property to the persistence engine bean definition:

<property name="loggingClasses">
  <set>
    <value>com.elasticpath.domain.attribute.impl.AbstractAttributeValueImpl</value>
    <value>com.elasticpath.domain.attribute.impl.AttributeGroupAttributeImpl</value>
    <value>com.elasticpath.domain.attribute.impl.AttributeImpl</value>
    <value>com.elasticpath.domain.attribute.impl.ProductAttributeValueImpl</value>
    <value>com.elasticpath.domain.attribute.impl.ProductTypeProductAttributeImpl</value>
    <value>com.elasticpath.domain.catalog.impl.AbstractPriceImpl</value>
    <value>com.elasticpath.domain.catalog.impl.AbstractPriceTierImpl</value>
    <value>com.elasticpath.domain.catalog.impl.CatalogProductPriceImpl</value>
    <value>com.elasticpath.domain.catalog.impl.AbstractLocaleDependantFieldsImpl</value>
    <value>com.elasticpath.domain.catalog.impl.ProductAssociationImpl</value>
    <value>com.elasticpath.domain.catalog.impl.ProductImpl</value>
    <value>com.elasticpath.domain.catalog.impl.ProductLocaleDependantFieldsImpl</value>
    <value>com.elasticpath.domain.catalog.impl.ProductPriceImpl</value>
    <value>com.elasticpath.domain.catalog.impl.ProductPriceTierImpl</value>
    <value>com.elasticpath.domain.catalog.impl.ProductTypeImpl</value>
  </set>
</property>

 

Then we add the relevant property, getter and setter to our class:

private Collection<String> loggingClasses;
 
public Collection<String> getLoggingClasses() {
  return loggingClasses;
}
 
public void setLoggingClasses(final Collection<String> loggingClasses) {
  this.loggingClasses = loggingClasses;
}

 

Finally, we add the following to our logDirtyState and afterLoad methods:

if (!getLoggingClasses().contains(object.getClass().getName())) {
  return;
}

Enhancement - configurable log level

Suppose we want to allow the log level used by our logging persistence engine to be configurable through spring. We can just add a getter and setter as follows:

private Level logLevel;
 
  public String getLogLevel() {
    return logLevel.toString();
  }
 
  public void setLogLevel(final String logLevel) {
    this.logLevel = Level.toLevel(logLevel);
  }

 

Then we just use the log4j log(Priority priority, Object message) method whenever we log. Make sure to set this in spring by adding the property to your bean definition:

<property name="logLevel" value="INFO"/>



0 Comments Permalink

Automated CM client testing

Posted by Andy Wang Mar 17, 2009

Test automation can be a great time-saver, but it requires proper planning, implementation, and maintenance, and it should be treated the same as any other software development project. At Elastic Path, we recently started to automate the testing of the Commerce Manager client. In the past, we used automation in several areas (JUnit for unit testing, FIT for business layer testing, and Web load for load testing), but CM client testing was completely manual. Our goals for automating CM client testing were:

 

  • Reduce time spent on tedious, repetitive testing. After each build, we perform smoke and regression testing. Some of this testing is automated. Our plan was to integrate the automated CM client testing scripts with our autodeploy build process, so that the build server would automatically copy the CM client application and the testing scripts to CM client testing server and launch the testing scripts. By automating CM client testing and integrating it with our other automated test processes, we would be able to run automated tests after work hours and check the results the next morning. This would allow our QA testers to focus on more complex, manual tests.

  • Increase result accuracy. Test results can be generated automatically after the automated tests are complete, and if the test scripts were written correctly, the results will always be accurate. For CM client testing, logs and results could be automatically retrieved from the CM server and sent as part of notification email so we could immediately know how many test cases were successful and how many failed.

  • Improve defect logging. The sooner a defect is found, the less expensive it is to fix. After logs are generated from the automated tests, they are automatically imported into our test case repository, Aptest. This allows QA team members to quickly create bug report based on failed test cases.

 

The first thing we needed to do was to select an automated test tool that met our requirements. We used the following criteria:


  • Ease of use: is there a steep learning curve? How quickly can we expect to be productive?

  • Integration: how well does it integrate with our tools, product, and processes (Java, ODBC, etc.)?

  • Platform support: is it platform independent? If not, what specific platform does this tool support?

  • Functionality: Does it support test recording and playback?

  • Result reporting: What output formats does it support (XML, HTML)?

  • Language: What scripting languages does it support? Do we need to learn new scripting language?

  • Price: Is it within the company's budget?

  • Support and training: How is the response time for support incidents? How is the documentation? Can they provide training if we need it?


We looked at many automated testing tools, including TestComplete, QFTest, and Squish for Java. We eventually selected froglogic's Squish for Java (http://www.froglogic.com/pg?id=Products&category=squish&sub=editions&subsub=java). It is especially well suited for Eclipse RCP applications, so it was a good choice for the CM client. Also, it supports commonly used scripting languages such as JavaScript and Python and we were already using JavaScript as our automation script language in other testing areas.


Next, we needed to decide which test cases to automate. For example, email notification was not a good choice, because it is difficult to send a message and check if it arrived using the same tool.

 

After that, we needed to prioritize test cases that we wanted to automate. We started with some easy ones, so we could gain confidence as our understanding of the tool increased. The CM client application user interface is very stable and our test cases in Aptest are in very strong form, so we were able to automate many of our test cases quickly and easily. Initially, our focus has been on automating the catalog management related areas of the CM client application. We will continue to increase our automated test coverage in the CM client over time.

CM client test automation is an important project. Initially, it required an important investment of time and resources to evaluate the tools, develop the strategy for automating our manual test cases, and finally to write the automated test scripts. This last step is still ongoing, but we have already seen a significant return on our investment in terms of better integration with existing automated tests and improved test result accuracy. Ultimately, this helps us to improve the overall quality of our product.

0 Comments Permalink

Alternative title: How to Make Your Life Easier and Not Go Bald from Pulling Your Hair out Trying to Keep Current on a Constantly Evolving and Shifting Codebase

 

This is probably the biggest and most avoided question from all of our successfully launched Professional Services projects. How can we upgrade to the greatest and latest on our heavily customized EP project? Let's be clear that performing a point release upgrade, or even consuming the latest sprint of code from our extremely productive Product Development group isn't trivial. A full version upgrade is even more daunting and usually heavily balanced against the cost of re-implementing from scratch.

 

First off, hopefully you are following our advice to try to avoid customizing EP code as far as possible, as noted in our documentation and tutorials. Following this methodology will make your life a lot easier, as upgrading core EP classes that have only minor changes will be a lot easier.

 

With that in mind, the best method through tried and tested experience, from brute force patching and merging, to re-implementing large customizations, is to take advantage of the merge tools from your chosen source control software (you are using source control I hope! If not, this book will be your best friend). Why do all the manual merge work when X does it so well already (X being subversion, perforce, cvs, vss, git, the next big flavor of the month). The key to leveraging your source control software to facilitate an upgrade is the concept of a vendor branch.

 

Working with a vendor branch is straight forward. This is applicable to any 3rd-party library you are using and modifying, not just EP. Take a quick gander at these various interweb sites for a better explanation than i can reasonably provide. For our purposes, a vendor branch for EP is an untainted, pristine copy of EP source. Don't just take our beloved zip of source code and stash it in your network drive! Check it in and show it off! You can branch your customized codeline off the vendor branch and start working away with the enthusiasm of a frenzied squirrel in the spring. The next time a new version of EP comes along, simply drop it directly on top of the current vendor branch of EP in your repository. With this new EP revision, you can perform your normal merge routine of just the diff between these EP revisions and merge the delta of changes into your customized eCommerce solution. And, if you have been following the procedures to avoid modifying EP source, conflicts should be kept to a minimum, files will just resolve themselves, and you can go out for a quick pint. Sure sounds easy huh? But how about in real life?

 

Well, let's see how we've taken this and made our lives easier at a client site in Perforce. I'm not the biggest Perforce fan, but I sure appreciate it's advanced branching and visual merging capabilities. We've set this up as follows:

 

  • EP_IMPORT, our EP vendor branch containing 100% pure EP 6.1 eCommerce goodness
  • Stable - our main development branch, full of customizations of the greatest and latest variety

 

Now, presume we're taking in change after massive change from our Product Development group. They've given us EP 6.1.1! Hooray! But how do we get this into our source control? Looks easy enough. Drop it on top of EP_IMPORT and check it in? Whoa, hold on there, code cowboy. This baby probably won't compile just with that.

 

The main things to remember is to collect three things with each new drop of code:

 

  1. All files that have been updated in 6.1.1
  2. All files that have been deleted in 6.1.1
  3. All files that have been added in 6.1.1

 

Once you have all three, you can mark the relevant files for delete, add and update. I've taken a short cut to calculate the files that fall into the above three buckets in this case. I have access to our internal subversion repository at EP, so I'm just going to take a log of files taken in by 'svn update' when updating from the 6.1 tag and the new 6.1.1 tag. You can essentially calculate the same bucket of files using the diff command. But in my case I'm just taking a shortcut and doing:

 

svn update > merge.txt

 

This logs out for us a lot of lines of this variety:

U    com.elasticpath.core/WEB-INF/src/main/java/com/elasticpath/service/rules/PromotionRuleDelegate.java
D    com.elasticpath.core/WEB-INF/src/main/java/com/elasticpath/service/misc/impl/WorkAroundOpenJPAFetchPlanHelperImpl.java
A    com.elasticpath.core/WEB-INF/src/main/java/com/elasticpath/service/misc/impl/OpenJPAEventListeningFetchPlanHelperImpl.java

 

With some help from sed (for the non-Unixers, feel free to grab and install cygwin, or your favorite port of the beloved Unix command line tools), I'm going to parse the log and split it into three files:

cat merge.txt | sed \-e '/D /d' | sed \-e '/U /d' | sed \-e 's/A[ ]*//' > adds.txt
cat merge.txt | sed \-e '/A /d' | sed \-e '/U /d' | sed \-e 's/D[ ]*//' > deletes.txt
cat merge.txt | sed \-e '/A /d' | sed \-e '/D /d' | sed \-e 's/[ ]*[U]*[ ]*//' > updates.txt

 

Now, Perforce is a bit finicky in that I need to mark which files I'm going to edit to remove the read-only permissions of relevant files. Sure, I can mark every file for editing, but let's play it safe:

pwd
/perforce/depot/branches/EP_IMPORT/
cat updates.txt | xargs p4 edit -f

 

Next, time to mark the deleted files for removal:

cat deletes.txt | xargs p4 delete -f

 

Finally, it's time to unzip on top all the files from the new EP 6.1.1 source zip and mark our new files for add:

tar -xvzf EP6.1.1_Export.tgz .
cat adds.txt | xargs p4 add -f

 

That does it! Now check that all in! Now we have EP 6.1.1 in our EP_IMPORT branch. Time to use the impressive P4V tool from Perforce to integrate from the EP_IMPORT branch into the Stable branch. Since we've avoided modify EP source as much as possible, the majority of the changes should auto-resolve. Clean up some of the conflicts and tweak the customizations as necessary, run the normal routine of compile, checkstyle, pmd, unit tests, integration tests and your project should now be ready to go. Okay, that's a gross over-simplication of the conflict resolution and regression testing process that goes hand in hand with a version upgrade, but hopefully you see how a vendor branch does wonders in facilitating an easier upgrade process with custom code. No more awkward patch files and brute force.

 

Assuming you don't have access to our subversion to get a hold of the list of changes, you can use diff to log file differences and files missing from the 6.1 and 6.1.1 codebases.

 

diff -r /perforce/depot/branches/EP_IMPORT /tmp/EP611/ > diffs.txt

 

This should give you a nice log that is easily parsed into the same add/delete/update buckets. So now you're freshly armed to do the upgrade that has been haunting you late at night. Vendor branches and merge tools are your new best friends! Happy upgrading and merging.

 

Drew

0 Comments Permalink

Acegi Security is a security framework for J2EE applications. We use it to secure access to the Commerce Manager server and certain pages in the storefront. If you're doing any work in those areas, you'll want to get familiar with Acegi.

 

Note: Elastic Path 6.1 uses Acegi Security version 1.0.5. Acegi Security was integrated into more recent releases of the Spring framework and is now called Spring Security.

 

In general, security is concerned with two things:

  • authentication (determining who you are)

  • authorization (controlling what you are allowed to do in a system).

 

For authentication, Acegi supports a variety of flavors, including Basic Authentication, digest authentication, client certificate authentication, and form based authentication. You can also provide your own authentication system if you have special requirements. Elastic Path's security system relies mainly on form based authentication for the storefront and Basic Authentication for CM server access from the CM client.

 

For authorization, there are three main areas of interest: web requests, method invocations, and access to individual domain object instances. Elastic Path uses web request authorization (at the URL level) to control access to certain functionality.

 

Here is an example of one Acegi customization our team did. Our client needed to integrate their authentication system with the Elastic Path storefront. We needed to disable Elastic Path's login functionality so that the client's customers could only log in through their system, which would provide a link to redirect customers to the storefront. The link would contain some customer information that Elastic Path would use to authenticate the customer. If authentication was successful, the customer would be allowed to browse the store and purchase products. If authentication failed, the customer would be redirected to an error page with a link back to the client's system where they could try to log in again. Except for the error page, all pages needed to be displayed securely (HTTPS only). The following flow diagram shows how it looked.

 

Acegi blog diagram1-small.PNG

 

To summarize, the security requirements were as follows:

  1. The entire store must be secured, including category pages, product detail pages, etc.

  2. The error page must not be secured.

  3. Valid customers must be redirected to the landing page.

  4. Invalid customers must be redirected to the error page.

  5. Valid customers must be assigned to the ROLE_CUSTOMER role, which gives them access to all the store resources.

 

We needed to make several changes to the Acegi configuration files for the storefront, which are in WEB INF/conf/spring/security. They are named acegi.xml and acegi.xml.vm. (When you build the storefront webapp, acegi.xml is overwritten, so you should make your changes in acegi.xml.vm.)

 

Securing the store pages

We modified the channelProcessingFilter bean definition to force all pages to be secured. The regular expression \A/.*\Z matches all URLs and maps them to REQUIRES_SECURE_CHANNEL. This ensures that all pages are displayed over HTTPS only. (To match URLs, you can use either regular expressions or Ant-style paths.)

 

<bean id="channelProcessingFilter" class="org.acegisecurity.securechannel.ChannelProcessingFilter">

<property name="channelDecisionManager">

<ref bean="channelDecisionManager"/></property>

      <property name="filterInvocationDefinitionSource">

            <value>

            CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

<!-- all of the pages are secured -->

                 \A/.*\Z=REQUIRES_SECURE_CHANNEL

      </value>

      </property>

</bean>

 

 

Customers must have ROLE_CUSTOMER to access store resources

In the filterInvocationInterceptor bean definition, we replaced the default URI patterns with a single wildcard pattern to map all URLs to ROLE_CUSTOMER. Now, only customers with the ROLE_CUSTOMER role are authorized to make web requests in the application.

<bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">

<property name="authenticationManager">

<ref bean="authenticationManager"/>

</property>

      <property name="accessDecisionManager">

<ref local="accessDecisionManager"/>

</property>

     <property name="objectDefinitionSource">

           <value>

CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

\A/.*\Z=ROLE_CUSTOMER

     </value>

</property>

</bean>

 

Make the error page non-secure

The error page does not require authorization, so we did not want it to execute through the filterInvocationInterceptor. We added login-error.ep to the filterChainProxy bean to invoke all the other filters, as shown in the following example.

 

<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">

    <property name="filterInvocationDefinitionSource">

        <value>

        CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON

        \A/login-error.ep.*\Z=channelProcessingFilter, httpSessionContextIntegrationFilter, logoutFilter, onePageLogoutFilter, authenticationProcessingFilter, exceptionTranslationFilter

 

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

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

        </value>

    </property>

</bean>

 

Remember that the order of URI patterns in filterChainProxy is critical; they are processed in order, so specific URI patterns must appear before generic, wildcard patterns. Now, when the error page was displayed, Acegi would not check to see if the customer has the required roles to access the current  resource.

 

Direct customers based on their validity

We wanted valid customers to be directed to landing.ep, while invalid customers would have to face an error page, login-error.ep. In the authenticationProcessingFilter bean, we set the authenticationFailureUrl to the error page, login-error.ep and we set the defaultTargetUrl to the landing page, landing.ep.

 

<bean id="authenticationProcessingFilter" class="com.elasticpath.sfweb.filters.EpXXXXAuthenticationProcessingFilter">

      <property name="webCustomerSessionService">

<ref bean="webCustomerSessionService"/>

</property>

<property name="requestHelper">

<ref bean="requestHelper"/>

</property>

<property name="authenticationManager">

<ref bean="authenticationManager"/>

</property>

      <property name="authenticationFailureUrl">

<value>/login-error.ep</value>

</property>

      <property name="defaultTargetUrl">

<value>/landing.ep</value>

</property>

      <property name="filterProcessesUrl">

<value>/j_acegi_security_check.ep</value>

</property>

</bean>

 

<!-- direct access to EP without login to client's system, will direct to login-error.ep -->

<bean id="authenticationProcessingFilterEntryPoint" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">

    <property name="loginFormUrl">

<value>/login-error.ep</value>

</property>

      <property name="forceHttps">

<value>true</value>

</property>

</bean>

 

Other changes

We only needed to do a couple other things to complete the customization. We created the EpXXXXAuthenticationProcessingFilter controller to extract customer information from the URL and authenticate the customer. We removed sign-in.ep from url-mapping.xml to prevent access to the Elastic Path login page. That was it.

 

One of the great things about Acegi is that it can do a lot with only a few small configuration changes. For more information, go to the Acegi security site (http://www.acegisecurity.org).

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

Performance testing is a task often left to the last stages of software development, after the code is checked in, passed QA for functional testing, and no longer fresh in the minds of developers who wrote it. With JUnitPerf, you can leverage your existing suite of JUnit tests to get performance measurements while you're developing. The framework provides easy to use decorator classes for creating:

 

  • Timed tests to measure execution time
  • Load tests for measuring how your code runs with multiple requests

 

Performance tuning should always be done with a performance goal in mind, and JUnitPerf shouldn't be the end all measurement tool for the performance your code. Instead it is more useful for

 

  • Getting a basic idea of where your execution bottlenecks may lay
  • Evaluating the performance difference between multiple technical solutions
  • Uncovering immediate concurrency issues

 

JUnitPerf uses decorator classes to wrap existing JUnit tests. This makes it possible to add performance measurements even to HttpUnit or DbUnit tests.

In our examples we will use the following unit test

public class SimpleUnitTest extends TestCase {
...
  public void test1() {
      ... //some operations
  }
...

 

A TimedTest can be used to provide a time limit for your running tests. Tests will fail if they take longer than the given time limit to execute. This is useful if you know you don't want to exceed a certain time constraint. Here we are running all tests within the SimpleUnitTest class as a suite with a time limit of 1 second.

public class SimplePerfTest {
  public static void main(String[] args) {
    int timeLimit = 1000;
    Test timedTest = new TimedTest(new TestSuite(SimpleUnitTest.class), timeLimit);
    junit.textui.TestRunner.run(timedTest);
  }
}

 

You can use LoadTest to create a load test with a specified number of threads(JVM will constrain the upper thread limit) running your test case either simultaneously or at randomly dispersed start times. We will do the simplest example of running a test suite with 100 threads concurrently.

public class SimplePerfTest {
  public static void main(String[] args) {
    int load = 100;
    Test loadTest = new LoadTest(new TestSuite(SimpleUnitTest.class), load);
    junit.textui.TestRunner.run(loadTest);
  }
}

 

In a more complex example of using LoadTest, we can simulate 100 threads running our test code, with a random delay before the addition of each concurrent thread. In addition to the RandomTimer class to specify a delay, there is also the ConstantTimer to specify a constant delay between thread addition.

public class SimplePerfTest {
  public static void main(String[] args) {
    int threads = 100;
    int delay = 10;
    int randomVariation = 20;
    Timer randomTimer = new RandomTimer(delay, randomVariation);
    Test loadTest = new LoadTest(new TestSuite(SimpleUnitTest.class), threads, randomTimer);
    junit.textui.TestRunner.run(loadTest);
  }
}

 

Lastly, we can combine both TimedTest, LoadTest, and RepeatedTest (part of JUnit) to string together a rather sophisticated concurrent performance test. Here's a test that concurrently runs your unit test in 100 threads, each repeating 10 times before ending, with a time limit of 2 seconds for the test to complete. Threads are being added after every 10ms by using the ConstantTimer.

public class SimplePerfTest {
  public static void main(String[] args) {
    int timeLimit = 2000;
    int threads = 100;
    int delay = 10;
    int repetitions = 10;
    Timer randomTimer = new ConstantTimer(delay);
    Test timedTest = new TimedTest(new TestSuite(SimpleUnitTest.class), timeLimit);
    Test repeatedTest = new RepeatedTest(timedTest, repetitions);
    Test loadTest = new LoadTest(repeatedTest, threads, randomTimer);
    junit.textui.TestRunner.run(loadTest);
  }
}

 

JUnitPerf tests are great for quickly spotting performance problems early in the development cycle, requiring very little extra work if your regular coding practice already includes JUnit, and extensions of JUnit such as HttpUnit and DbUnit. With multithreaded LoadTests, it can also be helpful in uncovering concurrency issues normally difficult to discover with code inspection or other forms of testing. There is unquestionable value in performing performance testing on a fully functional and integrated system, but you can cut down both cost and risk to your project by validating the performance of your designs early, especially architectural decisions that may be hard to change later on (though being Agilists, we always try to make change easy. But that's a topic for another day).

 

 

Resources:

http://clarkware.com/software/JUnitPerf.html

Home of JUnitPerf

 

http://junit.org

Home of JUnit

 

http://www.dbunit.org/

Home of DbUnit

 

http://httpunit.sourceforge.net/

Home of HttpUnit

 

http://www.ibm.com/developerworks/java/library/j-cq11296.html

Performance testing with JUnitPerf

0 Comments Permalink

Automated Import Jobs

Posted by Tony McAffee Mar 3, 2009

Elastic Path Commerce includes out-of-the-box import tools, which can add or update products, inventories, prices, etc. Unfortunately, import job execution is manual and requires user effort every time the import is to be run. Fortunately, automating the execution process is a fairly straightforward customization.

 

For example, assume that you want to run an import job to import inventory from an ERP system every day at three in the morning. The following high level steps will be necessary to meet this requirement.

 

  1. Create an import job.
  2. Expose the import job execution facility for ease of configuration.
  3. Define and configure a quartz job for automation.
  4. Schedule the data file transfer via FTP.

 

Creating an import job

 

Before you can run an import job, either manually or automatically, you need to define the import job in Commerce Manager.You need to map the columns of the CSV data files to data fields in Elastic Path. This is documented in the Commerce Manager User Guide, which you can get from the Elastic Path documentation site.

 

Exposing the import job execution facility

 

Let's define a class called AutomatedImportServiceImpl, which will serve as our interface to the import job facility. This class will have a few attributes and a trigger method. The following snippet illustrates the heart of the customization.

 

    private ImportService importService;
    private CmUserService cmUserService;
    private String importJobName;
    private String importJobOwnerUsername;

       public void triggerImport() {
        CmUser cmUser = cmUserService.findByUserName(importJobOwnerUsername);
        final ImportJob importJob = importService.findImportJob(importJobName);
        importJob.setCmUser(cmUser);    
          importService.runImportJob(importJob);
       }

 

The importService and cmUserService attributes will be injected by Spring. The importJobName and importJobOwnerName properties can be configured to allow different import jobs. The actual code required to invoke the import job is quite minimal as can be seen in the triggerImport method. (Note that the cmUser lookup is necessary because part of the job post processing is to email a status report to the user running the job.)

 

The next step is to define the Spring service. The best place to do this is in the cmserver web project's serviceCM.xml Spring configuration file.

 

    <bean id="updateInventoryService" class="com.example.service.dataimport.impl.AutomatedImportServiceImpl">
        <property name="importService">
            <ref bean="importService" />
        </property>
        <property name="cmUserService">
            <ref bean="cmUserService" />
        </property>
        <property name="importJobName">
            <value>UpdateInventory</value>
        </property>
        <property name="importJobOwnerUsername">
            <value>admin</value>
        </property>
    </bean>

 

Additional services could be defined with different import job names to automate other import jobs.

 

Defining the quartz job

 

The quartz job configuration is straightforward. Define a quartz job to invoke the service previously created along with a trigger to activate the quartz job.

 

    <bean id="updateInventoryJob" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject">
            <ref bean="update
InventoryService" />
        </property>
        <property name="targetMethod">
            <value>triggerImport</value>
        </property>
        <property name="concurrent">
            <value>false</value>
        </property>
    </bean>
 
          <bean id="update
InventoryTrigger" class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail">
            <ref bean="
updateInventoryJob" />
        </property>
        <property name="cronExpression">
            <value>0 0 3 * * ?</value>
        </property>
    </bean>

 

Scheduling the data file transfer via FTP

 

When an import job is executed, the data file needs to be transfered to the assets/import directory on the server. When an import job is executed manually, the Commerce Manager takes the user specified data file and sends it to server, usually via FTP. Your automated import process needs to do this as well. One way to do this by creating a cron job on the ERP system to FTP the data file before the import job is scheduled to run.

 

Other considerations

 

Before implementing an automated import process, the following should be taken into consideration:

 

  • Automated jobs should usually be scheduled during low-activity periods to avoid affecting the performance of the cmserver web application.
  • Failure conditions are not well represented in the example. What should happen if the FTP fails?  Should the import job be deleted after processing to prevent reprocessing?  If the import fails should an email be sent to an administrator?  Beefing up AutomatedImportServiceImpl to be smarter about failure conditions would be a wise thing to do.
  • Logging is always a good thing. Think about logging start/end processing times and perhaps even number of records processed.
  • Other automation techniques can be applied. For instance, instead of quartz, a polling method could be implemented such that files are processed as soon as they are available.
0 Comments 0 References Permalink