Remotely Back-up Subversion Repository

Thursday, September 2, 2010

It happens from time-to-time and for a variety of reasons that one might want to have a back-up of a Subversion repository. This includes the normal back-up reasons for disaster recovery, but could also provide a way to share repositories over slow connections, wanting a local copy of a remote repository, and probably more.

There are a few sure-fire ways to do it, but only one to do it without direct access to the original repository server, and (after initial creation of the repository) not even direct access to the back-up server. Well, you need HTTP access, but that’s all. We’re assuming proper access is allowed, and that neither server minds the occasional suck of the bandwidth.

To start, you need a Subversion repository on which to store the copy. Somewhere on this blog is a quickie how-to that will help get one configured. Once you have a server configured, you need to make a repository to receive the back-up; be sure that the server configuration allows exposing the repository path as necessary.

Unless you have some web-based tool to create the repository, you need to pop into the back-up “to” server (hereafter named “backup.server”) and create the repository (cleverly named “Repository”). Note my paths are for not-Windows, so if you’re on that OS, flip the slashes over. I’m also making some assumptions about the system configuration and permissions. If you’re not allowed to do this, of course, it will fail.

svnadmin create /path/to/svn/Repository

The command won’t return anything, but you should be able to look and see the new Repository folder in the /path/to/svn, or whever your subversion repository will live (in agreement with your Apache configuration). There’s one thing that Subversion doesn’t do by default, and that’s configure the hooks in a way that allow us to use the provided Subversion svnsync tool. In the new repository folder is a folder named “hooks.” By default in that folder are a number of templates. One in particular is of interest, the pre-revprop-change.tmpl file. The template file does some checking to ensure that a particular entity is limited to a particular action. The documentation notes that the file must exist and return zero for the hook to pass; you can just create an empty, executable script file named pre-revprop-change in that folder, and it works fine. Something very simple like #!/bin/sh works. Of course, it doesn’t do any protection (more on that), but gets us flying into the back-up.

After creating the repository and creating the pre-revprop-change script, direct access to the server is no longer required. Somewhere in the middle, either on the source server, destination server, or some other system in the middle with HTTP access to both and Subversion installed, we can kick off a pretty straight-forward svnsync command.. Here’s what it looks like, and following is a discussion of the bits we’re using.

svnsync init http://backup.server/Repository http://source.server/Repository --source-username=sourceUser --sync-username=syncUser

The “init” tells svnsync to configure the destination server so that it knows . It also does a little file manipulation on the system to create configurations to link the two together, hold usernames and passwords, and other Subversion magic. For the curious, watch the files in your home folder’s .subversion folder (again, on not-Windows) to see what changes. Slightly uncomfortable is that there are files there with passwords (assuming there are passwords associated with the servers) that are stored in clear-text; they are stored with owner-only permissions, but their presence can make some people squirm. Used as described above, svnsync will prompt for the passwords as required; add the parameters of –source-password and –sync-password to avoid being prompted (and have the passwords in your history…).

The first URL is the destination, or the back-up server Repository. Of course, the full and correct URL should be used.

The second URL is the source. Also here the full and correct URL should be used.

The source- and sync-username parameters (each preceded with double-hyphens) are optional. They’re used to provide the user to gain access where authentication is required. If authentication is required and these are not provided, the current username will be used. As noted in the comments, there are similar source- and sync-password parameters, too, to avoid being prompted if the server requires passwords.

If this is successful, a very simple response will confirm that the properties are configured.

Copied properties for revision 0.

Once done, trigger the copy command. This will be the same command to copy the first and each iteration in the middle. Issuing the command will bring the repository up to date, in full Subversion fashion, maintaining all of the history along the way. Since the init starts at revision 0, it may be some time if the repository is large or has a lot of revisions. While it’s working, there should be a cycle of “copying properties” and “transmitting data” and “committed version” messages as each revision is copied. When it reaches the current revision it will stop.

svnsync sync http://backup.server/RepositoryPath --source-username=sourceUser --sync-username=syncUser --sync-password=syncPassword

Here note that the sync-password parameter is provided. This line can be added to a cron job or other automatically run script to provide periodic updates to the repository. Of course, again, use the correct values for your system.

That’s the guts of it. Easy, four steps, and done. Make a repository. Prep the hooks. Run an init. Run a sync (repeat as necessary).

