Archive

Archive for the ‘JPA’ Category

Working with Freebase – Part 3

February 21, 2013 Leave a comment

My next step with Freebase was to generalize the way I queried it. I decided to model my API after the Java Persistence API (JPA) which provides a POJO persistence model for object-relational mapping. In this case its an object-structured data mapping. The Freebase graph is a set of nodes and a set of links or relationships between those nodes. My API will provide a straight forward way to retrieve those nodes and relationships in the form of a POJO. This post will describe my initial efforts.

In its most primitive form a Freebase node has a unique identifier, creation time stamp, and creator. Properties specify a relationship. That can be a relationship with another node or primitive value. Unique properties have only one relationship to a node/value. Non-unique properties can have many.  There are also universal properties (name, key, type, permission, mid). Primitive values include int, float, boolean, text, rawstring, uri, datetime, key, and id.

JPA describes an entity as …

An entity is a lightweight persistence domain object. Typically an entity represents a table in a relational database, and each entity instance corresponds to a row in that table. The primary programming artifact of an entity is the entity class, although entities can use helper classes.

The persistent state of an entity is represented either through persistent fields or persistent properties. These fields or properties use object/relational mapping annotations to map the entities and entity relationships to the relational data in the underlying data store.

I will reuse this concept of entity to represent a Freebase node. For example, the following class represents a node of type /people/person.

@FBEntity(type = "/people/person")
public class Person implements Serializable {

	@FBProperty(property_value = "/people/person")
	private String type;
	@FBProperty
	private String id;
	@FBProperty
	private String name;

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

Consider the simplest of queries – select all from. In SQL this would look like SELECT * FROM name. MQL does not have a true equivalent.  The following MQL would select type, id, name from node type /people/person.

[{
 "type": "/people/person",
 "id": null,
 "name": null
}]

The code for executing this query looks like this.


public class QueryTestOne {
	private static final String personQuery =
			"[{"						+
				  "\"type\": \"/people/person\","	+
				  "\"id\": null,"			+
				  "\"name\": null"			+
				  "}]";

	public static  void dump(List list) {
		if (list != null) {
			for (T result : list) {
				System.out.printf("result: %s\n", result);
			}
		}
	}

	public static void test1(EntityManager em, String query) {
		Query q = em.createQuery(query);
		List<?> list = q.getResultList();
		dump(list);
	}

	public static void test2(EntityManager em, String query, Class<?> clazz) {
		Query q = em.createQuery(query, clazz);
		List<?> list = q.getResultList();
		dump(list);
	}

	public static void main(String[] args) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("https", "www.googleapis.com", "/freebase/v1/mqlread/", null);
		EntityManager em = emf.createEntityManager();

		test1(em, personQuery);
		test2(em, personQuery, Person.class);
	}
}

The Freebase read API will return the results in a JSON response envelop that uses the following general form.

{
  "result": [{ MQL Response Here }],
  "status": "200 OK",
  "code": "/api/status/ok",
  "transaction_id":[opaque string value]
}

result contains the body of results and can either be {} (singleton) or [] array. My API will always use an array. I created  a class MQLMultipleResultResponse to hold the response envelop. For now I’m ignoring the other items such as status.

public class MQLMultipleResultResponse {
	private T[] result;

	public T[] getResult() {
		return result;
	}
	public void setResult(T[] result) {
		this.result = result;
	}
}

The query can be executed with or without a type. If the type is absent the underlying code will use Object. The type defines what kind of of object array will be returned. For example, if Person.class is specified an array of Person will be returned.

The underlying code executes the following …

URI uri = new URI(entityManager.getScheme(), entityManager.getAuthority(), entityManager.getPath(), Util.createQuery(variables), null);

ClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
ClientHttpRequest request = requestFactory.createRequest(uri, HttpMethod.GET);
ClientHttpResponse response = request.execute();
ObjectMapper objectMapper = new ObjectMapper();
MQLMultipleResultResponse body = objectMapper.readValue(response.getBody(), typeReference);
results = body.getResult();

Most of this is straight forward HTTP client and JSON calls. The use of objectMapper.readValue() is somewhat unique. Since MQLMultipleResultResponse uses a generic I initially ran into problems with the following …

objectMapper.readValue(response.getBody(), MQLMultipleResultResponse.class);

