Introduction

The aims of the assignment are:

  • To implement web services for making available MySQL resources regarding your fridge groceries and items. The groceries and items are those that you have worked with in Assignment 1. (Using Java Reflection and Servlet API)
  • To apply the relevant validation strategies to the grocery, using a similar Validation Framework to the one we built in the Labs.

Files Provided:

The following are provided in the fridge directory (zip file)

The servlet descriptor file web.xml (directory fridge/WEB-INF): You will need to edit this file.

The jar files for JSON conversion and JDBC (MySQL) driver (directory fridge/WEB-INF/lib)

The following files are in the directory fridge/WEB-INF/classes:

  • The Java files for the Validation framework (complete files). You should not need to change these, but you are encouraged to examine them carefully:
    • Min.java, MinValidator.java
    • Max.java, MaxValidator.java
    • NotNull.java, NotNullValidator.java
    • CharCount.java, CharCountValidator.java
    • alidator.java, ValidationException.java
  • The Data Source Controller, FridgeDSC.java (Complete. But you will need to edit the testing section in the main to add your username and password) NOTE: do not use your solution from Assignment 1
  • The Models
    • Grocery.java (You will need to add annotations to this file)
    • Item.java (complete)
  • The Controllers (You will need to complete this file)
    • GroceryController.java
    • ItemController.java
  • The Router Servlet (You will need to complete this file))
    • FridgeRouterServlet.java
  • A few custom Exception sub-classes to be used throughout this Assignment 2 (complete)
    • MissingArgumentException.java
    • ResourceNotFoundException.java
    • UpdateNotAllowedException.java
  • The SQL Script to create and populate your database (Either localhost or latcs7.cs.latrobe.du.au MySQL): You may need to edit this file.
    • CreateDatabaseScript.sql
    • CreateDatabaseScript-latcs7.sql

Task 1: Validation

Study the Validation Framework provided (complete code provided) as well as the lecture and lab materials that covered Java Reflection, Annotations and the Validation Framework.

  • TODO: Clearly annotate the relevant fields of the Grocery.java model. You may want to test if your validation on the model is working (see how we did that in the labs) - a static main has been provided in Grocery.java for you to add your testing code.

Task 2: The controllers

There are two controller classes. GroceryController and ItemController. These are the bridge between the FridgeDSC class and the FridgeRouterServlet class.

Study the complete FridgeDSC.java file provided, paying attention to its constructor. Some changes have been made, which differs from Assignment 1.

NOTE: the constructor requires database host, username and password as arguments. In the tomcat service these are obtain from the web.xml file.

For each controller (GroceryController.java and ItemController.java) complete each of the "TODO" method stubs provided. Each method stub needs to make a call to a relevant FridgeDSC.java method. Identify which one and code it in the controllers and return the right data.

You may want to test if the controllers are responding properly when calling each FridgeDSC method - a static main method has been provided in each class for you to add your testing code.

Task 3 The servlet

The bulk of the work for this assignment is in the servlet (FridgeRouterServlet class)

The purpose of this servlet class is to use tomcat's HttpServletRequest method to;

  • get the information about the request
  • translate data to and from JSON using the GSON library
  • route requests to the appropriate controller
  • add the returned information to the HttpServletResponse object
  • deal with Exceptions correctly

General steps

Carefully read the class java file and complete the TODO stubs. The provided code and your completed TODOs does the following;

Using the Servlet API the class retrieves URL and http method information, and your database host, username and password from the servlet descriptor file web.xml. (add your MySQL username and password where needed in file web.xml)

Uses normal Java string manipulation to find the requested controller names from the url path information. i.e GroceryController or ItemController.

Uses the Reflection API to find and create an instance of the required controller class, and calls the appropriate method, translating information from JSON if needed, and populating the HttpServletResponse object(response) with the results. Note:

  • HTTP GET (with no parameters) maps to controller method get()
  • HTTP GET (with a parameter) maps to either controller method get(id) or get(e)
  • HTTP POST maps to controller method add(g)
  • HTTP PUT (with a parameter) maps to controller method update(id)
  • HTTP DELETE (with a parameter) maps to controller method delete(id)

