I’ve learned way too much about RV water heaters

rv_water-heaterIn preparation for showers at our first camping trip, I pushed the button to ignite our hot water heater, set my timer for 30 minutes to check the water, and went back to playing with the kids.  The timer went off, and when I checked the hot water, it was still cold.

I should have checked that the fire had ignited in the water heater, but we were having so much fun at the time, I decided to just trust it, which was obviously a mistake I won’t make again.

I went out to check on it, and nothing looked obviously wrong with a cursory glance.  I checked all the connections and fuses, banged on the solenoid, and disconnected the pipe to the solenoid to confirm I smelled gas.

The temperature out was still in the 50s, so hot water was a must.  Since it was late, we began to think of alternatives with the only real options being no shower, or go to the camp’s showers.  Since it was getting pretty late, the wife and kids opted for no shower.  I opted for a really, really cold shower in the camper, which was a horrible decision.

The next morning I began to investigate a little deeper, and one of my fellow campers lent a hand in troubleshooting.  Lent a hand is putting it lightly, this guy was one of those “I’m going to help you by doing it all for you” types, which is not preferred.  The guy was helpful, but not exactly a team player.  The troubleshooting was limited anyway because we did not have a multimeter.  This job would have to wait until home.

Over the next couple of weeks, I dug all over the internet looking for troubleshooting information.  I discovered that there are two primary brands of RV water heaters: Suburban and Atwood.  I have an Atwood, model number: G6A-8E. A few youtube and google searches revealed some basic info, but the Atwood Owner’s Manual also provided good part number and schematic info.

The primary parts in question were the solenoid (93870) and the circuit board (93851).

The hope was that it was just a fuze on the circuit board, but after successfully checking both of them with a multimeter, it was obvious this project would not be an easy (and cheap) fix.  It could have also been the E.C.O. (Emergency Cut Off) switch, and the T-Stat (Thermostat) switches, but I was able to bypass them with some alligator clips, proving they were not the issue (in addition to checking them with the meter).

After looking up the solenoid and circuit board online and noticing that they were not cheap (about $250 combined), I checked to see what a new one would cost and that would have set me back about $500. There is also a tool that will test your Atwood circuit board, but it is about $300.  Not very cost-efficient for this one project.

After buying my first camper I found a guy, who works on campers/RVs out of his garage, to perform an inspection on it. I felt like he did a thorough job, so I decided to give him a call.  He was very helpful and gave me some pointers on troubleshooting before bringing it to him (mainly stuff listed above).  After the suggestions failed to narrow down the issue, he said he had an old solenoid and circuit board for the model I have (G6A-8E) that I could buy for $125 total.  He also gave me a money back guarantee that the parts would work for a while.

This sounded like the best option, so I met him at a local Home Depot, confirmed that his parts matched-up with mine, and bought the parts.

We went home, connected it all up, and it ignited like a champ. This project was a few months ago (lazy posting on my part), and the water heater is going strong.

Jersey RESTful Framework with Forms

I needed a RESTful service from which a variety of disparate apps and systems can submit to in order to create a new user. The basic flow will be for a user to fill out an HTML Form, and have the data sent to my service.

I have used the Jersey RESTful Framework for quite some time now, I and like its simple, intuitive API. I had not used Jersey as an endpoint for Form submissions, but I know other frameworks have this features, and some simple searching showed that Jersey supports Forms as well.

@Path("/user")
public class UserRestService extends AbstractSpringRestAware {

    UserService userService;

    public UserRestService(){
        userService = (UserService) context.getBean("userService");
    }

    @POST
    @Path("create")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response createUser(@FormParam("userId") String userId,
            @FormParam("password") String password, @FormParam("firstName") String firstName,
            @FormParam("lastName") String lastName, @FormParam("email") String email, 
            @FormParam("phone") String phone, @FormParam("fax") String fax,
            @FormParam("address") String address, @FormParam("city") String city, 
            @FormParam("state") String state, @FormParam("zip") String zip){ 

        if(StringUtils.isBlank(userId) || StringUtils.isBlank(password) 
                || StringUtils.isBlank(password) || StringUtils.isBlank(firstName)
                || StringUtils.isBlank(lastName) || StringUtils.isBlank(phone)
                || StringUtils.isBlank(address) || StringUtils.isBlank(city)
                || StringUtils.isBlank(state) || StringUtils.isBlank(zip)
                || StringUtils.isBlank(email)){

            if(logger.isDebugEnabled()){
                logger.debug("One of the required params was not provided.  userId=" + userId); 
            }

            // 412 - The server does not meet one of the preconditions that the requester put on the request.[2]
            return Response.status(412).entity("One of the required params was not provided.  userId=" + userId).build();
        }

        User existingUser = userService.getUser(userId); 

        if(existingUser != null){

            // 412 - The server does not meet one of the preconditions that the requester put on the request.[2]
            return Response.status(412).entity("userId already exists: " + userId).build();

        } else {

            User u = new User(); 
            u.setUserId(userId);
            u.setPassword(password);

            //set all User attributes

            User u2 = userService.createUser(u);

            if(u2 == null){
                return Response.status(412).entity("User was not created due to unforeseen issues. userId=" + userId).build();
            }

            String userXml = WSUtils.getXmlForObject(u2);

            return Response.status(200).entity(userXml).build();
        }
    }

}

My custom AbstractSpringRestAware class loads my ApplicationContext.xml to inject my services. I need to update this codebase to let Jersey know about spring in the web.xml (com.sun.jersey.spi.spring.container.servlet.SpringServlet) , but I have not completed that task just yet.

public abstract class AbstractSpringRestAware {
    ApplicationContext context = null; 