objectMapper.readValue() cannot know what type the result body will be so it defaults to a Map. The following isn’t legal …

objectMapper.readValue(response.getBody(), MQLMultipleResultResponse.class);

Fortunately Jackson provides an alternate. Instead of passing the Class you can pass a TypeReference. The following code creates the necessary TypeReference …

TypeReference<MQLMultipleResultResponse> typeReference =
  new TypeReference<MQLMultipleResultResponse>() {
    public Type getType() {
      return ParameterizedTypeImpl.make(MQLMultipleResultResponse.class, new Type[] { resultClass }, null    );
  }
};

The key is providing the correct Type. In this case the subclass ParameterizedType. ParameterizedTypeImpl implements ParameterizedType. Java contains a version of ParameterizedTypeImpl but it’s inaccessible. Implementing ParameterizedType is just complicated enough that I decided to just copy ParameterizedTypeImpl’s source code into my project. It comes with a helpful make() method.

With all this in place the test client generates the following results. In one case an array of objects and in the other an array of Person.

result: {name=Dan Milbrath, type=/people/person, id=/en/dan_milbrath}
result: {name=David Safavian, type=/people/person, id=/en/david_safavian}
result: {name=Robert Cook, type=/people/person, id=/en/robert_cook}

result: org.bwgz.freebase.model.Person@2a444: getName: Dan Milbrath; getId: /en/dan_milbrath; getType: /people/person; getClass: class org.bwgz.freebase.model.Person; }
result: org.bwgz.freebase.model.Person@c126b3: getName: David Safavian; getId: /en/david_safavian; getType: /people/person; getClass: class org.bwgz.freebase.model.Person; }
result: org.bwgz.freebase.model.Person@1c01b97: getName: Robert Cook; getId: /en/robert_cook; getType: /people/person; getClass: class org.bwgz.freebase.model.Person; }
Advertisements
Categories: Freebase, Java, JPA, Structured Data

REST Service Using Spring Data

October 24, 2012 1 comment

I was intrigued by Spring Data’s REST project which promises to, “… make it easy to expose JPA based repositories as RESTful endpoints.” To try it out I decided to create a service which provides famous quotes such as Albert Einstein’s.

Insanity: doing the same thing over and over again and expecting different results.

Consider a JPA entity that holds a quote.

@Entity
@Table(name = "quote")
public class Quote {
	@Id
	@GeneratedValue
	private Long id;
	private String quote;

	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}

	public String getQuote() {
		return quote;
	}
	public void setQuote(String quote) {
		this.quote = quote;
	}

}

This is a very simple example of an entity. Just a id and text string.

Now create a repository service to expose CRUD style access to the entity. For now I’m just going to read a quote so all that’s needed is the following interface.

@RestResource(path = "quote")
public interface QuoteRepository extends PagingAndSortingRepository<Quote, Long> {
}

Spring Data provide a paging repository (PagingAndSortingRepository) and the simpler CrudRepository.

All the magic is in the @RestResource annotation. When the Spring REST Exporter starts up it will expose the QuoteRespository as a REST endpoint.

The Spring REST Exporter is a web application build on top of Spring MVC. The documentation is sparse but examples are pretty straight forward.

Also the Spring REST Exporter example uses gradle. I want to use a combination of Maven and Eclipse. It uses Java classes to configure the application environment. I wanted to use XML application context files. I also wanted multiple projects to encapsulate various applications and libraries. The entire source code is available here at github. Getting all the pieces just right took some time. I ran the quote war using Tomcat 7.

My quote endpoint will expose a list of quotes. In this case I’ve exposed paging and sorting possibilities.

http://localhost:8080/quote-json-server/quote produces a list of quotes stored in the repository.

{
  "links" : [ ],
  "content" : [ {
    "links" : [ {
      "rel" : "self",
      "href" : "http://localhost:8080/quote-json-server/quote/1"
    } ],
    "quote" : "Insanity: doing the same thing over and over again and expecting different results."
  }, {
    "links" : [ {
      "rel" : "self",
      "href" : "http://localhost:8080/quote-json-server/quote/2"
    } ],
    "quote" : "Better to remain silent and be thought a fool than to speak out and remove all doubt."
  } ],
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 1
  }
}

The output is a JSON serialization of the org.springframework.hateoas.PagedResources class. The serialization uses a HATEOAS (Hypermedia as the Engine of Application State) style format to provide a state representation of the resource.

