Consuming FedEx Web Services from Grails using Apache CXF

A recent development task was integrating a FedEx web service consumer (client) in a Grails application. Users require the ability to generate FedEx shipping labels from within the app. To accomplish this, we call the SOAP based web services provided by FedEx.

I decided to use Apache CXF for the consumer code.

There were a couple of tricky parts:

1) The FedEx sample client code is Axis based.
2) Integrating the Apache CXF code, libraries and configuration into Grails
3) FedEx service is a secure (https) call

Let’s take a look at how each one of these items can be addressed:

1) The FedEx sample client code is Axis based.
This one is easily solved by using CXF wsdl2java against the FedEx wsdl. CXF tends to provide List based API instead of Arrays that Axis wsdl2java seems to prefer. So, if you review the FedEx sample code which is for Axis, you’ll see much use of Arrays; just switch those over to use Lists.

2) Integrating the CXF code with Grails
Like Grails, CXF also relies on Spring Framework. Create a cxf.xml file for CXF configuration and drop it in the “conf/spring”
directory in your Grails app.

conf/spring/cxf.xml:

<beans xmlns="http://www.springframework.org/schema/beans">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:sec="http://cxf.apache.org/configuration/security"
xmlns:http="http://cxf.apache.org/transports/http/configuration"
xsi:schemalocation="http://cxf.apache.org/configuration/security
http://cxf.apache.org/schemas/configuration/security.xsd
http://cxf.apache.org/transports/http/configuration
http://cxf.apache.org/schemas/configuration/http-conf.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
 
<import resource="classpath:META-INF/cxf/cxf.xml" />
<import resource="classpath:META-INF/cxf/cxf-extension-soap.xml" />
<import resource="classpath:META-INF/cxf/cxf-servlet.xml" />
 
<http:conduit name="{http://fedex.com/ws/ship/v3}ShipServicePort.http-conduit">
<http:tlsclientparameters securesocketprotocol="SSL">
</http:tlsclientparameters></http:conduit>
</beans>

Include this file from the Grails/Spring conf/spring/resources.xml file:
<beans>
<import resource="ImageCaptchaFactories.xml" />
<import resource="ImageCaptchaService.xml" />
<import resource="EmailService.xml" />
<import resource="cxf.xml" />
</beans>

Next, download the GroovyWS library from the Groovy site and drop it in your Grails lib directory. At the time of this writing it was 0.2.0. Finally,
add the following jars from the Apache CXF - asm-2.2.3.jar, groovyws-standalone-0.2.0.jar, jaxws-api-2.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar to your lib directory.

3) FedEx is a secure (https) call
CXF doesn’t support SSL web service calls without a little configurary. Ok, I made the word “configurary” up. I’m pretty sure you can do that when you’re a blog author.

Anyhow, you’ve already seen how to do in step 2:

<http:conduit name="{http://fedex.com/ws/ship/v3}ShipServicePort.http-conduit">
<http:tlsclientparameters securesocketprotocol="SSL">
</http:tlsclientparameters>
</http:conduit>

For more information on CXF configuration see links [1], [2] below.

Ok, you’re ready to roll. You can call the FedEx web service from within Grails.

I wrote this article to try save you some time. If you see something that can be improved and save me some time, leave a comment. Pasting XML code is a bit tricky, so hopefully it’s formatted ok for you.

As you can tell, this article is geared towards configuration rather than code. Hopefully, it seems easy. If it does seem easy, then I’ve achieved my objective.

If you want to see some more code that will probably blow-you-away on how technically gifted I am (oh, common, I’m not serious. It’s just another bad joke like “configurary”.), let me know. Or, if you are looking for a Grails developer, I’m always interested in hearing about opportunities.

Reference

[1] CXF Site SSL Configuration http://cwiki.apache.org/CXF20DOC/client-http-transport-including-ssl-support.html

[2] An article that helped with CXF SSL config: http://techpolesen.blogspot.com/2007/08/using-ssl-with-xfirecxf-battling.html

Radiant CMS Database Form Extension

With a few tweaks, the Radiant CMS Mailer Extension can be turned into an extension that allows form submissions (with
pre-post validation) to save to a database; e.g.