About that hook. Reading the svnsync documentation and the contents of the template file, many warnings abound about the trouble that happens if a sync’d-to repository is otherwise updated; and they’re true. Consider if a user uses the back-up repository and commits a change; they get to use the next revision number. The next sync will come along and try to use the same next revision number and fail because it already exists. For that simple reason it’s worth protecting with a more robust pre-revprop-change file. That’s a little outside the scope of this document, but it’s not much harder than changing the template file to use your sync username instead of the one in the file. Here’s a trivial example that will work with our example syncUser.

#!/usr/bin
if [ "$3" == "syncUser" ] ; then exit 0 ; fi
exit 1

Note this really only limits changing properties (and therefore revision numbers) to the one user, so if you’re going to do this, make that user be a non-user, one used only for the back-up.

Embracing Mylyn

Thursday, August 26, 2010

As a developer, one of the things I really want to do with my work day is development. As a member of a development team, I recognize the need to identify tasks, prioritize work, and (sometimes reluctantly) quantify effort, both in pre-work estimates and post-work time accounting. When these tasks are tracked using compatible software, Eclipse has a built-in tool to help leverage those lists and assist in tracking time and work related to completing tasks.

Anyone using Eclipse has undoubtedly run into Mylyn once or twice, probably by accident. There’s a goofy icon on a number of views that looks like three balls, one in front of the other, and if you bonk it the whole view presents different information than it did before. Especially if done by accident, it can be frustrating and difficult to revert; some of us taking the “when in doubt, restart” mindset brought about by one of our common operating system, and restart the IDE to get the view back to what we expect.

That icon is the gateway to a powerful tool that, like so many others, once understood can provide incredible utility. Before digesting a flow of work that includes using that button, here’s a bit about what Mylyn is.
(more…)

Gaining Access to the Spring Context in Non Spring Managed Classes

Monday, August 23, 2010

There are times where it’s not practical (or possible) to wire up your entire application into the Spring framework, but you still need a Spring loaded bean in order to perform a task.  JSP tags and legacy code are two such examples.   Here is a quick and easy way to get access to the application context.

First we create a class that has a dependency on the spring context.  The magic here is a combination of implementing ApplicationContextAware and defining the ApplicatonContext object as static.

package com.objectpartners.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SpringContext implements ApplicationContextAware {
  private static ApplicationContext context;

  public void setApplicationContext(ApplicationContext context) throws BeansException {
    this.context = context;
  }
  public static ApplicationContext getApplicationContext() {
    return context;
  }
}

Next,  we wire SpringContext into our spring container by defining it in our application-context.xml:

<bean id="springContext" class="com.objectpartners.util.SpringContext />

Now,  anywhere we need access to the spring context we can import SpringContext and call the getApplicationContext method like so:

import com.objectpartners.util.SpringContext;
class LegacyCode {
.
.

  SpringBean bean = (SpringBean)SpringContext.getApplicationContext.getBean("springBean");
.
.
}

Keep in mind that if there are multiple spring containers running on the JVM the static ApplicationContext object will be overwritten by the last container loaded so this approach may not work for you.

Mimicking External Actions With EasyMock

Thursday, August 19, 2010

It will happen sometimes that a unit test will need to do some work that an external source might normally do. One easy-to-see example of this is an ID that gets generated by a database when an entity is persisted. It may be the case that the software will assume success, but it could (nay, should) also be the case that some validation or use of the identifier is done after the persistence, and this can be tricky when mocking these interactions.

Let’s take a really simple and dumb annotated Entity object.

@Entity
@Table(name="pojo")
public class POJO {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public Long id;

    @Column
    public String something;
}

When used, we would expect the database to populate our id column when we saved the object for the first time. Simple, easy.

Let’s make a simple DAO definition to save such an object.

public interface POJODAO {
    public POJO saveOrUpdate(final POJO pojo);
}

We’ll leave the implementation of this up to the specific application, but now we know we have a way to save (or update) our POJO. Let’s make a trivial class that will do exactly this.

public class POJOWork {

    public POJODAO pojoDao = null;

    public void makeAndSavePOJO() {
        final POJO pojo = new POJO();
        pojo.something = Long.toHexString(System.currentTimeMillis());
        pojoDao.saveOrUpdate(pojo);
        if (pojo.id == null) {
            throw new RuntimeException("POJO Save Failed!");
        }
    }
}

