Преглед изворни кода

Documented/refactored java classes.

Thomas Flucke пре 7 година
родитељ
комит
f9c1d196a0

+ 3 - 0
conf/app/application.properties

@@ -8,6 +8,9 @@ spring.data.mongodb.host=localhost
 spring.data.mongodb.port=27017
 spring.data.mongodb.database=ieat
 
+# web resources
+web.resources.timeout = 0 # seconds
+
 # logging
 logging.level.*=error
 #logging.config=log4j.properties

+ 0 - 14
conf/ivy.xml

@@ -1,14 +0,0 @@
-<ivy-module version="2.0">
-  <info organisation="name.tflucke" module="ieat2"/>
-  <configurations defaultconfmapping="default->default">
-    <conf name="default" />
-    <conf name="provided" description="Provided by the environment" />
-    <conf name="compile" extends="default,provided" />
-    <conf name="war" extends="default"/>
-    <conf name="deploy" description="Used by ant to deploy war files." />
-  </configurations>
-  <dependencies>
-    <!-- Ant dependencies -->
-    <dependency org="org.apache.tomcat" name="catalina-ant" rev="6.0.53" conf="deploy->default"/>
-  </dependencies>
-</ivy-module>

+ 0 - 8
conf/web.xml

@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
-         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_5_0.xsd"
-	     version="5.0">
-
-  <display-name>Online recipe manager.</display-name>
-</web-app>

+ 12 - 5
src/name/tflucke/ieat2/AppInitializer.java

@@ -1,15 +1,24 @@
 package name.tflucke.ieat2;
 
+import javax.servlet.Filter;
+
 import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
 import name.tflucke.ieat2.configs.RootConfig;
-import name.tflucke.ieat2.controllers.*;
 
+/**
+ * Entry point for running the spring application.
+ * 
+ * Provides reference to relevant classes with server configurations.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
 
-    //private static final Log LOGGER = LogFactory.getLog(WebInitializer.class);
+    private static final Log log = LogFactory.getLog(AppInitializer.class);
 
     @Override
     protected Class<?>[] getRootConfigClasses() {
@@ -18,7 +27,7 @@ public class AppInitializer extends AbstractAnnotationConfigDispatcherServletIni
 
     @Override
     protected Class<?>[] getServletConfigClasses() {
-        return new Class[] {TestController.class, ViewController.class};
+        return new Class[] {};
     }
 
     @Override
@@ -26,10 +35,8 @@ public class AppInitializer extends AbstractAnnotationConfigDispatcherServletIni
         return new String[] { "/" };
     }
 
-    /*
     @Override
     protected Filter[] getServletFilters() {
         return new Filter[] {};
     }
-    */
 }

+ 32 - 10
src/name/tflucke/ieat2/configs/DBConfig.java

@@ -6,8 +6,6 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.config.BeanDefinition;
-import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.core.type.filter.AnnotationTypeFilter;
 
 import org.apache.log4j.Logger;
@@ -18,22 +16,36 @@ import org.mongodb.morphia.Datastore;
 import org.mongodb.morphia.Morphia;
 import org.mongodb.morphia.annotations.Entity;
 
+/**
+ * Provides access the MongoDB and configures the connection.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 @Configuration
-@SpringBootApplication
 public class DBConfig {
-    static Logger log = Logger.getLogger(DBConfig.class.getName());
+    private static final Logger log = Logger.getLogger(DBConfig.class);
 
+    /**
+     * Name of database to connect
+     */
     @Value("${spring.data.mongodb.database:ieat}")
     private String dbName;
-    
+
+    /**
+     * Should be auto-populated from the config files.
+     */
     @Autowired
     private MongoClient mongoClient;
 