In this case it is the state of the first page of the quotes list.

http://localhost:8080/quote-json-server/quote/1 provides the state of the first quote.

{
  "links" : [ {
    "rel" : "self",
    "href" : "http://localhost:8080/quote-json-server/quote/1"
  } ],
  "quote" : "Insanity: doing the same thing over and over again and expecting different results."
}

Having come from RPC as the method of choice REST takes a bit of getting used to. Roy T. Fielding’s REST APIs must be hypertext-driven discusses this. The biggest thing being individual resources are identified in requests. In this case a href (URL).

The next step is to create a client application to test the service. I created two clients. One a simple Java client and the other a simple Android application. REST clients are free to deserialize the response in any way they wish. I chose to use Spring’s RestTemplate API. Here’s the simple Java Client.

public class QuoteRestTemplateClient {
  private static final String URL = "http://localhost:8080/quote-json-server/quote/1";

  public static void main(String[] args) {
   RestTemplate restTemplate = new RestTemplate();

  HttpHeaders requestHeaders = new HttpHeaders();
  List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
  acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
  requestHeaders.setAccept(acceptableMediaTypes);
  HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
  restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());

  System.out.printf("url: %s\n", URL);
  try {
            ResponseEntity<Quote> entity = restTemplate.exchange(URL, HttpMethod.GET,              requestEntity, Quote.class);
            System.out.printf("json: %s\n", entity.getBody().toString());

            Quote quote = restTemplate.getForObject(URL, Quote.class);
            System.out.printf("quote: %s\n", quote.getQuote());
      } catch (RestClientException e) {
            e.printStackTrace();
    }
  }
}

The biggest challenge was creating the client side Quote class. There were actually a couple of challenges.

The Spring REST Explorer uses Spring’s HATEOAS API to wrap the JPA entity (entities in the case of a list). The response is either an instance of Resource or Resources. While I could have included the Spring’s HATEOAS jar with my application that would pulled in baggage that I wanted to avoid and as I found out later would also be problematic with the Android client. So I punted and just copied the six source files I needed into the project and edited them accordingly.

It would have been nice to have just used the service side Quote class with the client. This too was problematic because it’s annotated with JPA. This again would pull in jars I don’t want or need on the client side. Also, its getId() clashes with one found in the HATEOAS Identifier’s getId(). Here’s the client side Quote class.

public class Quote extends ResourceSupport {
	private String quote;

	public String getQuote() {
		return quote;
	}
	public void setQuote(String quote) {
		this.quote = quote;
	}
}

Pretty simple but then the example is simple. I’d truly want a way to easily generate the server and client side versions of Quote in a repeatable manner. Something to investigate on another day.

The Android application, if one discounts the necessary Android code, is identical to the Java client. It also needs the HATEOAS source files and the client side Quote class. Here’s the main Activity class.

public class MainActivity extends Activity {
    private static final String URL = "http://192.168.2.201:8080/quote-json-server/quote/1";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        TextView t1 =(TextView)findViewById(R.id.textView1);
        t1.setText(URL);

        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders requestHeaders = new HttpHeaders();
        List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>();
        acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
        requestHeaders.setAccept(acceptableMediaTypes);
        HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);
        restTemplate.getMessageConverters().add(new MappingJacksonHttpMessageConverter());

        String string;

        try {
        	ResponseEntity<Quote> entity = restTemplate.exchange(URL, HttpMethod.GET, requestEntity, Quote.class);
            string = entity.getBody().toString();
        } catch (RestClientException e) {
        	string = e.getMessage();
        }

        TextView t2 = (TextView)findViewById(R.id.textView2);
        t2.setText(string);

        Quote quote = restTemplate.getForObject(URL, Quote.class);
        TextView t3 =(TextView)findViewById(R.id.textView3);
        t3.setText(quote.getQuote());
    }
}

Here’s what it looks like.

 

Categories: Bruce's Posts, JPA, Spring Data

Open Lane Changes

April 21, 2012 3 comments