Here we’ve got a pretty useless method that creates and saves one of our objects, assigning some goofy data to its member. Of course, in your code, this would be more useful. After the save is complete, the ID field is used. Here, for a trivial and probably very undesireable validation, the ID field is checked for null, which it shouldn’t be after a successful save, and will throw an Exception if the field is null. Again, of course, in your code something more useful would be done.

In good test-driven (or test-defended) development, we want to have a test that verifies our method does what we expect it to. There are a few obstacles in our method that we run into when we’re writing our test, the least of which is that the object we need to mock is local to the member.

Here’s a quick method that works around this, ensures that the DB interaction is mocked, and passes our simple method without hitting that Exception.

import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertNotNull;

import org.easymock.IAnswer;
import org.junit.Test;

public class POJOWorkTest {
    @Test
    public void makeAndSavePOJOSuccess(){
        final POJOWork pojoWork = new POJOWork();
        pojoWork.pojoDao = createMock(POJODAO.class);

        expect(pojoWork.pojoDao.saveOrUpdate(isA(POJO.class))).andAnswer(new IAnswer() {
            @Override
            public POJO answer() throws Throwable {
                final POJO pojo = (POJO)getCurrentArguments()[0];
                assertNotNull(pojo.something);
                pojo.id = 0l;
                return pojo;
            }
        });

        replay(pojoWork.pojoDao);
        pojoWork.makeAndSavePOJO();
        verify(pojoWork.pojoDao);
    }
}

Digesting the test bit by bit, we start out making our object. We then assign its DAO to a mock implementation.

We know that we’re going to call the method, so we set up an expectation. Since we don’t have either an equals() method or other means by which to compare the object passed to our expectation, we simply accept one of its kind with the isA() EasyMock matcher. If EasyMock accepts our match (which it will), it then invokes our IAnswer, calling the answer() method.

In our answer() method, we grab the parameter (which we know from the matcher will be the right type and exist). We can do some validation here that helps us overcome other lack of access to the object; in our case, we just confirm that our POJO.somethign has a value.

Happy that our object is prepared for the DB, we play its part and assign the id field a value. Since our test simply makes sure it’s not null, we just give it a number. Should we have needed to, we could have put more thought into this, of course. Then, because our interface says we return our POJO, we do so from our answer() method.

With our peparation complete, the test tells EasyMock to start waiting for calls, we call the method, it runs, calling our prepared EasyMock method and running as if it were in a real execution flow, and finally we validate with EasyMock that our expectations were met. Running this test gives us total green-bar happiness.

Sure, that’s the easy one; a method with a return value. What happens if we don’t return anything, or what is returned has nothing to do with our object? Let’s change our interface a little to just save and not return our object.

public interface POJODAO {
    public void saveOrUpdate(final POJO pojo);
}

With that little change, our POJOWork code is still valid, as it was not making use of the returned value, but we can no longer use the easy expect().andAnswer() or expect().andReturn() EasyMock methods.

import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertNotNull;

import org.easymock.IArgumentMatcher;
import org.junit.Test;

class POJOMatcher implements IArgumentMatcher {
    @Override
    public void appendTo(StringBuffer arg0) {
    }

    @Override
    public boolean matches(Object arg0) {
        final POJO pojo = (POJO) arg0;
        assertNotNull(pojo.something);
        pojo.id = 0l;
        return true;
    }
}

public class POJOWorkTest {
    private POJO isPOJO() {
        reportMatcher(new POJOMatcher());
        return new POJO();
    }

    @Test
    public void makeAndSavePOJOSuccess() {
        final POJOWork pojoWork = new POJOWork();
        pojoWork.pojoDao = createMock(POJODAO.class);

        pojoWork.pojoDao.saveOrUpdate(isPOJO());
        expectLastCall();

        replay(pojoWork.pojoDao);
        pojoWork.makeAndSavePOJO();
        verify(pojoWork.pojoDao);
    }
}

That’s a bit more work, and it’s not nearly as elegant or flexible. Let’s go over this bit-by-bit, too.