class DatabaseFormPage < Page
 
  class DatabaseFormError < StandardError; end
 
  attr_reader :form_name, :form_conf, :form_error, :form_data, :tag_attr
    
  # Page processing. If the page has posted-back, it will try to save to contacts table
  # and redirect to a different page, if specified.
  def process(request, response)
    @request, @response = request, response
    @form_name, @form_error = nil, nil
    if request.post?
      @form_data = request.parameters[:database].to_hash
      # remove certain fields from hash
      form_data.delete("Submit")  
      form_data.delete("ignore")  
      @form_name = request.parameters[:form_name]
      @form_conf = config['form'][form_name].symbolize_keys || {}
      if save_form and form_conf.has_key? :success_page
        response.redirect( form_conf[:success_page] )
      else
        super(request, response) 
      end
    else
      super(request, response)
    end
  end
 
  # Save form data
  def save_form()
    begin
      contact = Contact.new(form_data.to_hash)
      contact.save
     rescue
      @form_error = "Error encountered while trying to submit form. #{$!}"
      return false
    end
    true
  end
 
 
  def cache?
    false
  end
  
  # DatabaseForm Tags:
    
    desc %{ This is just for creating the <r:database/> namespace. }
    tag "database" do |tag|
      tag.expand
    end
    
    desc %{    The <r:database:form name="X">...</r:database:form> tag is required.
    It should encompass all of the helper tags for creating form fields.}
    tag "database:form" do |tag|
      @tag_attr = { :class=>get_class_name('form') }.update( tag.attr.symbolize_keys )
      raise_error_if_name_missing 'database:form'
      # Build the html form tag...
      results = %Q(<script src="/javascripts/validation.js" type="text/javascript"></script>)
      results << %Q(<form id="contact-form-id" action="#{ url }" method="post" class="#{ tag_attr[:class] }" enctype="multipart/form-data">)
      results << %Q(<div><input type="hidden" name="form_name" value="#{ tag_attr[:name] }" /></div>)
      results << %Q(<div class="database-error">#{form_error}</div>) if form_error
      results << tag.expand
      results << %Q(</form>)
      results << %Q(<script>new Validation('contact-form-id');</script>)
 
    end
 
    # Build tags for all of the <input /> tags...
    %w(text password file submit reset checkbox radio hidden).each do |type|
      desc %{      Renders a #{type} form control for a database form.}
      tag "database:#{type}" do |tag|
        @tag_attr = tag.attr.symbolize_keys
        raise_error_if_name_missing "database:#{type}" unless %(submit reset).include? type
        input_tag_html( type )
      end      
    end
 
    desc %{    A <select>...</select> tag. This is compatible with the <r:database:option/> tag as well. }
    tag 'database:select' do |tag|
      @tag_attr = { :id=>tag.attr['name'], :class=>get_class_name('select'), :size=>'1' }.update( tag.attr.symbolize_keys )
      raise_error_if_name_missing "database:select"
      tag.locals.parent_tag_name = tag_attr[:name]
      tag.locals.parent_tag_type = 'select'
      results =  %Q(<select name="database[#{tag_attr[:name]}]" #{add_attrs_to("")}>)
      results << tag.expand
      results << "</select>"
    end
 
    desc %{ A <textarea>...</textarea> tag, of course }
    tag 'database:textarea' do |tag|
      @tag_attr = { :id=>tag.attr['name'], :class=>get_class_name('textarea'), :rows=>'5', :cols=>'35' }.update( tag.attr.symbolize_keys )
      raise_error_if_name_missing "database:textarea"
      results =  %Q(<textarea name="database[#{tag_attr[:name]}]" #{add_attrs_to("")}>)
      results << tag.expand
      results << "</textarea>"
    end
    
    %{    Special tag for radio groups. This one works with the <r:database:option/> tag }
    tag 'database:radiogroup' do |tag|
      @tag_attr = tag.attr.symbolize_keys
      raise_error_if_name_missing "database:radiogroup"
      tag.locals.parent_tag_name = tag_attr[:name]
      tag.locals.parent_tag_type = 'radiogroup'
      tag.expand
    end
 
    desc %{    This is a custom tag that will render an <option/> tag if the parent is a 
    <select>...</select> tag, or it will render an <input type="radio"/> tag if 
    the parent is a <r:database:radiogroup>...</r:database:radiogroup> }
    tag 'database:option' do |tag|
      @tag_attr = tag.attr.symbolize_keys
      raise_error_if_name_missing "database:option"
      result = ""
      if tag.locals.parent_tag_type == 'select'
        result << %Q|<option value="#{tag_attr.delete(:value) || tag_attr[:name]}" #{add_attrs_to("")}>#{tag_attr[:name]}</option>|
      elsif tag.locals.parent_tag_type == 'radiogroup'
        tag.globals.option_count = tag.globals.option_count.nil? ? 1 : tag.globals.option_count += 1
        options = tag_attr.clone.update({
          :id => "#{tag.locals.parent_tag_name}_#{tag.globals.option_count}",
          :value => tag_attr.delete(:value) || tag_attr[:name],
          :name => tag.locals.parent_tag_name
        })
        result << input_tag_html( 'radio', options )
        result << %Q|<label for="#{options[:id]}">#{tag_attr[:name]}</label>|
      end
    end
 
    desc %{    This is a custom tag that will render an obfuscated email address <option>
    using the email.js file. Use nested <r:address>...</r:address> to specify the email
    address and <r:label>...</r:label> to specify what the content of the tag should be. }
    tag 'database:email_option' do |tag|
      hash = tag.locals.params = {}
      contents = tag.expand
      address = hash['address'].blank? ? contents : hash['address']
      label = hash['label']
      if address =~ /([\w.%-]+)@([\w.-]+)\.([A-z]{2,4})/
        user, domain, tld = $1, $2, $3
        tld_num = TLDS.index(tld)
        unless label.blank?
        %{<script type="text/javascript">
              // <![CDATA[
              mail4('#{user}', '#{domain}', #{tld_num}, "#{label}");
              // ]]>
              </script>
        }
        else
        %{<script type="text/javascript">
              // <![CDATA[
              mail4('#{user}', '#{domain}', #{tld_num}, '#{user}');
              // ]]>
              </script>
        }
      end      
      end
    end
    
    tag "database:email_option:label" do |tag|
      tag.locals.params['label'] = tag.expand.strip
    end
  
    tag "database:email_option:address" do |tag|
      tag.locals.params['address'] = tag.expand.strip
    end
  
    
    desc %{    For use with email template parts -- retrieves the data posted by the form }
    tag 'database:get' do |tag|
      name = tag.attr['name']
      if name
        form_data[name].is_a?(Array) ? form_data[name].to_sentence : form_data[name]
      else
        form_data.to_hash.to_yaml.to_s
      end
    end  
 
protected
 
  # Since several form tags use the <input type="X" /> format, let's do that work in one place
  def input_tag_html(type, opts=tag_attr)
    options = { :id => tag_attr[:name], :value => "", :class=>get_class_name(type) }.update(opts)
    results =  %Q(<input type="#{type}" )
    results << %Q(name="database[#{options[:name]}]" ) if tag_attr[:name]
    results << "#{add_attrs_to("", options)}/>"
  end
  
  def add_attrs_to(results, tag_attrs=tag_attr)
    # Well, turns out I stringify the keys so I can sort them so I can test the tag output
    tag_attrs.stringify_keys.sort.each do |name, value|
      results << %Q(#{name.to_s}="#{value.to_s}" ) unless name == 'name'
    end
    results
  end
  
  # Get the default css class based on type
  def get_class_name(type, class_name=nil)
    class_name = 'database-form' if class_name.nil? and %(form).include? type
    class_name = 'database-field' if class_name.nil? and %(text password file select textarea).include? type
    class_name = 'database-button' if class_name.nil? and %(submit reset).include? type
    class_name = 'database-option' if class_name.nil? and %(checkbox radio).include? type
    class_name
  end
  
  # Raises a 'name missing' tag error
  def raise_name_error(tag_name)
    raise DatabaseFormTagError.new( "`#{tag_name}' tag requires a `name' attribute" )
  end
  def raise_error_if_name_missing(tag_name)
    raise_name_error( tag_name ) if tag_attr[:name].nil? or tag_attr[:name].empty?
  end
  
end

I
don’t like how it is hardcoded to use the contacts table. Any thoughts/ideas
on how to make the table more of dynamic, runtime setting? As you can imagine,
Contact is simply:

class Contact < ActiveRecord::Base
end

Automating Fit Tests in Fitnesse Server from Cruise Control, Hudson, Bamboo via Ant

Here’s an example of calling Fit tests defined in Fitnesse from Ant. This can be called from a continuous integration server like cruise control, hudson, bamboo, luntbuild, pmeasy:

<target name="fitnesse-tests"
           description="fitnesse-tests target can be called from CC"
           depends="custom-init, compile-fit"
>
 
 <java classname="fitnesse.runner.TestRunner"
           fork="true" resultproperty="FitRegressionResult">
    <arg line="-results fit-tests/reports/fitnesse-results.html
          10.99.99.99 80 FitnessePageName"/>
    <classpath><path refid="custom.classpath" /></classpath>
 </java>
<!-- xml file processed by cruise control -->
 <java classname="fitnesse.runner.FormattingOption" fork="true">
  <arg line="fit-tests/reports/fitnesse-results.html xml
             ${fit.report.dir}/fit-results.xml
             10.99.99.99 80 FitnessePageName"/>
  <classpath><path refid="custom.classpath" /></classpath>
 </java>
 
 <!-- fail if any fit test errors -->
 <condition property="fit-failures">
   <equals arg1="${FitRegressionResult}" arg2="0"/>
 </condition>
 <fail message="${FitRegressionResult} Fitnesse test(s) failed."
        unless="fit-failures" />
</target>

Fitnesse is an excellent communication and collaboration tool between developers, QA and clients. It works well in agile development environments where emphasis and priority is put on automating tests.

If you have any questions, feel free to contact Todd McGrath at http://www.supergloo.com/contact.htm

Unit Testing with StrutsTestCase for JUnit Tutorial

As the name implies, StrutsTestCase is a tool for testing your Struts code. In this article, I’ll cover:

  • What is StutsTestCase?
  • How?
  • When?
  • Where?

This article assumes the reader has familiarity and experience with Junit.

“StrutsTestCase for JUnit is an extension of the standard JUnit TestCase class that provides facilities for testing code based on the Struts framework. StrutsTestCase provides both a Mock Object approach and a Cactus approach to actually run the Struts ActionServlet, allowing you to test your Struts code with or without a running servlet engine.” -

In this article, we’ll discuss and examples using the MockObject approach; not Cactus.

StrutsTestCase reads the struts config file and loads up validators, plug-ins, forwards, etc. In other words, it can mock what happens when a servlet container is started without actually requiring a servlet container.

Just like JUnit – Extend a Class and follow a method naming convention

You have the same access to setUp() and tearDown():

public class TestLoginAction extends MockStrutsTestCase {
    public void setUp() { super.setUp(); }
    public void tearDown() { super.tearDown(); }
}

Extends JUnit’s TestCase, so access to all the assert*. Differentiates itself by using verify* methods.

Form validation example:

public void testNonNumberEntry() {
 
	addRequestParameter("txtFlatPercent", "arg1"); //add bogus percent
    	setRequestPathInfo("/calculate");
 
    	CalculatorValidatorActionForm form = new CalculatorValidatorActionForm();
    	form.setTxtName("Account Name");
    	form.setChkOptionRate("Flat");
    	form.setTxtAccountVal("55000");
    	form.setTxtFixedPer("string");
    	setActionForm(form);
 
    	actionPerform();
 
    	verifyActionErrors(new String[] { "prompt.txtFlatPercent"});
 
	}
 
 
    public void testFlatPercentValidationWithOutExceptionRequest() {
 
    	setRequestPathInfo("/calculate");
 
    	CalculatorValidatorActionForm form = new CalculatorValidatorActionForm();
    	form.setTxtName("Account Name");
    	form.setChkOptionRate("Flat");
    	form.setTxtFlatPercent("49");
    	form.setTxtAccountVal("500000");
    	setActionForm(form);
 
    	actionPerform();
 
    	verifyForward("flat");
    	assertNull(getMockRequest().getSession().getAttribute(ConstantsInterface.CONFIRM_EXCEPTION_REQUIRED));
 
    }

Struts Plug-In Validation

try {
	FeeCalcServiceFactory serviceFactory = (FeeCalcServiceFactory)getActionServlet().getServletContext().getAttribute(ConstantsInterface.FEECALC_SERVICE_FACTORY_KEY);
	assertNotNull(serviceFactory.createService());
  } catch (Exception e) {
	log.error(e);
	fail("testFactory - should not have thrown exception when obtaining service");
  }
}
 
public void testGetRates() {
   try {
	FeeCalcServiceFactory serviceFactory = (FeeCalcServiceFactory)getActionServlet().getServletContext().getAttribute(ConstantsInterface.FEECALC_SERVICE_FACTORY_KEY);
	FeeCalcService service = serviceFactory.createService();
	// should never be empty
	assertTrue(service.getRates().size() >= 0);
 
   } catch (Exception e) {
	log.error(e);
	fail("testGetRates - should not have thrown exception");
   }
}

StrutsTestCase Compatibility:

  • Struts 1.2, 1.1
  • Tiles
  • Sub-applications
  • Java Servlet 2.2, 2.3, and 2.4 specifications
  • JUnit 4.0

StrutsTestCase Gotchas:

  • WEB-INF - “By default, the Struts ActionServlet will look for the file WEB-INF/struts-config.xml, so you must place the directory that contains WEB-INF in your CLASSPATH. If you would like to use an alternate configuration file, please see the setConfigFile() method for details on how this file is located.”
  • Does not support Struts 1.3 or 2

Example of Java Acceptance Testing with Fit

Fit testing provides a way for non-developers to participate in test driven development. Fit testing facilitates collaboration and communication in applications that need to verify results from calculations. From the Fit testing website, http://fit.c2.com/:

Great software requires collaboration and communication. Fit is a tool for enhancing collaboration in software development. It’s an invaluable way to collaborate on complicated problems–and get them right–early in development.

Let’s run an example of using Fit tests in your Java application. This example assumes you are using Ant to build.

First, pick up the AntFit ant task from http://www.cmdev.com/antfit/. This will provide the code to call Fit tests from your Ant build file.

<taskdef name="antfit" classname="com.cmdev.fit.ant.FitTask" classpathref="antfit.classpath">
</taskdef>

and calling it:

<target name="test-fit" depends="init,compile" description="Executes the Fit tests">
<antcall target="populateDb"></antcall></target>
 
<!-- This uses a whole different set of folders -->
 
<property name="our.fit.dir" value="${basedir}/../fit"></property>
<property name="our.fit.src.dir" value="${our.fit.dir}/src"></property>
<property name="our.fit.test.dir" value="${our.fit.dir}/tests"></property>
<property name="our.fit.report.dir" value="${our.report.dir}/fit"></property>   <mkdir dir="${our.fit.report.dir}"></mkdir>  <!-- Now compile the fit tests -->
<property name="our.fit.compiled.dir" value="${our.work.dir}/compiled-fit"></property>   <mkdir dir="${our.fit.compiled.dir}">
<javac srcdir="${our.fit.src.dir}" destdir="${our.fit.compiled.dir}" target="1.4" fork="true" memoryMaximumSize="512m">
<classpath></classpath></javac></mkdir>
<path refid="our.classpath.buildtime"></path>
<path location="${our.test.compiled.dir}"></path> <!-- Now execute the fit tests -->
<antfit destdir="${our.fit.report.dir}" usewiki="false" fork="true">
<classpath></classpath></antfit>
<path location="${our.fit.compiled.dir}"></path>
<path refid="our.classpath.buildtime"></path>
<path location="${our.test.compiled.dir}"></path> <fileset dir="${our.fit.test.dir}">
<include name="**/*.html"></include>
</fileset>

In this example, we are not going to use a wiki (useWiki=”false”) for creating tests. We can cover that in a later example.Next, let’s look at some Java source code for Fit tests:

import java.text.NumberFormat;
import org.apache.commons.lang.StringUtils;
import util.RateFetcher;
import com.feecalc.vo.choice.BlendedResult;
import fit.ColumnFixture; 
/**
* Automates testing the standard and discount fee for the standard tiers. Does all of its
* calculations based on Equity, assuming that if the code works for Equity, it will work for any of
* them.
*
* @author robert.c.fischer
*/
public class FeeFixture extends ColumnFixture {

public double customRate;
protected static final String ZERO = “0″;
public double value;
public double discount;
public String assetType;

/**
* @return The standard fee.
*/
public String standardFee() {
return moneyToString(generateBlendedResult().getStandardFeeAnnual());
}

/**
* @return The discount fee.
*/
public String discountFee() {
return moneyToString(generateBlendedResult().getDiscountedFeeAnnual());
}

/**
* @return The discount blended rate
*/
public String discountRate() {
return percentToString(generateBlendedResult().getDiscountBlendedRate());
}

/**
* @return The standard blended rate
*/
public String standardRate() {
return percentToString(generateBlendedResult().getStandardBlendedRate());
}

/**
* @return The standard internal fee (post-”haircut” amount)
*/
public String internalStandardFee() {
return moneyToString(generateBlendedResult().getStandardRepFeeAnnual());
}

/**
* @return The discounted internal fee (post-”haircut” amount)
*/
public String internalDiscountFee() {
return moneyToString(generateBlendedResult().getDiscountedRepFeeAnnual());
}

/**
* @return The standard fee.
*/
public String getProposedClientDiscount() {
return percentToString(generateBlendedResult().getProposedClientDiscount());
}

protected static final String moneyToString(final double value) {
final NumberFormat format = NumberFormat.getInstance();
format.setMinimumFractionDigits(2);
format.setMaximumFractionDigits(2);
format.setGroupingUsed(false);
return format.format(value);
}

protected static final String percentToString(final double value) {
final NumberFormat format = NumberFormat.getInstance();
format.setMinimumFractionDigits(2);
format.setMaximumFractionDigits(2);
format.setGroupingUsed(false);
return format.format(value);
}

protected BlendedResult generateBlendedResult() {

final BlendedResult result = new BlendedResult();

// Set the rates
setEquityRates(result);
setCashRates(result);
setMutualRates(result);

// Set the test values provided by the user
if (StringUtils.isBlank(assetType) || assetType.toLowerCase().startsWith(”eq”)) {
result.setEquityDiscount(discount);
result.setEquityAmount(value);
result.setEquityRateTier(5, customRate);
} else {
result.setEquityDiscount(0);
result.setEquityAmount(0);
}
if (StringUtils.isNotBlank(assetType) && assetType.toLowerCase().startsWith(”mu”)) {
result.setMutualAmount(value);
result.setMutualDiscount(discount);
result.setMutualRateTier(5, customRate);
} else {
result.setMutualAmount(0);
result.setMutualDiscount(0);
}
if (StringUtils.isNotBlank(assetType) && assetType.toLowerCase().equals(”ca”)) {
result.setCashAmount(value);
result.setCashDiscount(discount);
result.setCashRateTier(5, customRate);
} else {
result.setCashAmount(0);
result.setCashDiscount(0);
}

return result;
}

private static final void setEquityRates(final BlendedResult result) {
final double[] equityRates = new RateFetcher().getEquityRates();
if (equityRates.length < 4) {
throw new RuntimeException(”Only have ” + equityRates.length
+ ” equity rates — need 4″);
}
for (int i = 0; i < equityRates.length; i++) {
result.setEquityRateTier(i + 1, equityRates[i]);
}
}

private static final void setCashRates(final BlendedResult result) {
final double[] cashRates = new RateFetcher().getCashRates();
if (cashRates.length < 4) {
throw new RuntimeException(”Only have ” + cashRates.length + ” cash rates — need 4″);
}
for (int i = 0; i < cashRates.length; i++) {
result.setCashRateTier(i + 1, cashRates[i]);
}
}

private static final void setMutualRates(final BlendedResult result) {
final double[] mutualRates = new RateFetcher().getMutualRates();
if (mutualRates.length < 4) {
throw new RuntimeException(”Only have ” + mutualRates.length
+ ” mutual rates — need 4″);
}
for (int i = 0; i < mutualRates.length; i++) {
result.setMutualRateTier(i + 1, mutualRates[i]);
}
}
}
Here’s the source that’s used in the Fit tests:

feecalc.FlatRateFeeFixture  
cash equity mutual flatRate assetSum() flatRateFee() comparisonFee()
300000 100000 100000 1 500000 5000.0 4975.0
4000000 4000000 4000000 0.25 12000000 30000.00 93875.00
100000 10000 10000 0.50 120000 600.00 850.00
5000 5000 5000 0.75 15000 112.50 200.00
130000 100000 10000 1.00 240000 2400.00 2800.00

And here is an example of the results after the Java Fit tests are run:

feecalc.FlatRateFeeFixture  
cash equity mutual flatRate assetSum() flatRateFee() comparisonFee()
300000 100000 100000 1 500000 5000.0 4975.0
4000000 4000000 4000000 0.25 12000000 30000.00 93875.00
100000 10000 10000 0.50 120000 600.00 850.00
5000 5000 5000 0.75 15000 112.50 200.00
130000 100000 10000 1.00 240000 2400.00 2800.00

Yae, all green. If there was an incorrect result, the errant result table cell would be red instead of green.

Hat tip to my co-worker Robert Fischer for writing the Java code and Fit test HTML.

More information on Fit testing can be found at:
http://fit.c2.com/

And for those that want a physical book:

DLL from Java with Jawin

Do you need to use a Windows DLL from Java? I recommend investigating the open source tool: Jawin. I found used this tool in order to call a DLL that converts HTML to RTF. After struggling with various JNI tutorials, Jawin was refreshing. What makes this tool outstanding is the jawinBrowser. Here’s a tutorial on calling a DLL from Java:

1. Download Jawin
2. In a command prompt, go to the typebrowser/ directory
3. java -jar jawinBrowser.jar

This will start the jawinBrowser that allows you to generate Java code to interact with the Windows DLL.

4. Start a new project and name it anything you wish
5. Next step is to point to the DLL - click the “New” button and select the DLL
6. Highlight the DLL and then double click the configuration options on the left hand side:

Calling DLL from Java with Jawin

7. Change the java package name to something appropriate. In my example, I converted it to com.supergloo.htmlconvert.
8. Specify an output directory; e.lg /tmp or c:/temp

9. Click Ok.

10. From the menu bar, select Code Generation->Generate Full Code
11. Next, from the menu bar, select Code Generation-> Save Java Files

You now have java src files to call your DLL methods! (check the directory you specified in step 8).

Going further, you will have an IConverter class that you can call. In my case, I used the following:

public static String convert(String htmlString) throws COMException{
String rtfString = "";
//Initialize service COM objects
Ole32.CoInitialize();
IConverter iconv = new IConverter("Html2Rtf.Converter");

//Set properties of coversions. See help for Sautin
iconv.setPreserveTables(true);
iconv.setPreserveNestedTables(true);
iconv.setPreserveTableWidth(true);
iconv.setPreserveImages(true);
iconv.setPreserveFontFace(true);
iconv.setPreserveFontColor(true);
iconv.setRtfLanguage(eLanguage.l_English);
//rtfString = iconv.Convert(htmlString, “”, “”);
rtfString = iconv.ConvertFileToString(htmlString, “”, “”);

//UnInitialize service COM objects
Ole32.CoUninitialize();
return rtfString;
}

To use in your Java project, make sure to include jawin.jar and jawin-Stubs.jar in your classpath.

You can drop the Jawin.dll in to %WINDOWS%\system32 or specifically set the PATH and library path when invoking your Java app; e.g. here’s a .bat file example:

set PATH=%PATH%;.\dll
set CLASSPATH=.\bin;.\lib\jawin.jar;.\lib\jawin-stubs.jar
java -Djava.library.path=.\dll Test

ServiceCycle is a registered trademark of Supergloo, inc..