My Open Lane application has undergone some significant changes. I’ve added:

  1. Entity classes for a swim meet and events within the meet.
  2. Enumerators for Gender and Stroke.
  3. An AgeGroup class to encapsulate an event’s age category.
  4. JSF DataModel subclasses for Meet and Event along with a refactored SignUp backing bean.
  5. JPA services for all data input and output.
  6. JSF Converter classes for Gender, Stroke, and AgeGroup.
  7. CSV file initialization functions to populate the Swimmer, Meet, Event, and User tables.

Gender and stroke each represent a fixed set of constants which can be objectified in Java as an enumerator. An enumerator provides a clean, self-documenting, and an efficient way of coding an object model. Java enumerators can also be used to encapsulated conversion functionality.

In the case of this application the meet, event, swimmer, and entry records are imported from a CSV file. Within the event record is a stroke (i.e. Freestyle, Backstroke) filed that is coded using a number.  A “1” represents Freestyle, a “2” represents Backstroke, and so on. Stroke has been coded so that each enumerator has its import field number code associated with it. During the import process a simple call to Stroke.parse(string) generates the correct enumerator.

Instead of putting parse into Stroke I could have created a distinct converter. That would have been a cleaner separation of the code. But I don’t anticipate the conversion changing or needing to be adaptable. This way the code is simpler and straight forward.

Stroke.java

package org.bwgz.swim.openlane.data.model;

public enum Stroke {
    FREE("1"),
    BACK("2"),
    BREAST("3"),
    FLY("4"),
    IM("5");

    private String code;

    Stroke(String code) {
    	this.code = code;
    }

     public String getCode() {
          return code;
     }

     public void setCode(String code) {
          this.code = code;
     }

    public static Stroke parse(String string) {
    	Stroke result = null;

    	for (Stroke stroke : Stroke.values() ) {
    		if (stroke.getCode().equalsIgnoreCase(string)) {
				result = stroke;
				break;
			}
		}

		return result;
    }
}

This is an example of using the Gender enumerator within a JPA query to abstract away the codes used in the database. I don’t have to concern myself with how the data is coded once it is imported into the database.

em.createQuery("select e from Event e where e.meet = :meet and (e.gender = :mixed or e.gender = :gender")
.setParameter("meet", meet)
.setParameter("mixed", Gender.MIXED)
.setParameter("gender", gender)
.getResultList();
 

Later in this article is an example of how the enumerator’s parse method is used.

Eventually the JSF tables will support sorting and paging. In preparation for that I’ve moved from List and Collection to JSF’s DataModel. DataModel is a wrapper that abstracts the underlying data. I’ve also used it to store which record (object) may have been selected by the user. Previously there was an independent bean to handle that. Putting it here keeps thing a bit tidier. I created DataModel’s for Event and Meet. Later I’ll reuse the pattern for a user’s open lane applications.

I’ve decided to build out services for each entity in the data model. This provides a level of encapsulation and organization that make the code more manageable. Using @Autowired I can also create services that are built upon other services. For example, the SignUpService uses the SwimmerService.

SignUpService.java snippet.

@Service("signupService")
@Repository
public class SignUpServiceImpl implements SignUpService, Serializable {

     private EntityManager em;
     @Autowired
     private SwimmerService swimmerService;