The new class, POJOMatcher, makes use of the EasyMock.IArgumentMatcher interface to allow EasyMock to send an object for comparison. The matches() method will be used to determine if that’s the right deal after all. Since we’ve really got nothing to compare to, we do our validation in the matches() method just like in the answer() method before, and when it passes we set the id. The argument passed to the matcher is the one created in our test method, so it will have an id when the method continues from the mocked save attempt.

Our test class has a new isPOJO() method, which kind of binds the IArgumentMatcher to our POJO type. While we do have to return an instance of our class, unless we want to use it to validate the information in the POJOMatcher.matches() method, it can be empty (as it is). had we wanted to, we could have made the isPOJO() more intelligent, perhaps taking a POJO as a parameter for comparison, and returning that after setting up the ReportMatcher.

In our test method, we changed the preparation a little bit. Since the method doesn’t return a value any more, we can’t pass it to EasyMock.expect(), and then can’t use the expect().andAnswer() or expect().andReturn() methods, which is how we got wrapped up in ReportMatcher and IArgumentMatcher anyway. “Calling” the method and then telling EasyMock we expect the last call using the expectLastCall() will satisfy the replay and verify and let the mock do its job.

When the call is run in the middle, our matcher will be called, comparing, ignoring our isPOJO()-provided POJO, and verifying the POJO actually passed in our implementation in the POJOMatcher.matches() method, and successfully setting the id.

Now, in both cases, we can validate the information contained in a local object when passing through a mocked method, and can also meet our initial goal of affecting the passed object as the real object is expected to have affected it.

Integrating OpenSSO/OpenAM with Liferay Portal on Tomcat

Monday, August 16, 2010

This article will demonstrate how to integrate OpenSSO/OpenAM with Liferay Portal to achieve single-signon.

Liferay Portal and OpenSSO both require a minimum 1.5 JVM, but I would recommend using Java 6 (as Java 1.5 reached its End of Service Life in October, 2009). Make sure that your JAVA_HOME environment variable is correctly set to point to your Java 6 installation.

For OpenSSO to work correctly with Liferay Portal, both servers need to be running in the same domain.  To solve this issue while running both servers on a single machine, edit the hosts file (/etc/hosts or %SystemRoot%\system32\drivers\etc\) and add/update your localhost entry:
127.0.0.1 localhost localhost.example.com
where example.com is your actual domain.

Install Liferay Portal
Liferay Portal is an open source portal. Liferay comes in two editions, Enterprise Edition (EE) and Community Edition (CE).
For a full discussion on the differences, see this. Downloads are available here. For this article, I used Liferay Portal 5.2.3 CE bundled with Tomcat 6.0 (6.0.18).

Installation consisted of:

  1. Unzip liferay-portal-tomcat-6.0-5.2.3.zip to a directory. This will create a liferay-portal-5.2.3 folder.
    • On Linux/MacOS, you will need to add execute permissions to all of the shell scripts in the bin directory: chmod +x *.sh
  2. In liferay-portal-5.2.3/tomcat-6.0.18/bin/, executing startup.sh (or startup.bat) will start Tomcat, and deploy Liferay Portal.
  3. Open a browser to http://localhost.example.com:8080, and you will see the Liferay login page. You can login with test@liferay.com/test.

Install OpenSSO/OpenAM
OpenSSO is an open source access management and federation server platform. Announced by Sun Microsystems in July 2005, OpenSSO was based on Sun Java System Access Manager, and was the core of Sun’s commercial access management and federation product, OpenSSO Enterprise (formerly Sun Access Manager and Sun Federation Manager). Oracle completed their acquisition of Sun Microsystems in February 2010 and announced that OpenSSO would no longer be their strategic product. OpenSSO will continue to be developed and supported by ForgeRock under the name of OpenAM (see this).

I downloaded the latest OpenAM build (OpenAM Snapshot 9.5.1 RC1) from here. For consistency, I will refer to OpenSSO as OpenAM for the remainder of this article.

