diff --git a/.gitignore b/.gitignore
index 29937ae..b879e54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
-out
-logs
-lib
-*.iml
-.idea
+/out
+/logs
+/lib
+/*.iml
+/.idea
+/.*.db
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b698fae
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+Based on Simple Public License (SimPL) 2.0
+The SimPL applies to the software's source and object code and comes with any rights that I have in it (other than trademarks).
+You agree to the SimPL by copying, distributing, or making a derivative work of the software.
+
+You get the royalty free right to:
+- Use the software for any purpose;
+- Make derivative works of it (this is called a "Derived Work");
+- Copy and distribute it and any Derived Work.
+
+There are some things that you must shoulder:
+- You get NO WARRANTIES. None of any kind;
+- If the software damages you in any way, you may only recover direct damages up to the amount you paid for it
+ (that is zero if you did not pay anything). You may not recover any other damages, including those called
+ "consequential damages." (The state or country where you live may not allow you to limit your liability in
+ this way, so this may not apply to you);
diff --git a/README b/README
index 7e2e73c..a266a9a 100644
--- a/README
+++ b/README
@@ -1 +1,41 @@
-This project has some branches that can be merged into your project to add skeleton stuff.
+This is a skeleton project for a Java webapp, done right.
+
+It includes:
+- Ant & Ivy for dependency management
+- Jersey for handling of http requests in a RESTful way
+- Spring for dependency injection in the code
+- H2 database (Windows users may need to edit jetty-web.xml)
+- Liquibase for DB migration
+- Hibernate for working with the DB
+- JUnit & Mockito for unit testing
+
+After cloning, run src/Launcher.java to start. Everything is preconfigured.
+
+Small amount of code it contains implements a simple database of good Photo Spots,
+which uses google maps to browse and edit them.
+
+Or you can follow how this all have been created by pulling various branches
+into your own branch in this order:
+- start - empty project
+- ivy - add ivy for dependency management
+- launcher - adds Jetty launcher to run the app without any external application servers
+- map - adds html with Google Map
+- web.xml - first step for Java web app
+- logging - adds good logging, useful for any web app
+- jersey - adds Jersey (JAX-RS implementation) for handling HTTP REST requests
+- map-with-spots - adds dummy photo spots to the Google Map
+- add-spot - possibility to add new spots to the map
+- spring - adds Spring for dependency injection
+- db - adds H2 database
+- hibernate - adds Hibernate for persisting of objects without hand-coded JDBC code
+- tests - adds some unit tests (in real life these should be added on every step)
+- db-testing - adds plain JDBC implementation for persistence as well as more tests, see 2nd presentation below
+
+By following these branches in this order you can also get an idea how to build an app step-by-step,
+following Just Enough Design principle.
+
+See related talk from GOTOCon and Devclub here - "How to get Java back":
+http://www.slideshare.net/antonkeks/simplicity-8971441
+
+And another one about DB testing from Topconf - "3 tales of testing of DB-enabled apps":
+http://prezi.com/kq0ghszq6e-j/3-tales-of-testing-db-enabled-apps/
diff --git a/build.xml b/build.xml
index 9dab332..d918a07 100644
--- a/build.xml
+++ b/build.xml
@@ -2,12 +2,8 @@
-
-
-
-
-
+
diff --git a/db-test/dbunit.pkb b/db-test/dbunit.pkb
new file mode 100644
index 0000000..142c99e
--- /dev/null
+++ b/db-test/dbunit.pkb
@@ -0,0 +1,45 @@
+create or replace package body dbunit
+is
+ procedure fail(message varchar2) is
+ begin
+ raise_application_error(-20000, 'Assertion failed' || chr(10) || message);
+ end;
+
+ procedure fail(expected varchar2, actual varchar2) is
+ begin
+ fail('Expected: ' || expected || chr(10) || 'Actual: ' || actual);
+ end;
+
+ procedure expect_exception is
+ begin
+ fail('Exception expected');
+ end;
+
+ procedure assert_equals(expected number, actual number) is
+ begin
+ if expected is null or actual is null or expected != actual then
+ fail(expected, actual);
+ end if;
+ end;
+
+ procedure assert_equals(expected varchar2, actual varchar2) is
+ begin
+ if expected is null or actual is null or expected != actual then
+ fail(expected, actual);
+ end if;
+ end;
+
+ procedure assert_null(actual varchar2) is
+ begin
+ if actual is not null then
+ fail(actual || ' is not null');
+ end if;
+ end;
+
+ procedure assert_not_null(actual varchar2) is
+ begin
+ if actual is null then
+ fail('argument is null');
+ end if;
+ end;
+end;
diff --git a/db-test/dbunit.pkh b/db-test/dbunit.pkh
new file mode 100644
index 0000000..c49373e
--- /dev/null
+++ b/db-test/dbunit.pkh
@@ -0,0 +1,9 @@
+create or replace package dbunit
+is
+ procedure fail(message varchar2 default '');
+ procedure expect_exception;
+ procedure assert_equals(expected number, actual number);
+ procedure assert_equals(expected varchar2, actual varchar2);
+ procedure assert_null(actual varchar2);
+ procedure assert_not_null(actual varchar2);
+end;
diff --git a/db-test/liquibase.xml b/db-test/liquibase.xml
new file mode 100644
index 0000000..6c2b590
--- /dev/null
+++ b/db-test/liquibase.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+ create or replace public synonym dbunit for dbunit;
+ grant execute on dbunit to public;
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/db-test/photo_spots_test.sql b/db-test/photo_spots_test.sql
new file mode 100644
index 0000000..a56c78b
--- /dev/null
+++ b/db-test/photo_spots_test.sql
@@ -0,0 +1,32 @@
+declare
+ spot_id number;
+ spot photospot%rowtype;
+begin
+ -- Can add photo spots
+ spot_id := photo_spots.add('Teletorn', 'Tallinn TV Tower', 59.47111, 24.8875);
+ dbunit.assert_not_null(spot_id);
+
+ select * into spot from photospot where id = spot_id;
+ dbunit.assert_equals('Teletorn', spot.name);
+ dbunit.assert_equals('Tallinn TV Tower', spot.description);
+ dbunit.assert_equals(59.47111, spot.latitude);
+ dbunit.assert_equals(24.8875, spot.longitude);
+
+ -- Cannot insert illegal latitude
+ begin
+ spot_id := photo_spots.add('A', 'B', 91.0, 24.0);
+ dbunit.expect_exception;
+ exception
+ when photo_spots.illegal_coordinates then null;
+ end;
+
+ -- Cannot insert illegal longitude
+ begin
+ spot_id := photo_spots.add('A', 'B', 59.0, -181);
+ dbunit.expect_exception;
+ exception
+ when photo_spots.illegal_coordinates then null;
+ end;
+
+ rollback;
+end;
diff --git a/db/liquibase.xml b/db/liquibase.xml
new file mode 100644
index 0000000..13ee553
--- /dev/null
+++ b/db/liquibase.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ begin dbms_utility.compile_schema('demo'); end;
+
+
+
+
diff --git a/db/photo_spots.pkb b/db/photo_spots.pkb
new file mode 100644
index 0000000..3205cbb
--- /dev/null
+++ b/db/photo_spots.pkb
@@ -0,0 +1,14 @@
+create or replace package body photo_spots is
+ function add(name varchar2, description varchar2, lat float, lon float) return number is
+ id number;
+ begin
+ if lat < -90 or lat > 90 or lon < -180 or lon > 180 then
+ raise illegal_coordinates;
+ end if;
+
+ select hibernate_sequence.nextval into id from dual;
+ insert into photospot (id, name, description, latitude, longitude)
+ values (id, name, description, lat, lon);
+ return id;
+ end;
+end;
\ No newline at end of file
diff --git a/db/photo_spots.pkh b/db/photo_spots.pkh
new file mode 100644
index 0000000..cbb050c
--- /dev/null
+++ b/db/photo_spots.pkh
@@ -0,0 +1,5 @@
+create or replace package photo_spots is
+ illegal_coordinates exception;
+
+ function add(name varchar2, description varchar2, lat float, lon float) return number;
+end;
diff --git a/src/ee/devclub/model/HibernatePhotoSpotRepository.java b/src/ee/devclub/model/HibernatePhotoSpotRepository.java
new file mode 100644
index 0000000..424dc38
--- /dev/null
+++ b/src/ee/devclub/model/HibernatePhotoSpotRepository.java
@@ -0,0 +1,23 @@
+package ee.devclub.model;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.orm.hibernate3.HibernateOperations;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public class HibernatePhotoSpotRepository implements PhotoSpotRepository {
+ @Autowired HibernateOperations hibernate;
+
+ @Override
+ public List getAllSpots() {
+ return hibernate.loadAll(PhotoSpot.class);
+ }
+
+ @Override
+ public PhotoSpot persist(PhotoSpot spot) {
+ hibernate.saveOrUpdate(spot);
+ return spot;
+ }
+}
diff --git a/src/ee/devclub/model/JDBCPhotoSpotRepository.java b/src/ee/devclub/model/JDBCPhotoSpotRepository.java
new file mode 100644
index 0000000..e3d674b
--- /dev/null
+++ b/src/ee/devclub/model/JDBCPhotoSpotRepository.java
@@ -0,0 +1,63 @@
+package ee.devclub.model;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JDBCPhotoSpotRepository implements PhotoSpotRepository {
+ @Autowired DataSource dataSource;
+
+ public List getAllSpots() {
+ Connection conn = null;
+ try {
+ List spots = new ArrayList();
+ conn = dataSource.getConnection();
+ ResultSet rs = conn.prepareStatement("select * from PhotoSpot").executeQuery();
+ while (rs.next()) {
+ spots.add(new PhotoSpot(rs.getString("name"), rs.getString("description"),
+ new Location(rs.getFloat("latitude"), rs.getFloat("longitude"))));
+ }
+ return spots;
+ }
+ catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ finally {
+ closeSilently(conn);
+ }
+ }
+
+ public PhotoSpot persist(PhotoSpot spot) {
+ Connection conn = null;
+ try {
+ conn = dataSource.getConnection();
+ PreparedStatement stmt = conn.prepareStatement("insert into PhotoSpot (name, description, latitude, longitude) values (?, ?, ?, ?)");
+ stmt.setString(1, spot.name);
+ stmt.setString(2, spot.description);
+ stmt.setFloat(3, spot.location.latitude);
+ stmt.setFloat(4, spot.location.longitude);
+ stmt.execute();
+ return spot;
+ }
+ catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ finally {
+ closeSilently(conn);
+ }
+ }
+
+ private void closeSilently(Connection conn) {
+ try {
+ if (conn != null) conn.close();
+ }
+ catch (SQLException ignore) {
+ }
+ }
+}
diff --git a/src/ee/devclub/model/Location.java b/src/ee/devclub/model/Location.java
index 1f79f3f..b8bba2c 100644
--- a/src/ee/devclub/model/Location.java
+++ b/src/ee/devclub/model/Location.java
@@ -5,8 +5,8 @@
@Embeddable
public class Location implements Serializable {
- private float latitude;
- private float longitude;
+ float latitude;
+ float longitude;
public Location() {
}
diff --git a/src/ee/devclub/model/PhotoSpot.java b/src/ee/devclub/model/PhotoSpot.java
index 34729c8..f2a6b78 100644
--- a/src/ee/devclub/model/PhotoSpot.java
+++ b/src/ee/devclub/model/PhotoSpot.java
@@ -1,10 +1,16 @@
package ee.devclub.model;
-import javax.persistence.*;
+import javax.persistence.Access;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
-@Entity @Access(AccessType.FIELD)
+import static javax.persistence.AccessType.FIELD;
+import static javax.persistence.GenerationType.AUTO;
+
+@Entity @Access(FIELD)
public class PhotoSpot {
- @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @GeneratedValue(strategy = AUTO)
@Id Long id;
String name;
@@ -31,4 +37,20 @@ public String getDescription() {
public Location getLocation() {
return location;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ PhotoSpot photoSpot = (PhotoSpot) o;
+ return !(description != null ? !description.equals(photoSpot.description) : photoSpot.description != null) && !(id != null ? !id.equals(photoSpot.id) : photoSpot.id != null) && !(location != null ? !location.equals(photoSpot.location) : photoSpot.location != null) && !(name != null ? !name.equals(photoSpot.name) : photoSpot.name != null);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (name != null ? name.hashCode() : 0);
+ return result;
+ }
}
diff --git a/src/ee/devclub/model/PhotoSpotRepository.java b/src/ee/devclub/model/PhotoSpotRepository.java
index 2ba89a6..e1e6af8 100644
--- a/src/ee/devclub/model/PhotoSpotRepository.java
+++ b/src/ee/devclub/model/PhotoSpotRepository.java
@@ -1,21 +1,9 @@
package ee.devclub.model;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.orm.hibernate3.HibernateOperations;
-import org.springframework.stereotype.Repository;
-
import java.util.List;
-@Repository
-public class PhotoSpotRepository {
- @Autowired HibernateOperations hibernate;
-
- public List getAllSpots() {
- return hibernate.loadAll(PhotoSpot.class);
- }
+public interface PhotoSpotRepository {
+ List getAllSpots();
- public PhotoSpot persist(PhotoSpot spot) {
- hibernate.saveOrUpdate(spot);
- return spot;
- }
+ PhotoSpot persist(PhotoSpot spot);
}
diff --git a/src/ee/devclub/rest/PhotoSpotResource.java b/src/ee/devclub/rest/PhotoSpotResource.java
index f54111f..c499042 100644
--- a/src/ee/devclub/rest/PhotoSpotResource.java
+++ b/src/ee/devclub/rest/PhotoSpotResource.java
@@ -12,10 +12,12 @@
@Produces("application/json")
public class PhotoSpotResource extends SpringAwareResource {
@Autowired PhotoSpotRepository repo;
+ int maxSpots = 1000;
@GET
public List getAllSpots() {
- return repo.getAllSpots();
+ List allSpots = repo.getAllSpots();
+ return allSpots.subList(0, Math.min(maxSpots, allSpots.size()));
}
@POST
diff --git a/test/ee/devclub/model/HibernatePhotoSpotRepositoryIntegrationTest.java b/test/ee/devclub/model/HibernatePhotoSpotRepositoryIntegrationTest.java
new file mode 100644
index 0000000..7ec878a
--- /dev/null
+++ b/test/ee/devclub/model/HibernatePhotoSpotRepositoryIntegrationTest.java
@@ -0,0 +1,58 @@
+package ee.devclub.model;
+
+import org.hibernate.dialect.H2Dialect;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+import org.springframework.orm.hibernate3.HibernateTemplate;
+import org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean;
+
+import javax.sql.DataSource;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.assertThat;
+
+public class HibernatePhotoSpotRepositoryIntegrationTest {
+ DataSource dataSource;
+ HibernatePhotoSpotRepository repo = new HibernatePhotoSpotRepository();
+
+ @Before
+ public void setUp() throws Exception {
+ dataSource = new DriverManagerDataSource("jdbc:h2:mem:hibernate;DB_CLOSE_DELAY=-1", "sa", "sa");
+
+ System.setProperty("hibernate.dialect", H2Dialect.class.getName());
+ System.setProperty("hibernate.hbm2ddl.auto", "create-drop");
+ AnnotationSessionFactoryBean sessionFactory = new AnnotationSessionFactoryBean();
+ sessionFactory.setDataSource(dataSource);
+ sessionFactory.setAnnotatedClasses(new Class[] {PhotoSpot.class});
+ sessionFactory.afterPropertiesSet();
+
+ repo.hibernate = new HibernateTemplate(sessionFactory.getObject());
+ }
+
+ @Test
+ public void loading() throws Exception {
+ dataSource.getConnection().createStatement()
+ .execute("insert into PhotoSpot (id, name, description, latitude, longitude) values (1, 'Kohtuotsa', 'Mega place!', 59.437755, 24.74209)");
+
+ List spots = repo.getAllSpots();
+ assertThat(spots.size(), is(1));
+
+ PhotoSpot spot = spots.get(0);
+ assertThat(spot.name, is("Kohtuotsa"));
+ assertThat(spot.description, is("Mega place!"));
+ assertThat(spot.location, is(new Location(59.437755f, 24.74209f)));
+ }
+
+ @Test
+ public void fullCycle() throws Exception {
+ PhotoSpot spot = new PhotoSpot("Teletorn", "Tallinn TV Tower", new Location(59.47111f, 24.8875f));
+ repo.persist(spot);
+ repo.hibernate.clear();
+
+ PhotoSpot spot2 = repo.getAllSpots().get(0);
+ assertThat(spot2, not(sameInstance(spot)));
+ assertThat(spot2, equalTo(spot));
+ }
+}
diff --git a/test/ee/devclub/model/HibernatePhotoSpotRepositoryTest.java b/test/ee/devclub/model/HibernatePhotoSpotRepositoryTest.java
new file mode 100644
index 0000000..7ed20fa
--- /dev/null
+++ b/test/ee/devclub/model/HibernatePhotoSpotRepositoryTest.java
@@ -0,0 +1,31 @@
+package ee.devclub.model;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.orm.hibernate3.HibernateOperations;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class HibernatePhotoSpotRepositoryTest {
+ HibernatePhotoSpotRepository repo = new HibernatePhotoSpotRepository();
+
+ @Before
+ public void initMocks() throws Exception {
+ repo.hibernate = mock(HibernateOperations.class);
+ }
+
+ @Test
+ public void allPhotoSpotsComeFromDB() throws Exception {
+ when(repo.hibernate.loadAll(PhotoSpot.class)).thenReturn(asList(new PhotoSpot("Kohtuotsa", "", new Location(59.437755f, 24.74209f))));
+
+ List spots = repo.getAllSpots();
+ assertThat(spots.size(), is(1));
+ assertThat(spots.get(0).name, is("Kohtuotsa"));
+ }
+}
diff --git a/test/ee/devclub/model/JDBCPhotoSpotRepositoryIntegrationTest.java b/test/ee/devclub/model/JDBCPhotoSpotRepositoryIntegrationTest.java
new file mode 100644
index 0000000..fc9e2e1
--- /dev/null
+++ b/test/ee/devclub/model/JDBCPhotoSpotRepositoryIntegrationTest.java
@@ -0,0 +1,53 @@
+package ee.devclub.model;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.jdbc.datasource.DriverManagerDataSource;
+
+import java.sql.Connection;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+public class JDBCPhotoSpotRepositoryIntegrationTest {
+ Connection conn;
+ JDBCPhotoSpotRepository repo = new JDBCPhotoSpotRepository();
+
+ @Before
+ public void initMockDB() throws Exception {
+ repo.dataSource = new DriverManagerDataSource("jdbc:h2:mem:test", "sa", "sa");
+ conn = repo.dataSource.getConnection();
+
+ conn.createStatement().execute("create table PhotoSpot (id int auto_increment primary key, name varchar, description varchar, latitude float, longitude float)");
+ }
+
+ @After
+ public void destroyMockDB() throws Exception {
+ conn.close();
+ }
+
+ @Test
+ public void loading() throws Exception {
+ conn.createStatement().execute("insert into PhotoSpot values (1, 'Kohtuotsa', 'Mega place!', 59.437755, 24.74209)");
+
+ List spots = repo.getAllSpots();
+ assertThat(spots.size(), is(1));
+
+ PhotoSpot spot = spots.get(0);
+ assertThat(spot.name, is("Kohtuotsa"));
+ assertThat(spot.description, is("Mega place!"));
+ assertThat(spot.location, is(new Location(59.437755f, 24.74209f)));
+ }
+
+ @Test
+ public void fullCycle() throws Exception {
+ PhotoSpot spot = new PhotoSpot("Teletorn", "Tallinn TV Tower", new Location(59.47111f, 24.8875f));
+ repo.persist(spot);
+
+ PhotoSpot spot2 = repo.getAllSpots().get(0);
+ assertThat(spot2, not(sameInstance(spot)));
+ assertThat(spot2, equalTo(spot));
+ }
+}
diff --git a/test/ee/devclub/model/JDBCPhotoSpotRepositoryTest.java b/test/ee/devclub/model/JDBCPhotoSpotRepositoryTest.java
new file mode 100644
index 0000000..bd6fc32
--- /dev/null
+++ b/test/ee/devclub/model/JDBCPhotoSpotRepositoryTest.java
@@ -0,0 +1,51 @@
+package ee.devclub.model;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.sql.DataSource;
+import java.sql.ResultSet;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.*;
+
+public class JDBCPhotoSpotRepositoryTest {
+ JDBCPhotoSpotRepository repo = new JDBCPhotoSpotRepository();
+
+ @Before
+ public void setUp() throws Exception {
+ repo.dataSource = mock(DataSource.class, RETURNS_DEEP_STUBS);
+ }
+
+ @Test
+ public void spotFieldsAreCorrectlyMappedToDBColumns() throws Exception {
+ ResultSet rs = repo.dataSource.getConnection().prepareStatement("select * from PhotoSpot").executeQuery();
+ when(rs.next()).thenReturn(true, false);
+
+ when(rs.getString("name")).thenReturn("Kohtuotsa");
+ when(rs.getString("description")).thenReturn("Mega place!");
+ when(rs.getFloat("latitude")).thenReturn(59.437755f);
+ when(rs.getFloat("longitude")).thenReturn(24.74209f);
+
+ List spots = repo.getAllSpots();
+
+ assertEquals(1, spots.size());
+ PhotoSpot spot = spots.get(0);
+
+ assertThat(spot.name, is("Kohtuotsa"));
+ assertThat(spot.description, is("Mega place!"));
+ assertThat(spot.location, is(new Location(59.437755f, 24.74209f)));
+
+ verify(repo.dataSource.getConnection()).close();
+ }
+
+ @Test
+ public void manySpotFieldsCanBeLoaded() throws Exception {
+ ResultSet rs = repo.dataSource.getConnection().prepareStatement("select * from PhotoSpot").executeQuery();
+ when(rs.next()).thenReturn(true, true, true, false);
+ assertThat(repo.getAllSpots().size(), is(3));
+ }
+}
diff --git a/test/ee/devclub/rest/PhotoSpotResourceTest.java b/test/ee/devclub/rest/PhotoSpotResourceTest.java
new file mode 100644
index 0000000..3cbef6a
--- /dev/null
+++ b/test/ee/devclub/rest/PhotoSpotResourceTest.java
@@ -0,0 +1,44 @@
+package ee.devclub.rest;
+
+import ee.devclub.model.Location;
+import ee.devclub.model.PhotoSpot;
+import ee.devclub.model.PhotoSpotRepository;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import static java.util.Collections.*;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+public class PhotoSpotResourceTest {
+ PhotoSpotResource resource = new PhotoSpotResource();
+
+ @Before
+ public void initMocks() throws Exception {
+ resource.repo = mock(PhotoSpotRepository.class, RETURNS_DEEP_STUBS);
+ }
+
+ @Test
+ public void resourceLimitedNumberOfSpotsInRepo() throws Exception {
+ resource.maxSpots = 10;
+ PhotoSpot photoSpot = mock(PhotoSpot.class);
+ when(resource.repo.getAllSpots()).thenReturn(nCopies(15, photoSpot));
+
+ assertEquals(10, resource.getAllSpots().size());
+ }
+
+ @Test
+ public void newPhotoSpotsArePersisted() throws Exception {
+ resource.newPhotoSpot("Aegna island", "WWI defence structures", 59.583771f, 24.749720f);
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(PhotoSpot.class);
+ verify(resource.repo).persist(captor.capture());
+
+ PhotoSpot spot = captor.getValue();
+ assertThat(spot.getName(), is("Aegna island"));
+ assertThat(spot.getDescription(), is("WWI defence structures"));
+ assertThat(spot.getLocation(), is(new Location(59.583771f, 24.749720f)));
+ }
+}
diff --git a/webapp/WEB-INF/jetty-web.xml b/webapp/WEB-INF/jetty-web.xml
index 4505ef3..c202260 100644
--- a/webapp/WEB-INF/jetty-web.xml
+++ b/webapp/WEB-INF/jetty-web.xml
@@ -9,6 +9,7 @@
true
jdbc:h2:.simple-java;AUTO_SERVER=TRUE;USER=sa;PASSWORD=sa
+
5
5
true
diff --git a/webapp/WEB-INF/liquibase.xml b/webapp/WEB-INF/liquibase.xml
deleted file mode 100644
index 4aec211..0000000
--- a/webapp/WEB-INF/liquibase.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/webapp/WEB-INF/spring.xml b/webapp/WEB-INF/spring.xml
index e307ab5..e0f02a3 100644
--- a/webapp/WEB-INF/spring.xml
+++ b/webapp/WEB-INF/spring.xml
@@ -2,7 +2,7 @@
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
@@ -13,11 +13,15 @@
-
-
+
-
+
+
+
+
+
+