    @PersistenceContext
    public void setEntityManager(EntityManager em) {
    	this.em = em;
    }
….
    @Transactional
     public Boolean doSignUp(SignUp signUp) {
     Boolean result = Boolean.FALSE;

     Swimmer swimmer = swimmerService.findSwimmer(signUp.getUsasId());

As a general rule the code should have a clean separation between how data is represented internally and externally. JSF’s Converter class is one way to accomplish that. I needed a Converter for Gender, Stroke, and AgeGroup. Since Gender and Stroke were enumerators I create an abstract base class to simplify things. The Gender and Stroke converters need only supply the mappings between the internal representation (an enumerator) and string (display value).

AbstractEnumConverter.java

package org.bwgz.swim.openlane.faces.converter;

import java.util.Hashtable;
import java.util.Map;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;

public abstract class AbstractEnumConverter<T> implements Converter {
	private final static int ASSOCIATION_ENUM	= 0;
	private final static int ASSOCIATION_STRING	= 1;

	private Class<T> clazz;
	private final Map<T, String> toStringMap = new Hashtable<T, String>();
	private final Map<String, T> toEnumMap = new Hashtable<String, T>();

	@SuppressWarnings("unchecked")
	public AbstractEnumConverter(Class<T> clazz, Object[][] associations) {
		this.clazz = clazz;

		for (Object[] association : associations) {
			toEnumMap.put((String) association[ASSOCIATION_STRING], (T) association[ASSOCIATION_ENUM]);
			toStringMap.put((T) association[ASSOCIATION_ENUM], (String) association[ASSOCIATION_STRING]);
		}
	}

	public Object getAsObject(FacesContext context, UIComponent component, String value) {
		return toEnumMap.get(value);
	}

	@SuppressWarnings("unchecked")
	public String getAsString(FacesContext context, UIComponent component, Object value) {
            if (value.getClass() == clazz) {
        	 return toStringMap.get((T) value);
            }
            else
            {
                throw new IllegalArgumentException(String.format("Cannot convert object - not of type %s", clazz.getSimpleName()));
            }
	}
}

StrokeConverter.java

package org.bwgz.swim.openlane.faces.converter;

import javax.faces.convert.FacesConverter;
import org.bwgz.swim.openlane.data.model.Stroke;

@FacesConverter(value="strokeConverter")
public class StrokeConverter extends AbstractEnumConverter<Stroke> {
	private final static Object associations[][] = {
		{ Stroke.FREE,		"Free" },
		{ Stroke.BACK,		"Back" },
		{ Stroke.BREAST,	"Breast" },
		{ Stroke.FLY,		"Fly" },
		{ Stroke.IM,		"IM" },
	};

	public StrokeConverter() {
		super(Stroke.class, associations);
	}
}

The AgeGroup converter encapsulates the four rules used to describe an age category.

AgeGroupConverter.java

package org.bwgz.swim.openlane.faces.converter;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.FacesConverter;

import org.bwgz.swim.openlane.data.model.AgeGroup;

@FacesConverter(value="ageGroupConverter")
public class AgeGroupConverter implements Converter {
	private static final String SENIOR		= "Senior";
	private static final String UNDER		= "Under";
	private static final String OVER		= "Over";
	private static final String AMPERSAND	= "&";
	private static final String HYPHEN		= "-";

	private static final int LEFT	= 0;
	private static final int RIGHT	= 1;

	private Object getAsObject(String string) {
		long min = 0;
		long max = 0;

		if (string.equals(SENIOR)) {
			min = 0;
			max = 0;
		}
		else if (string.contains(AMPERSAND)) {
			String[] fields = string.split(AMPERSAND);

			if (fields[RIGHT].equals(UNDER)) {
				min = 0;
				max = Long.parseLong(fields[LEFT]);
			}
			else if (fields[RIGHT].equals(OVER)) {
				min = Long.parseLong(fields[LEFT]);
				max = 0;
			}
		}
		else if (string.contains(HYPHEN)) {
			String[] fields = string.split(HYPHEN);

			min = Long.parseLong(fields[LEFT]);
			max = Long.parseLong(fields[RIGHT]);
		}

		return new AgeGroup(min, max);
	}

	public Object getAsObject(FacesContext context, UIComponent component, String value) {
		return getAsObject(value);
	}

	private String getAsString(AgeGroup ageGroup) {
		String string;

		if (ageGroup.getMin() == 0 & ageGroup.getMax() == 0) {
			string = SENIOR;
		}
		else {
			String left;
			String seperator;
			String right;

			if (ageGroup.getMin() == 0) {
				left = String.valueOf(ageGroup.getMax());
				seperator = AMPERSAND;
				right = UNDER;
			}
			else if (ageGroup.getMax() == 0) {
				left = String.valueOf(ageGroup.getMin());
				seperator = AMPERSAND;
				right = OVER;
			}
			else {
				left = String.valueOf(ageGroup.getMin());
				seperator = HYPHEN;
				right = String.valueOf(ageGroup.getMax());
			}

			string = left + seperator + right;
		}

		return string;
	}

	public String getAsString(FacesContext context, UIComponent component, Object value) {
            if (value instanceof AgeGroup) {
                return getAsString((AgeGroup) value);
            }
            else
            {
                throw new IllegalArgumentException("Cannot convert object - not of type AgeGroup");
            }
	}
}

Meet, event, swimmer, and later entry records are imported from another system. Unfortunately the limitations of that system prevent the application from accessing them directly. Instead the data is exported to CSV files and then imported into the application.

Eventually I’ve incorporate a form of file upload within the application to provide live data import. For now some dummy (test) CSV files are included in the application and imported when the application is first accessed. Some services now have an initialize method. When called the method will read a CSV file and write the data to the database using JPA.

Using <on-start> within home-flow.xml I trigger the initializations. This is a hack for testing purposes only.

home-flow.xml snippet

<on-start>
        <evaluate expression="swimmerService.initialize()" />
        <evaluate expression="meetService.initialize()" />
        <evaluate expression="signupService.initialize()" />
</on-start>

I use FlatPack to process the CSV files. It’s a nice CSV library that I’ve used many times in the past. I particularly like that it allows you to create a map of the column names. On the other hand the map requires names for every column. This can be a bit tedious when you only need the first few columns. It can also choke when the file contains records with differing number of columns.

SwimmerServiceImpl.java snippet.

    @Transactional
    public void initialize() {
    if (!initialized) {
        DataSet dataSet;

        Parser parser;
        parser = DefaultParserFactory.getInstance().newDelimitedParser(
            new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("/test/data/athlete.pzmap.xml")), // xml column mapping
            new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("/test/data/athlete.csv")),  // csv file to parse
            ';', // delimiter
             '"', // text qualfier
             false); // ignore the first record (may need to be done if first record contain column names)

            dataSet = parser.parse();
            while (dataSet.next()) {
                Swimmer swimmer = new Swimmer();

                swimmer.setId(dataSet.getString("Reg_ID"));
                swimmer.setFirst(dataSet.getString("First_name"));
                swimmer.setLast(dataSet.getString("Last_name"));
                swimmer.setGender(Gender.parse(dataSet.getString("Ath_Sex")));
                swimmer.setId(dataSet.getString("Reg_ID"));
                swimmer.setBirthdate(stringToDate(dataSet.getString("Birth_date")));

                em.persist(swimmer);
                }

            initialized = true;
        }
     }
 