As OpenAM also requires a servlet container, I downloaded the latest Tomcat (6.0.29) from here.
Installation of the Tomcat server consisted of:

  1. Unzip apache-tomcat-6.0.29 zip file. This will create an apache-tomcat-6.0.29 folder.
  2. As both Liferay Portal and OpenAM will be running on the same machine, I needed to update the ports that the OpenAM Tomcat server was using.
    • Edit apache-tomcat-6.0.29/conf/server.xml. I changed all of the ports from 8xxx to 9xxx. For example, 8080 to 9080, 8443 to 9443, etc.
    • On Linux/MacOS, you will need to add execute permissions to all of the shell scripts in the bin directory: chmod +x *.sh
  3. Edit catalina.sh (or catalina.bat) and add the following line to the start of the file, after the comment block listing the various Environment Variable Prequisites:
    Linux/MacOS: JAVA_OPTS="$JAVA_OPTS -Xmx1024m -XX:MaxPermSize=256m"
    Windows: set JAVA_OPTS="%JAVA_OPTS% -Xmx1024m -XX:MaxPermSize=256m"

Installation of OpenAM consisted of:

  1. Unzip openam_snapshot_951RC1.zip to a directory. This will create an opensso folder.
  2. Copy the opensso.war from opensso/deployable-war/ to apache-tomcat-6.0.29/webapps/.
  3. In apache-tomcat-6.0.29/bin/, execute startup.sh (or startup.bat) to start Tomcat and deploy OpenAM.
    • After Tomcat has deployed OpenAM, you will see the exploded war file as apache-tomcat-6.0.29/webapps/opensso.
  4. Open a browser to http://localhost.example.com:9080/opensso, which should redirect you to http://localhost.example.com:9080/opensso/config/options.htm,
    to complete the OpenAM configuration.
  5. You should see the OpenAM configuration options page. Under Custom Configuration click Create New Configuration. Enter the following:
    • Default User Password — password
    • Server Settings — default entries are ok
    • Configuration Data Store Settings — select First Instance, select OpenAM as Configuration Data Store, leave other entries
    • User Data Store Settings — select OpenAM User Data Store
    • Site Configuration — select No
    • Default Policy Agent User — policy01
    • Configurator Summary Details – click Create Configuration. This will create the configuration for your OpenAM server under ~/opensso (or c:\Documents and Settings\{username}\opensso).
  6. When this completes, in the Configuration Complete dialog, click Proceed to Login, which should now redirect you to http://localhost.example.com:9080/opensso/UI/Login.
    Type amAdmin as the username, password as the password, and click Log In. You should now see the OpenAM Console.

    • For detailed information about the OpenAM Console, see this and this. A detailed discussion of all of the functionality of OpenAM is beyond the scope
      of this article.
  7. You can now delete the opensso.war file from apache-tomcat-6.0.29/webapps/ directory.

Additional OpenAM Configuration
To get OpenAM to work correctly with Liferay, you need to set Encode Cookie Value to Yes. This will prevent infinite redirection between Liferay and OpenAM on login.

  1. In the OpenAM Console, select the Configuration tab.
  2. Select the Servers and Sites tab.
  3. Click Default Server Settings.
  4. Select the Security tab.
  5. In the Cookie section, select the Yes checkbox beside Encode Cookie Value.
  6. Click Save.

Other people have reported having to set the com.iplanet.am.cookie.c66Encode property to true as well, to resolve the infinite redirection problem:

  1. In the OpenAM Console, select the Configuration tab.
  2. Select the Servers and Sites tab.
  3. Click Default Server Settings.
  4. Select the Advanced tab.
  5. Find the com.iplanet.am.cookie.c66Encode property, and set the value to true.
  6. Click Save.

Before updating Liferay to use OpenAM, I recommend adding the default Liferay user, test@liferay.com, to OpenAM.

  1. In the OpenAM Console, select the Access Control tab.
  2. Click the / (Top Level Realm) realm.
  3. Select the Subjects tab.
  4. Click New…
  5. Setup the default Liferay user:
    • ID — joebloggs
    • First Name — Joe
    • Last Name — Bloggs
    • Full Name — Joe Bloggs
    • Password — password
    • Click OK to create the user.
  6. Click Joe Bloggs to add the email address. Enter test@liferay.com for the Email Address, and click Save.