Makes sure any potential errors are caught, identified and sent back to the browser (using GSON to convert messages to JSON) and using appropriate HTTP Status Codes and adequate (descriptive) error messages.

You will use Postman to test your API (https://www.getpostman.com/) - More on Postman will be covered in your lab sessions.

TODO list summary and location

TODO # Location (file name) Description
1 Grocery.java annotation
2 Grocery.java annotation
3 Grocery.java annotation
4 Grocery.java annotation
5 Grocery.java annotation
6 GroceryController.java complete get()
7 GroceryController.java complete get(id)
8 GroceryController.java complete getAllExpiredItems()
9 GroceryController.java complete add() validation
10 GroceryController.java complete add()
11 GroceryController.java complete update()
12 GroceryController.java complete delete()
13 ItemController.java complete get()
14 FridgeRouterServlet.java Set the response CONTENT_TYPE and CHARACTER_ENCODING
15 FridgeRouterServlet.java Get the path info from the HttpServletRequest argument.
16 FridgeRouterServlet.java Get the http method from the HttpServletRequest argument.
17 FridgeRouterServlet.java Get the data model name from pathInfoArray
18 FridgeRouterServlet.java Find the modelClass using String modelName
20 FridgeRouterServlet.java Find the modelId in pathInfoArray (don't forget to parse to int)
21 FridgeRouterServlet.java Identify the method get() with no id
22 FridgeRouterServlet.java Invoke the method on the controller instance
23 FridgeRouterServlet.java Find the modelId in pathInfoarray (don't forget to parse to int)
24 FridgeRouterServlet.java Identify the method update() with id
25 FridgeRouterServlet.java Invoke the method on the controller instance, passing modelId
26 FridgeRouterServlet.java Find the modelId in pathInfoarray (don't forget to parse to int)
27 FridgeRouterServlet.java Identify the method delete() with id
28 FridgeRouterServlet.java Invoke the method on the controller instance, passing modelId
29 FridgeRouterServlet.java Identify instances of the UpdateNotAllowedException exception
30 FridgeRouterServlet.java Identify instances of the ValidationException exception

Documentation

Provide API style reference USER documentation for the use of your API. This could be via javadoc, or a word document. It should be at a level that a Web programmer wanting to use your API could use it to add data to their web application. This means for the eight end points we support you need to provide;

  • The required URL. Including the http Method.
  • Define the data that must be sent (if any), and whether that data should be sent in the URL or as POST data.
  • Define the data that will be returned. i.e. describe the JSON array
  • The error messages that may be returned, and what they mean.

You should be able to do this in half a page. One page at the most. Be terse but be correct.

Starter Code

CharCount.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Define annotation interface CharCount.
* If both params gets defaults, Validator should through error.
* Only applies to String; If not String, Validator should through error.
*/

@Retention(RetentionPolicy.RUNTIME)
// Process this annotation at runtime

@Target(ElementType.FIELD)
// This is an annotation on a field (attribute)

public @interface CharCount {
public int min() default 0;
public int max() default 0;
}

MissingArgumentException.java


public class MissingArgumentException extends Exception {

private static final long serialVersionUID = 1L;

public MissingArgumentException() {
super();
}

public MissingArgumentException(String message) {
super(message);
}
}

Validator.java

import java.lang.reflect.*;
import java.lang.annotation.Annotation;
import java.util.*;

public abstract class Validator {
public abstract void applyRule(
Annotation annotation,
Object fieldValue,
Class< ?> fieldType
) throws Exception;

public static final String APPLY_RULE_METHOD_NAME = "applyRule";

private static Map< String, Object> validatorMap = new HashMap< String, Object>();

// NOTE: this method is static; it could have been placed in any other Class.
// we've put it in this abstract class because it is related to the overall
// validation use case that includes this "Validator" abstract class.
public static void validate(Object model) throws Exception {
if (model == null)
throw new ValidationException(
String.join(" ", "Model [", model.getClass().getName(), "] cannot be null")
);

List< Field> fields = getInheritedDeclaredFields(model);

for(Field field: fields) {
Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation: annotations) {
String annotationName = annotation.annotationType().getName();
String abstractValidatorClassName = Validator.class.getSimpleName();
/**
* some convention over configuration
* convention:
* if your annotation is named "Min" and we know this abstract class is
* named "Validator", then your validator rule will have to be implemented
* in a file named "MinValidator" ("Min" + "Validator" joined/concatenated)
*
* Why are we doing this?
* if we have an annotation "Min", based on that annotation, this Framework
* can then find the matching validator rule in a file named "MinValidator".
* This static "validate" method uses Java Reflection to:
* - the current forEach iteration's annotation (find the name in String)
* - this abstract class name (in String)
* - concat them together, annotation name first
* - looks for a class matching the concatenated String
* > this class should extend abstract class "Validator"
* - create an instance of that class
* > (through it's constructor, retieved using Java Reflection)
* - calls its "applyRule" method (with appropriate arguments)
* > we know the instance of the class has the "applyRule" method
* > because this class extends abstract class "Validator"
* > and this abstract class "Validator" has an abstract "applyRule" method
*/
String validatorClassName = String.join("", annotationName, abstractValidatorClassName);

Class< ?> validatorClass;
try {
validatorClass = Class.forName(validatorClassName);
} catch (Exception exp) {
throw new ValidationException(
"Cannot find Validator subclass " + validatorClassName +
".class to validate targetted field "" + String.join(".", field.getDeclaringClass().getName(), field.getName()) +
"" annotated with @" + annotationName + ". Was such a subclass defined?"
);
}

if (validatorMap.get(validatorClassName) == null)
validatorMap.put(validatorClassName, validatorClass.getConstructor().newInstance(new Object[0]));
Object validatorInstance = validatorMap.get(validatorClassName);
field.setAccessible(true); // to access private field

Method method = validatorClass.getMethod(
APPLY_RULE_METHOD_NAME,
Annotation.class,
Object.class,
Class.class
);

try {
method.invoke(validatorInstance, annotation, field.get(model), field.getType());
} catch (InvocationTargetException ite) {
if (ite.getCause() instanceof ValidationException)
throw new ValidationException(
"Field [ " +
String.join(".", field.getDeclaringClass().getName(), field.getName()) +
" ]" + ite.getCause().getMessage()
);
}
}
}
}

/**
* finding fields from model Class, as well as fields from its superclass hierarchy
*/
public static List< Field> getInheritedDeclaredFields(Object model) throws Exception {
List< Field> fields = new ArrayList< Field>();

Class< ?> modelClass = model.getClass();
while (modelClass != null) {
fields.addAll(Arrays.asList(modelClass.getDeclaredFields()));
modelClass = modelClass.getSuperclass();
}

return fields;
}

private final static Set< Class< ?>> NUMBER_REFLECTED_PRIMITIVES;
static {
Set< Class< ?>> s = new HashSet< >();
s.add(byte.class);
s.add(short.class);
s.add(int.class);
s.add(long.class);
s.add(float.class);
s.add(double.class);
NUMBER_REFLECTED_PRIMITIVES = s;
}

public static boolean isReflectedAsNumber(Class< ?> type) {
return Number.class.isAssignableFrom(type) || NUMBER_REFLECTED_PRIMITIVES.contains(type);
}
}

CharCountValidator.java

import java.lang.reflect.*;
import java.lang.annotation.Annotation;

public class CharCountValidator extends Validator {

private static CharCount charCount;

public void applyRule(Annotation annotation, Object fieldValue, Class fieldType) throws Exception {
charCount = (CharCount) annotation;

/**
* developer/coder throws for potential annotating errors
*/
if (!fieldType.equals(String.class))
throw new ValidationException(" is not of type String; Annotation @CharCount can only be applied to fields of type String.");

if (charCount.min() < 0 || charCount.max() < 0)
throw new ValidationException(" Annotation @CharCount parameters cannot be less than zero.");

if (charCount.min() == 0 && charCount.max() == 0)
throw new ValidationException(" Annotation @CharCount parameters min() and max() cannot both be zero (their default values). At least one of these parameters has to set to an integer greater than zero.");

String fValue = fieldValue.toString();
int fieldLength = fValue.length();

boolean valid = true;
String message = "";

/**
* user relevant error messages
*/
if (charCount.min() > 0 && charCount.max() > 0) {
if (fieldLength < charCount.min() || fieldLength > charCount.max()) {
valid = false;
if (charCount.min() == charCount.max())
message = " character count (length) has to be exactly " + charCount.min();
else {
message = " character count (length) has to be between " + charCount.min() + " and " + charCount.max();
message += " inclusive. Field character count is " + fieldLength + ", containing "" + fValue + "".";
}
}
}

if (charCount.max() == 0) {
if (fieldLength < charCount.min()) {
valid = false;
message = " character count (length) has to be a minimum of " + charCount.min();
message += ". Field character count is " + fieldLength + ", containing "" + fValue + "".";
}
}

if (charCount.min() == 0) {
if (fieldLength > charCount.max()) {
valid = false;
message = " character count (length) has to be a maximum of " + charCount.max();
message += ". Field character count is " + fieldLength + ", containing "" + fValue + "".";
}
}

if (!valid) throw new ValidationException(message);
}
}

CreateDatabaseScript.sql

-- MySQL dump 10.13 Distrib 5.7.16, for Win64 (x86_64)
--- The first 3 lines will only work if running on local mysql

DROP DATABASE IF EXISTS `fridgedb`;

CREATE DATABASE `fridgedb`;

USE `fridgedb`;

-- Run from this line when running on latcs7 mysql

DROP TABLE IF EXISTS `grocery`;
DROP TABLE IF EXISTS `item`;
--
-- Table structure for table `item`
--

CREATE TABLE `item` (
`name` varchar(20) NOT NULL,
`expires` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`name`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Dumping data for table `item`
--

LOCK TABLES `item` WRITE;

INSERT INTO `item` VALUES ('Beef',1),('Broccoli',1),('Cabbage',1),('Fish',1),('Ice Cream',0),('Gorgonzola',1),('Milk',0),('Oranges',0),('Paddle Pop',0),('Pecorino',1),('Tangerines',0),('Tofu',0);

UNLOCK TABLES;

--
-- Table structure for table `grocery`
--

CREATE TABLE `grocery` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`itemName` varchar(20) NOT NULL,
`date` varchar(10) DEFAULT NULL,
`quantity` int(11) DEFAULT NULL,
`section` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `itemName_idx` (`itemName`),
CONSTRAINT `fk_grocery_item` FOREIGN KEY (`itemName`) REFERENCES `item` (`name`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8;

--
-- Dumping data for table `grocery`
--

LOCK TABLES `grocery` WRITE;

INSERT INTO `grocery` VALUES (3,'Ice Cream','26/09/2019',1,'FREEZER'),(6,'Paddle Pop','26/09/2019',1,'FREEZER'),(19,'Beef','25/09/2019',1,'MEAT'), (20,'Pecorino','26/09/2019',2,'CRISPER'),(32,'Beef','26/09/2019',3,'MEAT');

UNLOCK TABLES;

-- Dump completed

FridgeDSC.java


import java.sql.*;
import java.util.*;
import java.io.File;
import java.io.PrintWriter;
import java.util.Scanner;
import java.time.LocalDate;
import java.time.Duration;
import java.time.format.DateTimeFormatter;

public class FridgeDSC {

// the date format we will be using across the application
public static final String DATE_FORMAT = "dd/MM/yyyy";

/*
FREEZER, // freezing cold
MEAT, // MEAT cold
COOLING, // general fridge area
CRISPER // veg and fruits section

note: Enums are implicitly public static final
*/
public enum SECTION {
FREEZER,
MEAT,
COOLING,
CRISPER
};

private Connection connection;
private Statement statement;
private PreparedStatement preparedStatement;

private String dbUserName;
private String dbPassword;
private String dbURL;

// TODO:
// in order to allow the DSC to be compatible with either
// - latcs7 MySQL server (Bundoora and Bendigo Campuses only)
// - your own MySQL server (or any other)
// The dbHost argument will include both the host and the database
// example:
// localhost:3306/fridgedb
// latcs7.cs.latrobe.edu.au:3306/12345678
//
// In tomcat this will come from web.xml
// In the testing methods in the main below they are in the constructor call CHANGE THIS.
public FridgeDSC(String dbHost, String dbUserName, String dbPassword) {
this.dbUserName = dbUserName;
this.dbPassword = dbPassword;
this.dbURL = "jdbc:mysql://" + dbHost;
}

public void connect() throws SQLException {
try {
Class.forName("com.mysql.jdbc.Driver");
connection = DriverManager.getConnection(dbURL, dbUserName, dbPassword);
statement = connection.createStatement();
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
}

public void disconnect() throws SQLException {
if (preparedStatement != null) {
preparedStatement.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}

public Item searchItem(String name) throws Exception {
String queryString = "SELECT * FROM item WHERE name = ?";
preparedStatement = connection.prepareStatement(queryString);
preparedStatement.setString(1, name);
ResultSet rs = preparedStatement.executeQuery();

Item item = null;

if (rs.next()) { // i.e. the item exists
boolean expires = rs.getBoolean(2);
item = new Item(name, expires);
}

return item;
}

public Grocery searchGrocery(int id) throws Exception {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_FORMAT);
String queryString = "SELECT * FROM grocery WHERE id = ?";
preparedStatement = connection.prepareStatement(queryString);
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();

Grocery grocery = null;

if (rs.next()) { // i.e. the grocery exists
String itemName = rs.getString(2);
Item item = searchItem(itemName);
if (item == null) {
System.err.println("[WARNING] Item: '" + itemName + "'' does not exist!");
}
LocalDate date = LocalDate.parse(rs.getString(3), dtf);
int quantity = rs.getInt(4);
FridgeDSC.SECTION section = SECTION.valueOf(rs.getString(5));

grocery = new Grocery(id, item, date, quantity, section);

}

return grocery;
}

public List getAllItems() throws Exception {
String queryString = "SELECT * FROM item";
ResultSet rs = statement.executeQuery(queryString);

List items = new ArrayList();

while (rs.next()) { // i.e. items exists
String name = rs.getString(1);
boolean expires = rs.getBoolean(2);
items.add(new Item(name, expires));
}

return items;
}

public List getAllExpiredItems() throws Exception {
String queryString = "SELECT * FROM item where expires is true";
ResultSet rs = statement.executeQuery(queryString);

List items = new ArrayList();

while (rs.next()) { // i.e. items exists
String name = rs.getString(1);
boolean expires = rs.getBoolean(2);
items.add(new Item(name, expires));
}

return items;
}

public List getAllGroceries() throws Exception {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_FORMAT);
String queryString = "SELECT * FROM grocery";
ResultSet rs = statement.executeQuery(queryString);

List groceries = new ArrayList();

while (rs.next()) { // i.e. groceries exists
int id = rs.getInt(1);
String itemName = rs.getString(2);
Item item = searchItem(itemName);
if (item == null) {
System.err.println("[WARNING] Item: '" + itemName + "'' does not exist!");
continue;
}
LocalDate date = LocalDate.parse(rs.getString(3), dtf);
int quantity = rs.getInt(4);
SECTION section = SECTION.valueOf(rs.getString(5));

groceries.add(new Grocery(id, item, date, quantity, section));
}

return groceries;
}

public List getAllGroceryExpiredItems() throws Exception {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_FORMAT);
String queryString = "select * from grocery,item where grocery.itemName=item.name AND item.expires=true";
ResultSet rs = statement.executeQuery(queryString);

List groceries = new ArrayList();

while (rs.next()) { // i.e. groceries exists
int id = rs.getInt(1);
String itemName = rs.getString(2);
Item item = searchItem(itemName);
if (item == null) {
System.err.println("[WARNING] Item: '" + itemName + "'' does not exist!");
continue;
}
LocalDate date = LocalDate.parse(rs.getString(3), dtf);
int quantity = rs.getInt(4);
SECTION section = SECTION.valueOf(rs.getString(5));

groceries.add(new Grocery(id, item, date, quantity, section));
}

return groceries;
}

public int addGrocery(String name, int quantity, SECTION section) throws Exception {
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DATE_FORMAT);
LocalDate date = LocalDate.now();
String dateStr = date.format(dtf);

// NOTE: should we check if itemName (argument name) exists in item table?
// --> adding a groceries with a non-existing item name should through an exception
String command = "INSERT INTO grocery VALUES(?, ?, ?, ?, ?)";
preparedStatement = connection.prepareStatement(command);

preparedStatement.setInt(1, 0);
preparedStatement.setString(2, name);
preparedStatement.setString(3, dateStr);
preparedStatement.setInt(4, quantity);
preparedStatement.setString(5, section.toString());

preparedStatement.executeUpdate();

ResultSet rs = statement.executeQuery("SELECT LAST_INSERT_ID()");
rs.next();
int newId = rs.getInt(1);

return newId;
}

public Grocery useGrocery(int id) throws Exception {
Grocery g = searchGrocery(id);

if (g == null) {
return null;
}

if (g.getQuantity() <= 1) {
throw new UpdateNotAllowedException("There is only one: " + g.getItemName() + " (bought on " + g.getDateStr() + ") - use DELETE instead.");
}

String queryString
= "UPDATE grocery "
+ "SET quantity = quantity - 1 "
+ "WHERE quantity > 1 "
+ "AND id = " + id + ";";

if (statement.executeUpdate(queryString) > 0) {
return searchGrocery(id);
} else {
return null;
}
}

public int removeGrocery(int id) throws Exception {
String queryString = "SELECT COUNT(*) FROM grocery WHERE id = ?";
preparedStatement = connection.prepareStatement(queryString);
preparedStatement.setInt(1, id);
ResultSet rs = preparedStatement.executeQuery();

// are there any results
boolean pre = rs.next();
if (!pre) { // no, throw error
throw new RuntimeException("The grocery does not exist!");
}

// there are results, proceed with delete
return statement.executeUpdate("DELETE FROM grocery WHERE id = " + id);
}

// STATIC HELPERS -------------------------------------------------------
public static long calcDaysAgo(LocalDate date) {
return Math.abs(Duration.between(LocalDate.now().atStartOfDay(), date.atStartOfDay()).toDays());
}

public static String calcDaysAgoStr(LocalDate date) {
String formattedDaysAgo;
long diff = calcDaysAgo(date);

if (diff == 0) {
formattedDaysAgo = "today";
} else if (diff == 1) {
formattedDaysAgo = "yesterday";
} else {
formattedDaysAgo = diff + " days ago";
}

return formattedDaysAgo;
}

public static void main(String[] args) throws Exception {

// FridgeDSC dsc = new FridgeDSC("latcs7.cs.latrobe.edu.au:3306/12345678", "12345678", "j2Pth2f5GntPTFn9mmNk");
FridgeDSC dsc = new FridgeDSC("localhost:3306/fridgedb", "root", "");

try {
dsc.connect();
System.out.println(dsc.getAllGroceries());
System.out.println(dsc.removeGrocery(455));
System.out.println(dsc.useGrocery(455));
System.out.println(dsc.useGrocery(19));
} catch (Exception exp) {
exp.printStackTrace();
}
}
}

Item.java


public class Item {

// name is the unique id
private String name;
private boolean expires; // defaults to false

// constructor
public Item(String name, boolean expires) {
this.name = name;
this.expires = expires;
}

// constructor
public Item(String name) {
this(name, false);
}

public String getName() {
return this.name;
}

public boolean canExpire() {
return this.expires;
}

public String toString() {
return "[ name: " + this.name
+ ", expires: " + this.expires
+ " ]";
}

// To perform some quick tests
public static void main(String[] args) {
Item i1 = new Item("Milk", false);
System.out.println(i1);

Item i2 = new Item("Fish", true);
System.out.println(i2);
}
}

Max.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/* Define annotation interface Min
*/

@Retention(RetentionPolicy.RUNTIME)
// Process this annotation at runtime

@Target(ElementType.FIELD)
// This is an annotation on a field (attribute)

public @interface Max {
public double value();
public boolean inclusive() default true;
}

MaxValidator.java

import java.lang.reflect.*;
import java.lang.annotation.Annotation;

public class MaxValidator extends Validator {

private static Max max;

public void applyRule(Annotation annotation, Object fieldValue, Class< ?> fieldType) throws Exception {
/**
* developer/coder throws for potential annotating errors
*/
if (!isReflectedAsNumber(fieldType))
throw new ValidationException(" is not a primitive number type or a subclass of Number. Annotation @Max cannot be applied to non-number types.");

max = (Max) annotation;

double fValue = Double.parseDouble(fieldValue.toString());

boolean valid = max.inclusive() ? fValue < = max.value(): fValue < max.value();

if (!valid) {
String message = " must be greater than ";
String orEquals = max.inclusive() ? "(or equals to) " : "";
message += orEquals + max.value() + ".";
throw new ValidationException(message);
}
}
}

Min.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/* Define annotation interface Min
*/

@Retention(RetentionPolicy.RUNTIME)
// Process this annotation at runtime

@Target(ElementType.FIELD)
// This is an annotation on a field (attribute)

public @interface Min {
public double value();
public boolean inclusive() default true;
}

MinValidator.java

import java.lang.reflect.*;
import java.lang.annotation.Annotation;

public class MinValidator extends Validator {

private static Min min;

public void applyRule(Annotation annotation, Object fieldValue, Class< ?> fieldType) throws Exception {
/**
* developer/coder throws for potential annotating errors
*/
if (!isReflectedAsNumber(fieldType))
throw new ValidationException(" is not a primitive number type or a subclass of Number. Annotation @Min cannot be applied to non-number types.");

min = (Min) annotation;

double fValue = Double.parseDouble(fieldValue.toString());

boolean valid = min.inclusive() ? fValue >= min.value(): fValue > min.value();

if (!valid) {
String message = " must be greater than ";
String orEquals = min.inclusive() ? "(or equals to) " : "";
message += orEquals + min.value() + ".";
throw new ValidationException(message);
}
}
}

NotNull.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks the field as NotNull : a null value is not allowed.
*/
@Retention(RetentionPolicy.RUNTIME)
// Process this annotation at runtime

@Target(ElementType.FIELD)
// This is an annotation on a field (attribute)

public @interface NotNull {
}

NotNullValidator.java

import java.lang.reflect.*;
import java.lang.annotation.Annotation;

public class NotNullValidator extends Validator {

private static NotNull notNull;

public void applyRule(Annotation annotation, Object fieldValue, Class< ?> fieldType) throws Exception {
/**
* developer/coder throws for potential annotating errors
*/
if (isReflectedAsNumber(fieldType))
throw new ValidationException(" is a primitive number type or a subclass of Number. Annotation @NotNull for null check cannot be applied.");

notNull = (NotNull) annotation;

boolean valid = fieldValue != null;
if (!valid) {
String message = " must not be null.";
throw new ValidationException(message);
}
}
}

ResourceNotFoundException.java


public class ResourceNotFoundException extends Exception {

private static final long serialVersionUID = 1L;

public ResourceNotFoundException() {
super();
}

public ResourceNotFoundException(String message) {
super(message);
}
}

UpdateNotAllowedException


public class UpdateNotAllowedException extends Exception {

private static final long serialVersionUID = 1L;

public UpdateNotAllowedException() {
super();
}

public UpdateNotAllowedException(String message) {
super(message);
}
}

ValidationException.java

public class ValidationException extends Exception {
private static final long serialVersionUID = 1L;

public ValidationException() {
super();
}

public ValidationException(String message) {
super(message);
}
}
Academic Honesty!
It is not our intention to break the school's academic policy. Posted solutions are meant to be used as a reference and should not be submitted as is. We are not held liable for any misuse of the solutions. Please see the frequently asked questions page for further questions and inquiries.
Kindly complete the form. Please provide a valid email address and we will get back to you within 24 hours. Payment is through PayPal, Buy me a Coffee or Cryptocurrency. We are a nonprofit organization however we need funds to keep this organization operating and to be able to complete our research and development projects.