The code is available here on GitHub.

Open Lane – Supporting a User Part 2

April 14, 2012 Leave a comment

Having added some user authentication to my open lane application the next step involved associating the user with a profile. Things can start to get complex quickly when you need more than simple authentication and authorization. At lot of applications do not need their own form of user management because they’re part of a larger solution that already has it. Those application tap into the existing service for authentication and authorization. In our case we don’t have that. So there are a couple of options. I could add a separate user management service from another provider into the solution or I could write my own user management service.

At this point I don’t want to get into integrating with another solution. I’m sure that day will come but not today. For now I’m going to write some code that supports Spring Security and gives me enough of what I need to continue building out the application.

So what do I really need:

  1. My core user is a swimmer. Someone who will apply for an open lane swim. I’ll also need operational users such as an administrator but that can wait. I need to gather and store enough information about a swimmer to process the application. I’ll call this the user’s profile.
  2. A means to authentication a user via a login. I’ll be working with Spring Security to implement this.
  3. A means to secure pages and actions to authorized users. Again I’ll use Spring Security to implement this.
  4. A registration process to add new user.

In the previous post I used Spring’s in-memory UserDetailsService to handle authentication.

<security:authentication-manager>
	<security:authentication-provider>
		<security:password-encoder hash="md5" />
		<security:user-service>
			<security:user name="keith" password="417c7382b16c395bc25b5da1398cf076" authorities="ROLE_USER, ROLE_SUPERVISOR" />
			<security:user name="erwin" password="12430911a8af075c6f41c6976af22b09" authorities="ROLE_USER, ROLE_SUPERVISOR" />
			<security:user name="jeremy" password="57c6cbff0d421449be820763f03139eb" authorities="ROLE_USER" />
			<security:user name="scott" password="942f2339bf50796de535a384f0d1af3e" authorities="ROLE_USER" />
		</security:user-service>
	</security:authentication-provider>
</security:authentication-manager>

Given the Spring centricity of this application I’ll stick with Spring Security. The question is how to get authentication/authorization with customized user management. I’ve got specific profile information that I need to capture and store.

I could continue to use a Spring Security implementation and create a look aside table but this mean creating/updating two distinct elements when a change occurs. Or, I could subclass UserDetailsService but I’m concerned that this could be a rabbit hole that I don’t want to go down right now. Instead I’ll take a look at Spring Security’s JdbcDaoImpl. JdbcDaoImpl is an implementation of UserDetailsService which uses a database to fetch the authentication and authorization data.