Integrate Liferay Portal with OpenAM
Now you are ready to update Liferay Portal to integrate with OpenAM for authentication.

  1. If Liferay is running, shut it down (bin/shutdown).
  2. Create a new file, called portal-ext.properties, in your Liferay directory, under liferay-portal-5.2.3/tomcat-6.0.18/webapps/ROOT/WEB-INF/classes/.
  3. Edit this file, and add the following properties:
    open.sso.auth.enabled=true
    open.sso.login.url=\

    http://localhost.example.com:9080/opensso/UI/Login?goto=\

    http://localhost.example.com:8080/c/portal/login

    open.sso.logout.url=\

    http://localhost.example.com:9080/opensso/UI/Logout?goto=\

    http://localhost.example.com:8080/web/guest/home

    open.sso.service.url=http://localhost.example.com:9080/opensso
    open.sso.screen.name.attr=uid
    open.sso.email.address.attr=mail
    open.sso.first.name.attr=givenname
    open.sso.last.name.attr=sn

  4. Start Liferay (bin/startup).
  5. Once Liferay has started, open a browser to http://localhost.example.com/8080, and you should be redirected to the OpenAM login page
    (http://localhost.example.com:9080/opensso/UI/Login). Enter joebloggs for the User Name, and password for the Password. Click Log In.

You will be authenticated against OpenAM, and redirected to Liferay.

Now that Liferay is using OpenAM for authentication, if you create a new user in OpenAM, that user will also be created in Liferay on the first log in. That newly created user in Liferay will only have the basic information filled in – First Name, Last Name, Screenname, Email Address – and will have the default Roles, Groups, and Organizations assigned.

This article demonstrated a basic integration with OpenAM and Liferay Portal. Now you are ready to explore more advanced topics include configuring OpenAM to use an existing LDAP or other user datastore, creating a custom datastore plugin (e.g. JDBC) for OpenAM, setting up a separate realm for Liferay users, as well as taking advantage of OpenAM for incoming and outbound SSO in conjuction with Liferay Portal. Enjoy!

Spring @PathVariable Head-slapper

Thursday, August 12, 2010

Recently some peers and I spent a little time spinning around a goofy little annotation trick that Spring uses, that bit us because of the way p-code is generated. It all makes sense afterwards, but at the time it was a little frustrating and puzzling.

Taken straight from the Spring documentation, the following example shows a similar use to what we’d done.

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable String ownerId, Model model) {
  Owner owner = ownerService.findOwner(ownerId);
  model.addAttribute("owner", owner);
  return "displayOwner";
}

Our code looked pretty much the same, with our names and useful bits, of course. Quickly scanning that documentation shows that our syntax was accurate. The compiler didn’t complain, and sometimes it worked. Where it got confusing is that the code would work just fine when run in an integration test using HTTPUnit (in the IDE and run by Ant) and in a Servlet engine (Tomcat, specifically) when launched from Eclipse. Some people had success when deploying to Tomcat using an Eclipse-created WAR file, but some people experienced failure. Everyone failed when using the Ant-built WAR file.

When the error occurred, the root cause of the Exception caught ultimately was the following:

java.lang.IllegalStateException: No parameter name specified for argument of type [java.lang.String], and no parameter name information found in class file either.

When you look at the code, you can see the @RequestMapping has the appropriate {variable} notation, and that the parameter list has a variable of the same name, of type String, as expected (other types can be used, but ours was a String also).

A peek at the Ant script gave a clue to the solution. Changing the javac target’s debug attribute to “on” allowed the Ant-built WAR file to also deploy and run with success. That’s when the head-slapping began.

When the code is compiled with debug, as it is when working in the IDE, and apparently is sometimes when exporting from the IDE (probably some of us have a workspace setting different than the others), the name of the parmeter is available to the JVM at runtime. When the code is compiled without debugging, as the Ant script was doing, then the parameter name is lost, truncated by the p-code generator based on its type and order and other factors.

Adding the name to the @PathVariable annotation allows the runtime to find the correct parameter even without debug information in the class file. Again, straight from the same documentation, just a couple paragraphs down from the other example shows the more correct way to declare the @PathVariable. Right above the example on their page is a discrete mention of this fact, and a recommendation that you specify the name. Below is the subtle difference in the declaration, one that makes all the difference.

@RequestMapping(value="/owners/{ownerId}", method=RequestMethod.GET)
public String findOwner(@PathVariable("ownerId") String ownerId, Model model) {
  // implementation omitted
}

While it’s convenient to have Spring work this out for you during the development cycle, it seems more appropriate that the value be required which can be achieved simply by removing the default from the annotation. Since it isn’t that way, it’s certainly a good practice to get into to always provide the name (or names) of your path variable when annotating your controllers in Spring.