The default behavior of the tomcat6 server installed from the ubuntu repository is to forbid any read operation of System.getProperty(). I used tomcat6 as deployment target for the hudson builds on the same machine. AspectJ is used in a sample Spring 3 REST application. Tomcat6 logs (catalina-…log) shows the following exception
Caused by: java.security.AccessControlException: access denied (java.util.PropertyPermission org.aspectj.tracing.debug read)
at java.security.AccessControlContext.checkPermission(AccessControlContext.java:323)
at java.security.AccessController.checkPermission(AccessController.java:546)
at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
at java.lang.SecurityManager.checkPropertyAccess(SecurityManager.java:1285)
at java.lang.System.getProperty(System.java:686)
at org.aspectj.weaver.tools.TraceFactory.getBoolean(TraceFactory.java:34)
at org.aspectj.weaver.tools.TraceFactory.(TraceFactory.java:21)
... 68 more
Updating aspectj in the maven pom to the latest version 1.6.8 doesn’t solve
the problem. The same war file works under windows with tomcat 6.0.24 without any problems. An Forum entry shows the right hint. Inside the init script
/etc/init.d/tomcat6 is default TOMCAT_SECURITY set to true, which forces the /etc/tomcat6/policy.d/* policies to be used in every web
application inside tomcat.
# Use the Java security manager? (yes/no)
TOMCAT6_SECURITY=yes
So the solution can be quick (set the TOMCAT_SECURITY property inside the init script to no) or a bit more complicated (set a new policy for your web application as new file inside the policy.d folder). The problem with the more complicated one is that you need to know every security relevant operation to write the policy file).
SpringSource has recently released 3.0.1 of their Spring Framework. Spring 3 extended the REST functionalities inside the Web MVC part of the Framework. Rest Service are defined as Controller and can be tested with the new Resttemplate. In this little sample project is Spring 3 combined with JUnit 4, Maven, Hibernate 3.2, Jetty, MySQL, JPA, JAXB and AspectJ. Additional tests was made with Poster as a Firefox Plugin to test REST bases Webservices. Jetty 6.1 is used as embedded container for the web application as backend for the seperated maven integration tests. Jetty can be started with mvn jetty:run for tests with Poster or will be started before the integration tests runs with mvn integration-test. The Project is configured via Maven pom.xml as seen later in this post. Via mvn eclipse:eclipse will the eclipse settings generated to use the project as WTP project. I used Maven 2.2.1 in combination with eclipse 3.5.2.. As backend infrastructure runs inside Ubuntu 9.1 Server a Bugzilla 3, Artifactory 2.2.1, Subversion 1.6 and a Hudson 1.348.
REST as representational state transferr has less protocol overhead as SOAP. It heavily depends on HTTP mechanism like PUT, GET, DELETE or POST method calls or HTTP accept header definition of the mime type like ‘text/xml’, ‘text/plain’ or ‘image/jpeg’ to define delivered and expected content. So you make a HTTP GET request with an accept header ‘image/jpeg’ with a url like http://myserver/restservice/1 in firefox to see their a picture of catalog item with id 1. A HTTP Post send data to the server as new data and a PUT override existing data. HTTP 1.1 defines a list of methods to use in your REST based Service.
The whole project is Java annotation driven and use JDK 1.6 but can easily switched to run with JDK 1.5 as well. Configuration is Spring based as IOC pattern and as annotations inside the Java classes. XML content is converted by JAXB Marshaller to use only domain objects inside your business logic. Persistence is defined as JPA annotations with Hibernate as implementation against a MySQL 5 database. The code was inspired by a blog from Solomon Duskis.
The Java Source files
The controller RestServiceController
package de.schaeftlein.dev.spring.rest.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import de.schaeftlein.dev.spring.rest.dao.PersonDao;
import de.schaeftlein.dev.spring.rest.domain.People;
import de.schaeftlein.dev.spring.rest.domain.Person;
@Controller
@RequestMapping("/people")
public class RestServiceController
{
@Autowired
private PersonDao personDao;
@RequestMapping(method = RequestMethod.GET)
@Transactional(readOnly = true)
@ResponseBody
public People getAll() {
List persons = personDao.getPeople();
People people = new People(persons);
return people;
}
@RequestMapping(value = "/person/{id}", method = RequestMethod.GET)
@ResponseBody
@Transactional(readOnly = true)
public Person getPerson(@PathVariable("id") Long personId) {
return personDao.getPerson(personId);
}
@RequestMapping(method = RequestMethod.POST)
@Transactional(readOnly = false)
@ResponseBody
public Person savePerson(@RequestBody Person person) {
personDao.savePerson(person);
return person;
}
}
The Interface for the DAO PersonDAO
package de.schaeftlein.dev.spring.rest.dao;
import java.util.List;
import de.schaeftlein.dev.spring.rest.domain.Person;
public interface PersonDao {
public Person getPerson(Long personId);
public void savePerson(Person person);
public List getPeople();
public Person getPersonByUsername(String username);
}
The DAO implementation PersonDAOHibernate
package de.schaeftlein.dev.spring.rest.dao.hibernate;
import java.util.List;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import de.schaeftlein.dev.spring.rest.dao.PersonDao;
import de.schaeftlein.dev.spring.rest.domain.Person;
@Repository
@Transactional
@SuppressWarnings("unchecked")
public class PersonDaoHibernate extends HibernateDaoSupport implements
PersonDao {
@Autowired
public void setupSessionFactory(SessionFactory sessionFactory) {
this.setSessionFactory(sessionFactory);
}
public Person getPerson(Long personId) throws DataAccessException {
return this.getHibernateTemplate().get(Person.class, personId);
}
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void savePerson(Person person) throws DataAccessException {
this.getHibernateTemplate().saveOrUpdate(person);
}
public List getPeople() throws DataAccessException {
return this.getHibernateTemplate().find("select people from Person people");
}
public Person getPersonByUsername(String username) {
List people = this.getHibernateTemplate().findByNamedParam(
"select people from Person people "
+ "where people.username = :username", "username", username);
Person person = getFirst(people);
if (person != null)
getHibernateTemplate().evict(person);
return person;
}
private static T getFirst(List list) {
return CollectionUtils.isEmpty(list) ? null : list.get(0);
}
}
The JAXB domain object People
package de.schaeftlein.dev.spring.rest.domain;
import java.io.Serializable;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class People implements Serializable {
private static final long serialVersionUID = 1L;
private List person;
public People() {
// empty constructor required for JAXB
}
public People(List person) {
this.person = person;
}
public List getPerson() {
return person;
}
public void setPerson(List person) {
this.person = person;
}
}
The JAXB and JPA entity domain object Person
package de.schaeftlein.dev.spring.rest.domain;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.xml.bind.annotation.XmlRootElement;
@Entity
@XmlRootElement
public class Person implements Serializable
{
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String firstName;
private String lastName;
private String username;
private String password;
private int roleLevel;
@Version
private Integer version;
public Person()
{
}
public Person(String firstName, String lastName, String username, String password, int roleLevel)
{
this.firstName = firstName;
this.lastName = lastName;
this.username = username;
this.password = password;
this.roleLevel = roleLevel;
}
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getFirstName()
{
return firstName;
}
public void setFirstName(String firstName)
{
this.firstName = firstName;
}
public String getLastName()
{
return lastName;
}
public void setLastName(String lastName)
{
this.lastName = lastName;
}
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
public int getRoleLevel()
{
return roleLevel;
}
public void setRoleLevel(int roleLevel)
{
this.roleLevel = roleLevel;
}
public Integer getVersion()
{
return version == null ? 1 : version;
}
public void setVersion(Integer version)
{
this.version = version;
}
public enum RoleLevel {
ADMIN(1), GUEST(2), PUBLIC(3);
private final int level;
RoleLevel(int value)
{
this.level = value;
}
public static RoleLevel getLevel(String roleName)
{
return RoleLevel.valueOf(roleName);
}
public int getLevel()
{
return this.level;
}
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
else if (!(obj instanceof Person))
{
return false;
}
else
{
Person p = (Person) obj;
if (p.getId().equals(getId()) && p.getUsername().equals(getUsername()) &&
p.getVersion().equals(getVersion()) &&
p.getLastName().equals(getLastName()) && p.getPassword().equals(getPassword())
&& p.getFirstName().equals(getFirstName()) && p.getRoleLevel() == getRoleLevel())
{
return true;
}
}
return false;
}
}
The DAO test PersonDAOTest
package de.schaeftlein.dev.spring.rest.dao;
import java.util.List;
import static junit.framework.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import de.schaeftlein.dev.spring.rest.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:spring-context.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
public class PersonDaoTest {
@Autowired
PersonDao personDao;
private Logger log = LoggerFactory.getLogger(PersonDaoTest.class);
@Test
public void testPerson() {
String username = "jane.doe";
Person person = new Person("Jane", "Doe", username, "password",
Person.RoleLevel.ADMIN.getLevel());
person.setVersion(1);
personDao.savePerson(person);
final List people = personDao.getPeople();
assertEquals(1, people.size());
assertEquals(person,people.get(0));
Long personId = person.getId();
Person savedPerson = personDao.getPerson(personId);
assertEquals(username,savedPerson.getUsername());
}
@Test
public void testVersion() {
Person solomon = new Person("John", "Doe", "john.doe", "mypass",
Person.RoleLevel.ADMIN.getLevel());
// version 0
personDao.savePerson(solomon);
Integer version = solomon.getVersion();
log.info("old version:"+solomon.getVersion());
solomon = personDao.getPersonByUsername("john.doe");
solomon.setPassword("password1");
// version 1
personDao.savePerson(solomon);
log.info("new version:"+solomon.getVersion());
assertTrue(!(version.equals(solomon.getVersion())));
}
}
The Rest test RestClientTest
package de.schaeftlein.dev.spring.rest.itest.controller;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import java.util.Collections;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.web.client.RestTemplate;
import de.schaeftlein.dev.spring.rest.domain.People;
import de.schaeftlein.dev.spring.rest.domain.Person;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
public class RestClientTest
{
private static final String BASE_URL = "http://localhost:9090/spring3-rest-sample/people";
private Logger log = LoggerFactory.getLogger(RestClientTest.class);
@Autowired
private RestTemplate restTemplate;
@Test
public void saveAndGet() throws Exception{
// save
Person input = new Person("jane","doe","jane.doe","pw",Person.RoleLevel.ADMIN.getLevel());
assertNull(input.getId());
Person output = restTemplate.postForObject(BASE_URL, input, Person.class, new Object[]{});
assertNotNull("no person",output);
assertNotNull(output.getId());
assertEquals(input.getUsername(), output.getUsername());
log.info("Saved jane.doe with id "+output.getId());
// get all
People people = restTemplate.getForObject(BASE_URL, People.class,new Object[]{});
assertNotNull("no people",people);
assertNotNull("no persons in people",people.getPerson());
assertTrue("empty persons in people",!people.getPerson().isEmpty());
assertEquals("no one person in people",input.getUsername(),people.getPerson().get(0).getUsername());
log.info("Peple size "+people.getPerson().size());
// get id
Map vars = Collections.singletonMap("id", output.getId()+"");
Person idPerson = restTemplate.getForObject(BASE_URL+"/person/{id}", Person.class,vars);
assertNotNull("no person",idPerson);
assertNotNull(idPerson.getId());
assertEquals(input.getUsername(), idPerson.getUsername());
log.info("Get person by id <"+output.getId()+"> : "+idPerson.getUsername());
}
}
4.0.0de.schaeftlein.dev.spring.restspring3-rest-samplewar1.0-SNAPSHOTspring3-rest-sample Maven WebappA sample project for showing how to write JUnit Test against Spring 3 Rest ServicesRalf Schäftleinhttp://ralf.schaeftlein.com2010bugzillahttp://ubuntu-vm.localdomain/cgi-bin/bugzilla3/index.cgihttp://ralf.schaeftlein.de/2010/03/05/junit-tests-for-spring-3-rest-spring-3-rest-servicesorg.apache.maven.pluginsmaven-surefire-report-plugin2.5org.apache.maven.pluginsmaven-checkstyle-plugin2.5org.apache.maven.pluginsmaven-javadoc-plugin2.6.1org.codehaus.mojocobertura-maven-plugin2.3org.apache.maven.pluginsmaven-pmd-plugin2.4utf-81.6org.apache.maven.pluginsmaven-jxr-plugin2.1UTF-8UTF-8org.codehaus.mojotaglist-maven-plugin2.4org.apache.maven.pluginsmaven-changes-plugin2.3changes-reportinternal
dav:http://ubuntu-vm.localdomain:8081/artifactory/internal
snapshots
dav:http://ubuntu-vm.localdomain:8081/artifactory/snapshots
scm:svn:http://ubuntu-vm.localdomain/svn/
scm:svn:http://ubuntu-vm.localdomain/svn/
http://ubuntu-vm.localdomain/svn/Hudsonhttp://ubuntu-vm.localdomain/hudson/job/spring3-rest-samplespring3-rest-sampleorg.apache.maven.pluginsmaven-surefire-report-plugin2.5org.apache.maven.pluginsmaven-deploy-plugin2.5org.apache.maven.pluginsmaven-surefire-plugin2.5truesurefire-testtesttestfalse**/itest/**surefire-itestintegration-testtestfalse**/itest/**org.apache.maven.pluginsmaven-pmd-plugin2.4utf-81.6checkcpd-checkorg.codehaus.mojocobertura-maven-plugin2.3htmlxmlorg.apache.maven.pluginsmaven-jxr-plugin2.1org.apache.maven.pluginsmaven-checkstyle-plugin2.5maven-compiler-plugin2.11.6org.mortbay.jettymaven-jetty-plugin6.1.2210foo9999/${artifactId}909060000start-jettypre-integration-testrun0truestop-jettypost-integration-teststoporg.apache.maven.pluginsmaven-eclipse-plugin2.8truetrue2.0org.apache.maven.pluginsmaven-jar-plugin2.3trueorg.apache.maven.pluginsmaven-source-plugin2.1.1attach-sourcesverifyjarorg.apache.maven.pluginsmaven-scm-plugin1.3${svn_username}${svn_password}org.apache.maven.pluginsmaven-war-plugin2.1-beta-1src/main/webappsrc/main/webapp/WEB-INF/web.xmltruelib/src/main/resources**/.svn**/.svn/****/_svn_svn**/_svn/**src/test/resources**/.svn**/.svn/****/_svn_svn**/_svn/**3.0.1.RELEASE1.5.2UTF-8org.aspectjaspectjweaver1.6.2org.aspectjaspectjrt1.6.2org.springframeworkspring-beans${spring.framework.version}org.springframeworkspring-context-support${spring.framework.version}quartzquartzorg.springframeworkspring-core${spring.framework.version}org.springframeworkspring-aspects${spring.framework.version}org.springframeworkspring-jdbc${spring.framework.version}org.springframeworkspring-test${spring.framework.version}testorg.springframeworkspring-orm${spring.framework.version}org.springframeworkspring-oxm${spring.framework.version}org.springframeworkspring-tx${spring.framework.version}org.springframeworkspring-web${spring.framework.version}org.springframeworkspring-webmvc${spring.framework.version}cglibcglib-nodep2.1_3org.hibernatehibernate-annotations3.2.0.gaorg.hibernatehibernate3.2.6.gajavax.persistencepersistence-api1.0javassistjavassist3.11.0.GAmysqlmysql-connector-java5.1.12jarcompilec3p0c3p00.9.1.2jarcompilecommons-dbcpcommons-dbcp1.4commons-collectionscommons-collections3.2.1javax.transactionjta1.1javax.xml.bindjaxb-api2.0junitjunit4.8.1testorg.slf4jslf4j-api${org.slf4j.version}org.slf4jslf4j-simple${org.slf4j.version}org.mortbay.jettymaven-jetty-plugin6.1.15test
Sun has published milestone 5 (b76) of the upcoming Java JDK 7. JDK 7 release is currently scheduled for September 2010. My standard IDE is eclipse but the current production version 3.5 SR 1 support only Java up to JDK 6. I tried the new eclipse e4 1.0 milestone 2 (status: technical preview) which has JDK support up to JDK 7. You can define a JDK 7 as new JRE and create a new Java project with that JRE and compiler compliance level JDK 1.7. According to Joseph Darcy from SUN is a developer build of Netbeans available with support for JDK 7. I tested with code from CertPal which includes several features of JDK 7. Eclipse e4 still complain about compiler errors but the developer build of netbeans can compile and run the sample code. Check in netbeans under tools -> Java platform the availability of JDK 7 and create a new java project. Set in the java project under properties the source/binary format to jdk 1.7 (sources tab) and choose as java platform JDK 1.7 (libraries tab). On OpenJDK is a overview of the current implemented features of JDK 7 in the M5 version. Mark Reinhold from sun blogs as well about the new features. A more complete list of features which was formerly planned shows Alex Miller in his blog.
SpringSource has published release candidate 2 of the upcoming 3.0 release of their Spring Framework. New Feature is the compliance with JSR-330 (“Dependency Injection for Java”). The JSR was developed together by Google (for their Guice Framework and SpringSource (for their Spring Framework) and is finally approved since 14.10.2009.
A little example shows how to develop services with interfaces and implementations without dependencies to Spring Framework or Google Guice:
The Maven pom.xml
4.0.0de.schaeftlein.devjsr330-samplejarjsr330-sample0.0.1-SNAPSHOTSample App with Spring 3.0 RC 2 and JSR3303.0.0.RC2org.springframeworkspring-core${springVersion}org.springframeworkspring-beans${springVersion}org.springframeworkspring-context${springVersion}org.springframeworkspring-asm${springVersion}org.springframeworkspring-expression${springVersion}junitjunit4.7testjavax.injectjavax.inject1jsr330-samplesrc/main/java${basedir}/src/test/javasrc/main/resources**/.svn**/.svn/****/_svn_svn**/_svn/**src/test/resources**/.svn**/.svn/****/_svn_svn**/_svn/**org.apache.maven.pluginsmaven-compiler-plugin1.6org.apache.maven.pluginsmaven-eclipse-plugintruetrue_svn.svnorg.apache.maven.pluginsmaven-jar-plugintrueorg.apache.maven.pluginsmaven-source-pluginattach-sourcesverifyjar
package de.schaeftlein.dev.spring;
public interface SecureUtil
{
void compareEncryption(String input);
}
a implementation for our service
package de.schaeftlein.dev.spring;
import javax.inject.Inject;
import javax.inject.Named;
@Named("SecureUtil")
public class SecureUtilImpl implements SecureUtil
{
@Inject // automatically set by DI framework
@Named("secure") // get the namend bean
private Encryption secureEncryption;
@Inject // automatically set by DI framework
@Named("URLEncoderEncyrption") // get the bean by its classname
private Encryption unsecureEncryption;
public void compareEncryption(String input){
String encodedSecure = secureEncryption.encode(input);
System.out.println("Secure encoded: "+encodedSecure);
String encodeUnsecure = unsecureEncryption.encode(input);
System.out.println("Unsecure encoded: "+encodeUnsecure);
System.out.println("Secure decoded: "+secureEncryption.decode(encodedSecure));
System.out.println("Unsecure decoded: "+unsecureEncryption.decode(encodeUnsecure));
}
}
finally a main class for testing
package de.schaeftlein.dev.spring;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main
{
/**
* main method
*/
public static void main(String[] args)
{
ApplicationContext ctx = new AnnotationConfigApplicationContext(Main.class.getPackage().getName()); // new way to get Application context without applicationContext.xml available
SecureUtil util = ctx.getBean("SecureUtil", SecureUtil.class); // get bean with new generics method
util.compareEncryption("an sample input string 1234567890");
}
}
output of our Main class
19.11.2009 18:56:03 org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@c1b531: startup date [Thu Nov 19 18:56:03 CET 2009]; root of context hierarchy
19.11.2009 18:56:03 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
INFO: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
19.11.2009 18:56:03 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@a83b8a: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,secure,SecureUtil,URLEncoderEncyrption]; root of factory hierarchy
Secure encoded: QDQvd4uK14YNUQ4uoqhqsZEMDDxUqJAMyisvZr2wsA2GyMC5tSEIiw==
Unsecure encoded: an+sample+input+string+1234567890
Secure decoded: an sample input string 1234567890
Unsecure decoded: an sample input string 1234567890