<security:authentication-manager>
	<security:authentication-provider>
		<security:jdbc-user-service data-source-ref="dataSource"/>
		<security:password-encoder hash="md5" />
	</security:authentication-provider>
</security:authentication-manager>

When using JdbcDaoImpl you must ensure that you’ve correctly configured the database tables. You can find details on this here. I created two JPA entities – User,  and Authority. I’m using Hibernate on the backside.

User combines the fields that JdbcDaoImpl requires with user profile fields that the application needs.

User.java

package org.bwgz.swim.openlane.model;

import java.io.Serializable;
import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "Users")
public class User implements Serializable {
	private static final long serialVersionUID = -3475658623185783516L;

	private String username;
	private String password;
	private Boolean enabled;
	private String name;
	private String email;
	private String usasId;

	private Collection<Authority> authorities;

	public User() {
	}

	public User(String username, String name) {
		this.username = username;
		this.name = name;
	}

	@Id
	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}

	public String getUsasId() {
		return usasId;
	}

	public void setUsasId(String usasId) {
		this.usasId = usasId;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Boolean getEnabled() {
		return enabled;
	}

	public void setEnabled(Boolean enabled) {
		this.enabled = enabled;
	}

    @OneToMany(mappedBy = "username", fetch=FetchType.EAGER)
    public Collection<Authority> getAuthorities() {
        return authorities;
    }

	public void setAuthorities(Collection<Authority> authorities) {
		this.authorities = authorities;
	}

    @Override
    public String toString() {
    	return String.format("%s@%x; Username: %s; Password: %s; Enabled: %s; Authorities: %s; Name: %s; Email: %s; UsasId: %s;",
    			this.getClass().getName(), this.hashCode(),
    			getUsername(), getPassword(), getEnabled(), getAuthorities(),
    			getName(), getEmail(), getUsasId());
    }

}

Authority.java

package org.bwgz.swim.openlane.model;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "Authorities")
public class Authority implements Serializable {
	private static final long serialVersionUID = -3475658623185783516L;

	private String username;
	private String authority;

	public Authority() {
	}

	public Authority(String username, String authority) {
		this.username = username;
		this.setAuthority(authority);
	}

	@Id
        public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getAuthority() {
		return authority;
	}

	public void setAuthority(String authority) {
		this.authority = authority;
	}

    @Override
    public String toString() {
    	return String.format("%s@%x; Username: %s; Authority: %s;", this.getClass().getName(), this.hashCode(), getUsername(), getAuthority());
    }

}

For now I’ll use an in-memory instance of HSQLDB to store my data. I initialize the tables with a SQL file that Hibernate loads when the application starts up.

import.sql

insert into Users (username, password, enabled, name, email, usasId) values ('keith', '417c7382b16c395bc25b5da1398cf076', TRUE, 'Keith Lee', 'keith@email.com', 'leemkei0891' )

insert into Authorities (username, authority) values ('keith', 'ROLE_USER, ROLE_SUPERVISOR, ROLE_SWIMMER' )

Now when I go to the profile page I can see that SWF’s currentUser and the user’s profile are set.

Current User: Name: keith
Credentials: [ROLE_USER, ROLE_SUPERVISOR, ROLE_SWIMMER]
Principal: org.springframework.security.core.userdetails.User@0: Username: keith; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER, ROLE_SUPERVISOR, ROLE_SWIMMER
Autorities: [ROLE_USER, ROLE_SUPERVISOR, ROLE_SWIMMER]
Details: org.springframework.security.web.authentication.WebAuthenticationDetails@255f8: RemoteIpAddress: 127.0.0.1; SessionId: 543BB337D17562B62F8CFFC8428272FB
User Profile: Object: org.bwgz.swim.openlane.model.User@3c8c7; Username: keith; Password: 417c7382b16c395bc25b5da1398cf076; Enabled: true; Authorities: [org.bwgz.swim.openlane.model.Authority@14bda9d; Username: keith; Authority: ROLE_USER, ROLE_SUPERVISOR, ROLE_SWIMMER;]; Name: Keith Lee; Email: keith@email.com; UsasId: leemkei0891;
Username: keith
Name: Keith Lee
Email: keith@email.com
UsasId: leemkei0891

Source code is available at github.