    public AbstractSpringRestAware(){
        String env = System.getProperty(StaticVars.ENV_VAR); 

        if(env != null && env.equals(StaticVars.TRUE)){
            context = new FileSystemXmlApplicationContext("applicationContext.xml");
        } else {

            //Try-Catch is needed b/c in UAT, the appContext.xml is used, but when testing locally, we want the test-appContext.xml
            try{
                context = new FileSystemXmlApplicationContext("classpath:test-applicationContext.xml");
            } catch(Exception e){
                context = new FileSystemXmlApplicationContext("classpath:applicationContext.xml");
            }
        }
    }
}

My hope was to use Grizzly integrated with Maven to test this feature locally, but I ran into SSL issues on my local machine when trying to save the user, which I have not yet fixed. (Post on local SSL config coming soon, hopefully) That left me with having to deploy the WAR out to our WebSphere environment, and testing the form submission manually. Not a good long-term solution, but it was able to get the other developers something to test against in a timely fashion while I figure out the SSL issues.

I used a few different client frameworks to try and mock the form submission (mainly straight-up URLConnection and Apache HTTPClient), but ran into a multiple issues, and once they were working, it was somewhat messy. My guess is I was doing something wrong with those frameworks, but once I found the JerseyClient API, it was a much easier and cleaner solution in my opinion.

public class TestUserRestService {
    private static final String userCreateUrl = "http://dev.corporation.com/app/rest/user/create"; 
    private static String userId = "testUser_";

    @BeforeTest
    public static void beforeTests(){
            userId = userId + WSUtils.getCurrentDateAndTimeAsString();
    }

    @Test
    public void test_createUser() throws Exception {

        User u = TestData.getTestUser_1(userId); 

        ClientConfig config = new DefaultClientConfig();
        Client client = Client.create(config);
        WebResource webResource = client.resource(userCreateUrl);

        MultivaluedMap<String, String> formData = new MultivaluedMapImpl();
        formData.add("userId", u.getUserId());
        formData.add("password", u.getPassword());

        //set all User data

        ClientResponse response = webResource.type(MediaType.APPLICATION_FORM_URLENCODED_TYPE).post(ClientResponse.class, formData);

        String xml = this.getResponseAsString(response.getEntityInputStream());

        User uFromXml = (User) WSUtils.getObjectFromXml(xml); 

        Assert.assertNotNull(uFromXml); 
        Assert.assertEquals(u.getUserId(), uFromXml.getUserId()); 

        //assert all User values were saved

    }
}

Spring Lldap Authentication And Pooled Connections

I have a new project at work, which I’ve been pretty pumped about: create an Authentication and Self Registration Web Service to be used first by a mobile device, then by a plethora of apps.

I’m most familiar with the Jersey RESTful framework, so I started there.  I ran into some Spring @Autowired issues with my automated tests, but I found a way to get around that for now (an acceptable solution, but not the way I want it long term, more on this later).

So far the biggest hurdle I have hit was trying to get Spring Ldap’s ldapTemplate.authenticate(user, pwd) to work. The documentation makes it look pretty easy, and I already use Spring Ldap throughout my code base, so using the authenticate() method appears to be straight forward.

A few hours later, it was obvious it was not straight forward, and I needed to start from scratch to see why it was failing.

My first appContext.xml

<bean id="corporateConnectionDetails" class="org.springframework.ldap.core.support.LdapContextSource" scope="prototype">
<property name="url" value="${ldap.url}" />
<property name="userDn" value="${ldap.userDn}" />
<property name="password" value="${ldap.password}" />
<property name="pooled" value="false"/>
<property name="referral" value="${ldap.referral}"/>
</bean>

<bean id="corporateContextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
<property name="contextSource" ref="corporateConnectionDetails" />
<property name="dirContextValidator" ref="dirContextValidator" />
<property name="testOnBorrow" value="true" />
<property name="testWhileIdle" value="true" />
</bean>

The above code uses a basic LdapContextSource, which is then wrapped by a PoolingContextSource to create the pool from which LDAP queries are used.

To authenticate users, you should simply call ldapTemplate.authenticate(user, pwd), but with the settings above, authentication does not work.

After much troubleshooting (mainly adjusting the XML config, and retrying my JUnit tests), began to debug the Spring Ldap code. Then starring me in the face was an OperationNotSupportedException from the authenticate() method.

I decided to start from the beginning and read the documentation.

At the bottom of Sprig Ldap’s documentation on Pooling, I found a little, but very important piece of information.

9.5. Known Issues
9.5.1. Custom Authentication

The PoolingContextSource assumes that all DirContext objects retrieved from ContextSource.getReadOnlyContext() will have the same environment and likewise that all DirContext objects retrieved from ContextSource.getReadWriteContext() will have the same environment. This means that wrapping a LdapContextSource configured with an AuthenticationSource in a PoolingContextSource will not function as expected. The pool would be populated using the credentials of the first user and unless new connections were needed subsequent context requests would not be filled for the user specified by the AuthenticationSource for the requesting thread.

So far I have gotten around this issue by using a non-pooled connection. My service could use one, but it is not necessary for phase 1. So for now, I have fixed the issue with the following update to my appContext.xml:

The updated appContext.xml

<bean id="corporateLdapTemplate" class="org.springframework.ldap.core.LdapTemplate" scope="prototype">
<constructor-arg ref="corporateConnectionDetails" />
<property name="ignorePartialResultException" value="true"/>
</bean>

Note that the param has been changed to use the corporateConnectionDetails reference, which uses the LdapContextSource instead of the PoolingContextSource.

In the near future, I hope to find a way to implement and integrate my own Authentication service that will mesh with the PoolingContextSource in order to authenticate a single user.

Hope this helps someone else to not hit their thumb with the Spring Ldap hammer.