-    @Bean
-    public Datastore datastore() {
-        Morphia morphia = new Morphia();
-        ClassPathScanningCandidateComponentProvider entityScanner =
-            new ClassPathScanningCandidateComponentProvider(true);
+    /**
+     * Finds all models and adds a mapping from the Java POJO to mongo documents.
+     *
+     * @param morphia The morphia session to add mappings to.
+     */
+    private void addModelsToDB(Morphia morphia) {
+        ClassPathScanningCandidateComponentProvider entityScanner;
+        entityScanner = new ClassPathScanningCandidateComponentProvider(true);
         entityScanner.addIncludeFilter(new AnnotationTypeFilter(Entity.class));
         try {
             for (BeanDefinition candidate : entityScanner.findCandidateComponents("name.tflucke.ieat2.models")) {
@@ -44,7 +56,17 @@ public class DBConfig {
             log.error("Class found, then not found: ");
             log.error(cnfe);
         }
+    }
 
+    /**
+     * Creates a database connection that can take any model as an input.
+     *
+     * @return A new database connection to include in relevant controllers.
+     */
+    @Bean
+    public Datastore datastore() {
+        Morphia morphia = new Morphia();
+        addModelsToDB(morphia);
         return morphia.createDatastore(mongoClient, dbName);
     }
 }

+ 9 - 5
src/name/tflucke/ieat2/configs/RootConfig.java

@@ -1,15 +1,19 @@
 package name.tflucke.ieat2.configs;
 
-import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.ComponentScan;
-import org.springframework.context.annotation.Import;
 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
-@Configuration
-@EnableWebMvc
+/**
+ * Class which loads all other configuration classes.
+ * 
+ * One-time global configurations should be enabled here.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 @SpringBootApplication
+@EnableWebMvc
 @ComponentScan(basePackages = "name.tflucke.ieat2.*")
-@Import({DBConfig.class, WebConfig.class})
 public class RootConfig {
 }

+ 21 - 4
src/name/tflucke/ieat2/configs/WebConfig.java

@@ -2,20 +2,35 @@ package name.tflucke.ieat2.configs;
 
 import java.util.concurrent.TimeUnit;
 
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.ComponentScan;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Bean;
 import org.springframework.web.servlet.ViewResolver;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 import org.springframework.web.servlet.view.InternalResourceViewResolver;
 import org.springframework.web.servlet.view.JstlView;
 import org.springframework.web.servlet.resource.WebJarsResourceResolver;
 import org.springframework.http.CacheControl;
 
+/**
+ * Provides access to web resources such as jsp, css, and js files.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
+    
+    /**
+     * Expiration time of web resources in seconds.
+     */
+    @Value("${web.resources.timeout:0}")
+    private long timeout;
+
+    /**
+     * Allows access to jsp files inside /views
+     */
     @Bean
     public ViewResolver viewResolver() {
         InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
@@ -25,11 +40,13 @@ public class WebConfig implements WebMvcConfigurer {
         return viewResolver;
     }
 
+    @Override
     public void addResourceHandlers(ResourceHandlerRegistry registry) {
+        // Allow /static/** to access js/css files
         registry.addResourceHandler("/static/**")
             .addResourceLocations("classpath:/META-INF/resources/webjars/")
             .addResourceLocations("/js/")
-            .setCacheControl(CacheControl.maxAge(0L, TimeUnit.DAYS).cachePublic())
+            .setCacheControl(CacheControl.maxAge(timeout, TimeUnit.SECONDS).cachePublic())
             .resourceChain(true)
             .addResolver(new WebJarsResourceResolver());
 

+ 78 - 8
src/name/tflucke/ieat2/controllers/AbstractController.java

@@ -8,6 +8,9 @@ import java.lang.reflect.Modifier;
 
 import org.springframework.beans.factory.annotation.Autowired;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 import org.mongodb.morphia.Datastore;
 import org.mongodb.morphia.Key;
 import org.mongodb.morphia.query.Query;
@@ -19,12 +22,28 @@ import org.bson.types.ObjectId;
 import name.tflucke.ieat2.models.DBObject;
 import name.tflucke.ieat2.errors.ResourceNotFoundException;
 
+/**
+ * Generic framework controller providing sane defaults for most REST operations.
+ *
+ * This class does not map any request automatically, rather provides any easy
+ * way to have similar functionally spread out among child classes.
+ * The actual mapping is the responsibility of the children.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 public abstract class AbstractController<T extends DBObject> {
+
+    private static final Log log = LogFactory.getLog(AbstractController.class);
+
     @Autowired
     protected Datastore db;
     private final Class<T> clazz;
     private final Set<Field> fields;
 
+    /**
+     * Sets up class information about how/what to store.
+     */
     protected AbstractController(final Class<T> clazz)
     {
         this.clazz = clazz;
@@ -33,16 +52,34 @@ public abstract class AbstractController<T extends DBObject> {
             .collect(Collectors.toSet());
     }
 
+    /**
+     * Convert a hex string into a mongo key
+     *
+     * @param id The id to convert
+     * @return A key which matches the given id
+     */
     protected Key<T> toKey(final String id) {
         return new Key(clazz, clazz.getSimpleName(), new ObjectId(id));
     }
 
+    /**
+     * Create a query for documents with the given id
+     *
+     * @param id The id to query
+     * @return A query which will return the element(s) matching the id
+     */
     protected Query<T> toQuery(final String id) {
         return db.createQuery(clazz).field("id").equal(new ObjectId(id));
     }
     
+    /**
+     * Get the document with the matching id
+     *
+     * @param id The id of the element to fetch
+     * @return The element with the given id
+     */
     protected T get(final String id) {
-        T result = db.getByKey(clazz, toKey(id));
+        final T result = db.getByKey(clazz, toKey(id));
         if (result == null)
             {
                 throw new ResourceNotFoundException();
@@ -53,34 +90,67 @@ public abstract class AbstractController<T extends DBObject> {
             }
     }
     
+    /**
+     * Insert a new element into the database.
+     *
+     * Any provided id is ignored as a new one will be assigned automatically.
+     *
+     * @param newElement An element to insert into the database
+     * @return The original element inserted with a new id
+     */
     protected T insert(T newElement) {
+        newElement.id = null;
         db.save(newElement);
         return newElement;
     }
-    
+
+    /**
+     * Save an element's not-null fields to the database with a given id.
+     * 
+     * Any fields with a null value will be ignored. 
+     *
+     * An element with the provided id must exist to be updated.
+     *
+     * This operation increments the version counter.
+     *
+     * @param id The id of the element to update
+     * @param element A reference element containing the changes to make
+     * @return The newly updated element
+     */
     protected T update(final String id, final T element) {
+        // Update is more complex to allow partial updates.
         final UpdateOperations<T> updateOps = db.createUpdateOperations(clazz);
         updateOps.inc("version");
         fields.forEach((Field field) -> {
                 try {
-                    Object value = field.get(element);
+                    final Object value = field.get(element);
                     if (value != null)
                         {
                             updateOps.set(field.getName(), value);
                         }
                 }
-                catch (IllegalAccessException iae) {
-                    // Filter at beginning should leave only public field.
+                catch (IllegalAccessException ise) {
+                    // Filter at beginning should leave only public fields.
                     // Should never get here.
-                    throw new RuntimeException("Tried to store non-public field", iae);
+                    log.error("Somehow tried to store a field that shouldn't have been accessible");
+                    log.error(ise);
+                    throw new IllegalStateException("Tried to store non-public field", ise);
                 }
             });
         db.update(toKey(id), updateOps);
         return get(id);
     }
-    
+
+    /**
+     * Remove the element with the matching id from the database.
+     *
+     * @param id The id of the element to remove
+     * @return The element removed from the database
+     * @throws ResourceNotFoundException If no item could be found
+     * with that id
+     */
     protected T delete(final String id) {
-        T result = db.findAndDelete(toQuery(id));
+        final T result = db.findAndDelete(toQuery(id));
         if (result == null)
             {
                 throw new ResourceNotFoundException();

+ 0 - 92
src/name/tflucke/ieat2/controllers/BasicFoodController.java

@@ -1,92 +0,0 @@
-package name.tflucke.ieat2.controllers;
-
-import java.util.List;
-
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-
-import name.tflucke.ieat2.models.BasicFood;
-import name.tflucke.ieat2.models.Food;
-import name.tflucke.ieat2.models.Recipe;
-
-@RestController("/food/")
-public class BasicFoodController extends AbstractController<Food> {
-
-    public BasicFoodController()
-    {
-        super(Food.class);
-    }
-    
-    /* Generic Food APIs */
-    
-    @GetMapping("/food/{id}")
-    public Food get(@PathVariable("id") final String id) {
-        return super.get(id);
-    }
-    
-    @GetMapping("/food/query")
-    public List<Food> query() {
-        return db.find(Food.class).asList();
-    }
-    
-    @GetMapping("/food/query/{query}")
-    public List<Food> query(@PathVariable("query") final String query) {
-        return db.find(Food.class).field("name").containsIgnoreCase(query).asList();
-    }
-    
-    @DeleteMapping("/food/{id}")
-    public Food delete(@PathVariable("id") final String id) {
-        return super.delete(id);
-    }
-
-    /* BasicFood APIs */
-    
-    @GetMapping("/food/basic/query")
-    public List<BasicFood> queryBasicFoods() {
-        return db.find(BasicFood.class).asList();
-    }
-    
-    @GetMapping("/food/basic/query/{query}")
-    public List<BasicFood> queryBasicFoods(@PathVariable("query") final String query) {
-        return db.find(BasicFood.class).field("name").containsIgnoreCase(query).asList();
-    }
-    
-    @PutMapping("/food/basic")
-    public BasicFood insert(@RequestBody BasicFood newElement) {
-        return (BasicFood) super.insert(newElement);
-    }
-
-    @PostMapping("/food/basic/{id}")
-    public BasicFood update(@PathVariable("id") final String id,
-                       @RequestBody final BasicFood element) {
-        return (BasicFood) super.update(id, element);
-    }
-
-    /* Recipe APIs */
-    
-    @GetMapping("/food/recipe/query")
-    public List<Recipe> queryRecipes() {
-        return db.find(Recipe.class).asList();
-    }
-    
-    @GetMapping("/food/recipe/query/{query}")
-    public List<Recipe> queryRecipes(@PathVariable("query") final String query) {
-        return db.find(Recipe.class).field("name").containsIgnoreCase(query).asList();
-    }
-    
-    @PutMapping("/food/recipe")
-    public Recipe insert(@RequestBody Recipe newElement) {
-        return (Recipe) super.insert(newElement);
-    }
-    
-    @PostMapping("/food/recipe/{id}")
-    public Recipe update(@PathVariable("id") final String id,
-                       @RequestBody final Recipe element) {
-        return (Recipe) super.update(id, element);
-    }
-}

+ 201 - 0
src/name/tflucke/ieat2/controllers/FoodController.java

@@ -0,0 +1,201 @@
+package name.tflucke.ieat2.controllers;
+
+import java.util.List;
+
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+
+import name.tflucke.ieat2.models.BasicFood;
+import name.tflucke.ieat2.models.Food;
+import name.tflucke.ieat2.models.Recipe;
+import name.tflucke.ieat2.errors.ResourceWrongTypeException;
+
+/**
+ * Provides APIs for managing Food objects and it's subclasses.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
+@RestController("/food/")
+public class FoodController extends AbstractController<Food> {
+
+    /**
+     * Creates a new controller
+     */
+    public FoodController()
+    {
+        super(Food.class);
+    }
+    
+    /* Generic Food APIs */
+
+    @Override
+    @GetMapping("/food/{id}")
+    public Food get(@PathVariable("id") final String id) {
+        return super.get(id);
+    }
+
+    /**
+     * Get a list of the most common foods.
+     *
+     * Provides a default behavior for empty queries.
+     * 
+     * @return A list of the most common foods
+     */
+    @GetMapping("/food/query")
+    public List<Food> query() {
+        return db.find(Food.class).asList();
+    }
+
+    /**
+     * Get a list of foods whose name contains a string.
+     *
+     * The input is case insensitive.
+     *
+     * @param query A name-fragment to search
+     * @return A list of foods with the query in it's name
+     */
+    @GetMapping("/food/query/{query}")
+    public List<Food> query(@PathVariable("query") final String query) {
+        return db.find(Food.class).field("name").containsIgnoreCase(query).asList();
+    }
+
+    @Override    
+    @DeleteMapping("/food/{id}")
+    public Food delete(@PathVariable("id") final String id) {
+        return super.delete(id);
+    }
+
+    /* BasicFood APIs */
+
+    /**
+     * Get a list of the most common BasicFoods.
+     *
+     * Provides a default behavior for empty queries.
+     * 
+     * @return A list of the most common basic foods
+     */
+    @GetMapping("/food/basic/query")
+    public List<BasicFood> queryBasicFoods() {
+        return db.find(BasicFood.class).asList();
+    }
+
+    /**
+     * Get a list of basic foods whose name contains a string.
+     *
+     * The input is case insensitive.
+     *
+     * @param query A name-fragment to search
+     * @return A list of basic foods with the query in it's name
+     */    
+    @GetMapping("/food/basic/query/{query}")
+    public List<BasicFood> queryBasicFoods(@PathVariable("query") final String query) {
+        return db.find(BasicFood.class).field("name").containsIgnoreCase(query).asList();
+    }
+
+    /**
+     * Insert a new basic food into the database.
+     *
+     * Any provided id is ignored as a new one will be assigned automatically.
+     *
+     * @param newElement A basic food to insert into the database
+     * @return The original element inserted with a new id
+     */
+    @PutMapping("/food/basic")
+    public BasicFood insert(@RequestBody BasicFood newElement) {
+        return (BasicFood) super.insert(newElement);
+    }
+
+    /**
+     * Save a basic food's not-null fields to the database with a given id.
+     * 
+     * Any fields with a null/missing value will be ignored. 
+     *
+     * An element with the provided id must exist to be updated.
+     *
+     * This operation increments the version counter.
+     *
+     * @param id The id of the basic food to update
+     * @param element A reference basic food containing the changes to make
+     * @return The newly updated basic food
+     */
+    @PostMapping("/food/basic/{id}")
+    public BasicFood update(@PathVariable("id") final String id,
+                            @RequestBody final BasicFood element) {
+        if (get(id) instanceof BasicFood) {
+            return (BasicFood) super.update(id, element);
+        }
+        else {
+            throw new ResourceWrongTypeException(id+" is not a Recipe.");
+        }
+    }
+
+    /* Recipe APIs */
+
+    /**
+     * Get a list of the most common recipes.
+     *
+     * Provides a default behavior for empty queries.
+     * 
+     * @return A list of the most common recipes
+     */
+    @GetMapping("/food/recipe/query")
+    public List<Recipe> queryRecipes() {
+        return db.find(Recipe.class).asList();
+    }
+
+    /**
+     * Get a list of recipes whose name contains a string.
+     *
+     * The input is case insensitive.
+     *
+     * @param query A name-fragment to search
+     * @return A list of recipes with the query in it's name
+     */
+    @GetMapping("/food/recipe/query/{query}")
+    public List<Recipe> queryRecipes(@PathVariable("query") final String query) {
+        return db.find(Recipe.class).field("name").containsIgnoreCase(query).asList();
+    }
+
+    /**
+     * Insert a recipe into the database.
+     *
+     * Any provided id is ignored as a new one will be assigned automatically.
+     *
+     * @param newElement A recipe to insert into the database
+     * @return The original recipe inserted with a new id
+     */
+    @PutMapping("/food/recipe")
+    public Recipe insert(@RequestBody Recipe newElement) {
+        return (Recipe) super.insert(newElement);
+    }
+
+    /**
+     * Save a recipe's not-null fields to the database with a given id.
+     * 
+     * Any fields with a null/missing value will be ignored. 
+     *
+     * An element with the provided id must exist to be updated.
+     *
+     * This operation increments the version counter.
+     *
+     * @param id The id of the recipe to update
+     * @param element A reference recipe containing the changes to make
+     * @return The newly updated recipe
+     */
+    @PostMapping("/food/recipe/{id}")
+    public Recipe update(@PathVariable("id") final String id,
+                         @RequestBody final Recipe element) {
+        if (get(id) instanceof Recipe) {
+            return (Recipe) super.update(id, element);
+        }
+        else {
+            throw new ResourceWrongTypeException(id+" is not a Recipe.");
+        }
+    }
+}

+ 0 - 24
src/name/tflucke/ieat2/controllers/TestController.java

@@ -1,24 +0,0 @@
-package name.tflucke.ieat2.controllers;
-
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.beans.factory.annotation.Autowired;
-
-import org.mongodb.morphia.Datastore;
-
-import name.tflucke.ieat2.models.BasicFood;
-
-@RestController("/echo/")
-public class TestController {
-    @Autowired
-    private Datastore db;
-    
-    @GetMapping("/echo/{name}")
-    public String echo(@PathVariable("name") String name) {
-        BasicFood food = new BasicFood();
-        food.name = "Test food";
-        db.save(food);
-        return "Test: " + name;
-    }
-}

+ 19 - 4
src/name/tflucke/ieat2/controllers/ViewController.java

@@ -5,19 +5,34 @@ import org.springframework.ui.ModelMap;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RequestMethod;
 import org.springframework.web.bind.annotation.PathVariable;
- 
+
+/**
+ * Provides access to jsp files through APIs.
+ * 
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 @Controller
 @RequestMapping("/")
 public class ViewController {
- 
+
+    /**
+     * Provides the default home page.
+     * 
+     * @return The name of the default JSP page
+     */
     @RequestMapping
     public String defaultPage(ModelMap model) {
         return "index";
     }
- 
+
+    /**
+     * Returns the name of an arbitrary JSP file.
+     *
+     * @return The name of the requested file.
+     */
     @RequestMapping(value = "/{file:.+}")
     public String arbitraryFile(@PathVariable("file") String filename, ModelMap model) {
         return filename;
     }
- 
 }

+ 6 - 0
src/name/tflucke/ieat2/errors/ResourceNotFoundException.java

@@ -3,6 +3,12 @@ package name.tflucke.ieat2.errors;
 import org.springframework.web.bind.annotation.ResponseStatus;
 import org.springframework.http.HttpStatus;
 
+/**
+ * Represents a request for a resource which does not exist.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
 @ResponseStatus(value = HttpStatus.NOT_FOUND)
 public class ResourceNotFoundException extends RuntimeException {
 }

+ 17 - 0
src/name/tflucke/ieat2/errors/ResourceWrongTypeException.java

@@ -0,0 +1,17 @@
+package name.tflucke.ieat2.errors;
+
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.http.HttpStatus;
+
+/**
+ * Represents a request by id for a resource of the wrong type.
+ *
+ * @author Thomas Flucke
+ * @since 2.0.0
+ */
+@ResponseStatus(value = HttpStatus.BAD_REQUEST)
+public class ResourceWrongTypeException extends RuntimeException {
+    public ResourceWrongTypeException(final String message) {
+        super(message);
+    }
+}