diff --git a/WK6/ORM_Example/ORM_Example.iml b/WK6/ORM_Example/ORM_Example.iml new file mode 100644 index 0000000..e8eb938 --- /dev/null +++ b/WK6/ORM_Example/ORM_Example.iml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/WK6/ORM_Example/pom.xml b/WK6/ORM_Example/pom.xml new file mode 100644 index 0000000..633d071 --- /dev/null +++ b/WK6/ORM_Example/pom.xml @@ -0,0 +1,87 @@ + + 4.0.0 + com.origamisoftware.teach.effective + ORM_Example + jar + 1.0-SNAPSHOT + starter-app + http://maven.apache.org + + + UTF-8 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + -Xlint + ${project.build.sourceEncoding} + 1.8 + 1.8 + + + + + + + + + + junit + junit + 4.10 + jar + + + + mysql + mysql-connector-java + 5.1.6 + + + + javax.validation + validation-api + 1.0.0.GA + + + + org.mockito + mockito-all + 1.9.5 + + + + org.apache.httpcomponents + httpcore + 4.4 + + + + org.apache.ibatis + ibatis-sqlmap + 2.3.4.726 + + + + org.hibernate + hibernate-core + 4.2.6.Final + + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + 1.0.1.Final + + + + + diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/Hobby.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/Hobby.java new file mode 100644 index 0000000..17c0f02 --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/Hobby.java @@ -0,0 +1,129 @@ +package com.origamisoftware.teach.advanced.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * Models the Hobby table + */ +@Entity +public class Hobby { + + private int id; + private String name; + private String description; + private int minimumAge; + + /** + * Primary Key - Unique ID for a particular row in the hobby table. + * + * @return an integer value + */ + @Id + @Column(name = "ID", nullable = false, insertable = true, updatable = true) + public int getId() { + return id; + } + + /** + * Set the unique ID for a particular row in the hobby table. + * This method should not be called by client code. The value is managed in internally. + * + * @param id a unique value. + */ + public void setId(int id) { + this.id = id; + } + + /** + * @return the name column as a String + */ + @Basic + @Column(name = "name", nullable = false, insertable = true, updatable = true, length = 256) + public String getName() { + return name; + } + + /** + * Specify the person's name + * + * @param name a String value + */ + public void setName(String name) { + this.name = name; + } + + /** + * + * @return the value of the description column as a String + */ + @Basic + @Column(name = "description", nullable = false, insertable = true, updatable = true, length = 2056) + public String getDescription() { + return description; + } + + /** + * Specify the Hobby's description + * + * @param description a String value + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * The recommended age for the hobby, an int value + * @return the value of the minimum_age column as an int value + */ + @Basic + @Column(name = "minimum_age", nullable = false, insertable = true, updatable = true) + public int getMinimumAge() { + return minimumAge; + } + + /** + * Specify the recommended age for the hobby + * @param minimumAge an int value + */ + public void setMinimumAge(int minimumAge) { + this.minimumAge = minimumAge; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Hobby hobby = (Hobby) o; + + if (id != hobby.id) return false; + if (minimumAge != hobby.minimumAge) return false; + if (description != null ? !description.equals(hobby.description) : hobby.description != null) + return false; + if (name != null ? !name.equals(hobby.name) : hobby.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (name != null ? name.hashCode() : 0); + result = 31 * result + (description != null ? description.hashCode() : 0); + result = 31 * result + minimumAge; + return result; + } + + @Override + public String toString() { + return "Hobby{" + + "id=" + id + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + ", minimumAge=" + minimumAge + + '}'; + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/Person.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/Person.java new file mode 100644 index 0000000..69bd94e --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/Person.java @@ -0,0 +1,133 @@ +package com.origamisoftware.teach.advanced.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Models the Person table + */ +@Entity +@Table(name="person") +public class Person { + + private int id; + private String firstName; + private String lastName; + private Timestamp birthDate; + + /** + * Primary Key - Unique ID for a particular row in the person table. + * + * @return an integer value + */ + @Id + @Column(name = "ID", nullable = false, insertable = true, updatable = true) + public int getId() { + return id; + } + + /** + * Set the unique ID for a particular row in the person table. + * This method should not be called by client code. The value is managed in internally. + * + * @param id a unique value. + */ + public void setId(int id) { + this.id = id; + } + + /** + * + * @return the person's first name + */ + @Basic + @Column(name = "first_name", nullable = false, insertable = true, updatable = true, length = 256) + public String getFirstName() { + return firstName; + } + + /** + * Specify the person's first name + * @param firstName a String value + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * + * @return the person's last name + */ + @Basic + @Column(name = "last_name", nullable = false, insertable = true, updatable = true, length = 256) + public String getLastName() { + return lastName; + } + + /** + * Specify the person's last name + * @param lastName a String value + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + + /** + * + * @return the person's birthdate. + */ + @Basic + @Column(name = "birth_date", nullable = false, insertable = true, updatable = true) + public Timestamp getBirthDate() { + return birthDate; + } + + /** + * Specify the person's date of birth. + * @param birthDate the time the person was born. + */ + public void setBirthDate(Timestamp birthDate) { + this.birthDate = birthDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Person person = (Person) o; + + if (id != person.id) return false; + if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null) + return false; + if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) + return false; + if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (firstName != null ? firstName.hashCode() : 0); + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + result = 31 * result + (birthDate != null ? birthDate.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Person{" + + "id=" + id + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", birthDate=" + birthDate + + '}'; + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/PersonHobby.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/PersonHobby.java new file mode 100644 index 0000000..cc4a84b --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/model/PersonHobby.java @@ -0,0 +1,126 @@ +package com.origamisoftware.teach.advanced.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * Models a table the combines person with their hobbies. + */ +@Entity +@Table(name = "person_hobby", catalog = "activities") +public class PersonHobby { + private int id; + private Person person; + private Hobby hobby; + + /** + * Create a PersonHobby that needs to be initialized + */ + public PersonHobby() { + // this empty constructor is required by hibernate framework + + } + + /** + * Create a valid PersonHobby + * + * @param person the person to assign the hobby to + * @param hobby the hobby to associate the person with + */ + public PersonHobby(Person person, Hobby hobby) { + setHobby(hobby); + setPerson(person); + } + + /** + * Primary Key - Unique ID for a particular row in the person_hobby table. + * + * @return an integer value + */ + @Id + @Column(name = "ID", nullable = false, insertable = true, updatable = true) + public int getId() { + return id; + } + + /** + * Set the unique ID for a particular row in the person_hobby table. + * This method should not be called by client code. The value is managed in internally. + * + * @param id a unique value. + */ + public void setId(int id) { + this.id = id; + } + + /** + * + * @return get the Person associated with this hobby + */ + @ManyToOne + @JoinColumn(name = "person_id", referencedColumnName = "ID", nullable = false) + public Person getPerson() { + return person; + } + + /** + * Specify the Person associated with the hobby. + * + * @param person a person instance + */ + public void setPerson(Person person) { + this.person = person; + } + + /** + * + * @return get the Hobby associated with this Person + */ + @ManyToOne + @JoinColumn(name = "hobby_id", referencedColumnName = "ID", nullable = false) + public Hobby getHobby() { + return hobby; + } + + /** + * Specify the Hobby associated with the Person. + * + * @param hobby a person instance + */ + public void setHobby(Hobby hobby) { + this.hobby = hobby; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PersonHobby that = (PersonHobby) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + person.hashCode(); + result = 31 * result + hobby.hashCode(); + return result; + } + + @Override + public String toString() { + return "PersonHobby{" + + "id=" + id + + ", person=" + person + + ", hobby=" + hobby + + '}'; + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesService.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesService.java new file mode 100644 index 0000000..f25f614 --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesService.java @@ -0,0 +1,50 @@ +package com.origamisoftware.teach.advanced.service; + +import com.origamisoftware.teach.advanced.model.Hobby; +import com.origamisoftware.teach.advanced.model.Person; + +import java.util.List; + +/** + * + */ +public interface ActivitiesService { + + /** + * Get a list of all people + * + * @return a list of Person instances + * @throws ActivitiesServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + List getPerson() throws ActivitiesServiceException; + + /** + * Add a new person or update an existing Person's data + * + * @param person a person object to either update or create + * @throws ActivitiesServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + void addOrUpdatePerson(Person person) throws ActivitiesServiceException; + + /** + * Get a list of all a person's hobbies. + * + * @return a list of hobby instances + * @throws ActivitiesServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + List getHobbies(Person person) throws ActivitiesServiceException; + + /** + * Assign a hobby to a person. + * + * @param hobby The hobby to assign + * @param person The person to assign the hobby too. + * @throws ActivitiesServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + public void addHobbyToPerson(Hobby hobby, Person person) throws ActivitiesServiceException; + +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesServiceException.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesServiceException.java new file mode 100644 index 0000000..216ec83 --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesServiceException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.service; + +/** + * Used to signal an issue with ActivitiesService + */ +public class ActivitiesServiceException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public ActivitiesServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesServiceFactory.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesServiceFactory.java new file mode 100644 index 0000000..d9ac28e --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/ActivitiesServiceFactory.java @@ -0,0 +1,21 @@ +package com.origamisoftware.teach.advanced.service; + +/** + * A factory that returns a ActivitiesService instance. + */ +public class ActivitiesServiceFactory { + + /** + * Prevent instantiations + */ + private ActivitiesServiceFactory() {} + + /** + * + * @return get a StockService instance + */ + public static ActivitiesService getInstance() { + return new DatabaseActivitiesService(); + } + +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/DatabaseActivitiesService.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/DatabaseActivitiesService.java new file mode 100644 index 0000000..7ef016f --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/service/DatabaseActivitiesService.java @@ -0,0 +1,152 @@ +package com.origamisoftware.teach.advanced.service; + +import com.origamisoftware.teach.advanced.model.Hobby; +import com.origamisoftware.teach.advanced.model.Person; +import com.origamisoftware.teach.advanced.model.PersonHobby; +import com.origamisoftware.teach.advanced.util.DatabaseUtils; +import org.hibernate.Criteria; +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.criterion.Restrictions; + +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class DatabaseActivitiesService implements ActivitiesService { + + + + + + /** + * Get a list of all people + * + * @return a list of Person instances + */ + @Override + @SuppressWarnings("unchecked") + public List getPerson() throws ActivitiesServiceException{ + Session session = DatabaseUtils.getSessionFactory().openSession(); + List returnValue = null; + Transaction transaction = null; + try { + transaction = session.beginTransaction(); + Criteria criteria = session.createCriteria(Person.class); + + /** + * NOTE criteria.list(); generates unchecked warning so SuppressWarnings + * is used - HOWEVER, this about the only @SuppressWarnings I think it is OK + * to suppress them - in almost all other cases they should be fixed not suppressed + */ + returnValue = criteria.list(); + + } catch (HibernateException e) { + if (transaction != null && transaction.isActive()) { + transaction.rollback(); // close transaction + } + throw new ActivitiesServiceException("Could not get Person data. " + e.getMessage(), e); + } finally { + if (transaction != null && transaction.isActive()) { + transaction.commit(); + } + } + + return returnValue; + + } + + /** + * Add a new person or update an existing Person's data + * + * @param person a person object to either update or create + */ + @Override + public void addOrUpdatePerson(Person person) { + Session session = DatabaseUtils.getSessionFactory().openSession(); + Transaction transaction = null; + try { + transaction = session.beginTransaction(); + session.saveOrUpdate(person); + transaction.commit(); + } catch (HibernateException e) { + if (transaction != null && transaction.isActive()) { + transaction.rollback(); // close transaction + } + } finally { + if (transaction != null && transaction.isActive()) { + transaction.commit(); + } + } + } + + /** + * Get a list of all a person's hobbies. + * + * @param person the person + * @return a list of hobby instances + */ + @Override + @SuppressWarnings("unchecked") + public List getHobbies(Person person) { + Session session = DatabaseUtils.getSessionFactory().openSession(); + Transaction transaction = null; + List hobbies = new ArrayList<>(); + try { + transaction = session.beginTransaction(); + Criteria criteria = session.createCriteria(PersonHobby.class); + criteria.add(Restrictions.eq("person", person)); + /** + * NOTE criteria.list(); generates unchecked warning so SuppressWarnings + * is used - HOWEVER, this about the only @SuppressWarnings I think it is OK + * to suppress them - in almost all other cases they should be fixed not suppressed + */ + List list = criteria.list(); + for (PersonHobby personHobby : list) { + hobbies.add(personHobby.getHobby()); + } + transaction.commit(); + } catch (HibernateException e) { + if (transaction != null && transaction.isActive()) { + transaction.rollback(); // close transaction + } + } finally { + if (transaction != null && transaction.isActive()) { + transaction.commit(); + } + } + return hobbies; + + } + + /** + * Assign a hobby to a person. + * + * @param hobby The hobby to assign + * @param person The person to assign the hobby too. + */ + @Override + public void addHobbyToPerson(Hobby hobby, Person person) { + Session session = DatabaseUtils.getSessionFactory().openSession(); + Transaction transaction = null; + try { + transaction = session.beginTransaction(); + PersonHobby personHobby = new PersonHobby(); + personHobby.setHobby(hobby); + personHobby.setPerson(person); + session.saveOrUpdate(personHobby); + transaction.commit(); + } catch (HibernateException e) { + if (transaction != null && transaction.isActive()) { + transaction.rollback(); // close transaction + } + } finally { + if (transaction != null && transaction.isActive()) { + transaction.commit(); + } + } + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseConnectionException.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseConnectionException.java new file mode 100644 index 0000000..ea8d91e --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseConnectionException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.util; + +/** + * This class is used to signal a problem connecting to a database. + */ +public class DatabaseConnectionException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public DatabaseConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseInitializationException.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseInitializationException.java new file mode 100644 index 0000000..bc09f7d --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseInitializationException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.util; + +/** + * This class is used to signal a problem initializing to a database. + */ +public class DatabaseInitializationException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public DatabaseInitializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseUtils.java b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseUtils.java new file mode 100644 index 0000000..03f34b9 --- /dev/null +++ b/WK6/ORM_Example/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseUtils.java @@ -0,0 +1,112 @@ +package com.origamisoftware.teach.advanced.util; + +import com.ibatis.common.jdbc.ScriptRunner; +import com.origamisoftware.teach.advanced.service.DatabaseActivitiesService; +import org.hibernate.SessionFactory; +import org.hibernate.cfg.Configuration; +import org.hibernate.service.ServiceRegistry; +import org.hibernate.service.ServiceRegistryBuilder; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +/** + * A class that contains database related utility methods. + */ +public class DatabaseUtils { + + public static final String initializationFile = "./src/main/sql/db_initialization.sql"; + + private static SessionFactory sessionFactory; + private static Configuration configuration; + + /* + * @return SessionFactory for use with database transactions + */ + public static SessionFactory getSessionFactory() { + + // singleton pattern + synchronized (DatabaseActivitiesService.class) { + if (sessionFactory == null) { + + Configuration configuration = getConfiguration(); + + ServiceRegistry serviceRegistry = new ServiceRegistryBuilder() + .applySettings(configuration.getProperties()) + .buildServiceRegistry(); + + sessionFactory = configuration.buildSessionFactory(serviceRegistry); + + } + } + return sessionFactory; + } + + /** + * Create a new or return an existing database configuration object. + * + * @return a Hibernate Configuration instance. + */ + private static Configuration getConfiguration() { + + synchronized (DatabaseUtils.class) { + if (configuration == null) { + configuration = new Configuration(); + configuration.configure("hibernate.cfg.xml"); + } + } + return configuration; + } + + public static Connection getConnection() throws DatabaseConnectionException { + Connection connection = null; + Configuration configuration = getConfiguration(); + try { + + Class.forName("com.mysql.jdbc.Driver"); + String databaseUrl = configuration.getProperty("connection.url"); + String username = configuration.getProperty("hibernate.connection.username"); + String password = configuration.getProperty("hibernate.connection.password"); + connection = DriverManager.getConnection(databaseUrl, username, password); + + // an example of throwing an exception appropriate to the abstraction. + } catch (ClassNotFoundException | SQLException e) { + throw new DatabaseConnectionException("Could not connect to the database." + e.getMessage(), e); + } + return connection; + } + + /** + * A utility method that runs a db initialize script. + * + * @param initializationScript full path to the script to run to create the schema + * @throws DatabaseInitializationException + */ + public static void initializeDatabase(String initializationScript) throws DatabaseInitializationException { + + Connection connection = null; + try { + connection = getConnection(); + connection.setAutoCommit(false); + ScriptRunner runner = new ScriptRunner(connection, false, false); + InputStream inputStream = new FileInputStream(initializationScript); + + InputStreamReader reader = new InputStreamReader(inputStream); + + runner.runScript(reader); + reader.close(); + connection.commit(); + connection.close(); + + } catch (DatabaseConnectionException | SQLException | IOException e) { + throw new DatabaseInitializationException("Could not initialize db because of:" + + e.getMessage(), e); + } + + } +} diff --git a/WK6/ORM_Example/src/main/resources/hibernate.cfg.xml b/WK6/ORM_Example/src/main/resources/hibernate.cfg.xml new file mode 100644 index 0000000..c327342 --- /dev/null +++ b/WK6/ORM_Example/src/main/resources/hibernate.cfg.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + jdbc:mysql://localhost:3306/activities + com.mysql.jdbc.Driver + org.hibernate.dialect.MySQLDialect + + monty + some_pass + + + + + + + + diff --git a/WK6/ORM_Example/src/main/sql/db_initialization.sql b/WK6/ORM_Example/src/main/sql/db_initialization.sql new file mode 100644 index 0000000..80aac40 --- /dev/null +++ b/WK6/ORM_Example/src/main/sql/db_initialization.sql @@ -0,0 +1,59 @@ + +/* delete tables if they exist already - ensuring a clean db*/ +DROP TABLE IF EXISTS activities.person_hobby CASCADE; +DROP TABLE IF EXISTS activities.hobbies CASCADE; +DROP TABLE IF EXISTS activities.person CASCADE; + + +/* creates a table to store a list of hobbies and their recommended ages */ +CREATE TABLE activities.hobbies +( + ID INT PRIMARY KEY NOT NULL AUTO_INCREMENT, + name VARCHAR(256) NOT NULL, + description VARCHAR(2056) NOT NULL, + minimum_age INT NOT NULL +); + +/** creates a table to store a list of people */ +CREATE TABLE activities.person +( + ID INT PRIMARY KEY NOT NULL AUTO_INCREMENT, + first_name VARCHAR(256) NOT NULL, + last_name VARCHAR(256) NOT NULL, + birth_date DATETIME NOT NULL +); + +/** A list of people and their hobbies */ +CREATE TABLE activities.person_hobby +( + ID INT PRIMARY KEY NOT NULL AUTO_INCREMENT, + person_id INT NOT NULL, + hobbie_id INT NOT NULL, + FOREIGN KEY (person_id) REFERENCES person (ID), + FOREIGN KEY (hobbie_id) REFERENCES hobbies (ID) +); + +/** now add some sample data */ + +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('board games', 'assorted board games like monopoly', 8); +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('chess', 'ancient strategy game', 5); +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('go', 'ancient strategy game', 5); +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('archery', 'shot arrows with a bow', 12); +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('alpine skiing', 'recreation of sliding down snow-covered hills on skis with fixed-heel bindings', 2); +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('snow shoeing', 'traverse snow wearing large funny shoes', 6); +INSERT INTO activities.hobbies (name,description,minimum_age) VALUES ('stamp collecting', 'collect rare and unusual postage stamps', 6); + +INSERT INTO activities.person (first_name,last_name,birth_date) VALUES ('Drew', 'Hope', '1999/10/10'); +INSERT INTO activities.person (first_name,last_name,birth_date) VALUES ('Lang', 'Heckman', '1959/11/11'); +INSERT INTO activities.person (first_name,last_name,birth_date) VALUES ('Lucy', 'Jones', '2010/1/1'); +INSERT INTO activities.person (first_name,last_name,birth_date) VALUES ('Stew', 'Hammer', '1990/3/28'); +INSERT INTO activities.person (first_name,last_name,birth_date) VALUES ('Dan', 'Lane', '1986/4/18'); + +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (1, 1, 2); +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (2, 1, 1); +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (3, 2, 1); +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (4, 3, 1); +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (5, 3, 3); +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (6, 3, 4); +INSERT INTO activities.person_hobby (ID, person_id, hobbie_id) VALUES (7, 4, 7); + diff --git a/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/HobbyTest.java b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/HobbyTest.java new file mode 100644 index 0000000..0b6b406 --- /dev/null +++ b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/HobbyTest.java @@ -0,0 +1,41 @@ +package com.origamisoftware.teach.advanced.model; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for Hobby class + */ +public class HobbyTest { + + final static String description = "ride on a skateboard"; + final static int minimumAge = 10; + final static String hobbyName = "SkateBoarding"; + + /** + * Testing helper method for generating Hobby test data + * + * @return a Hobby object that uses static constants for data. + */ + public static Hobby createHobby() { + Hobby hobby = new Hobby(); + hobby.setDescription(description); + hobby.setMinimumAge(minimumAge); + hobby.setName(hobbyName); + return hobby; + } + + @Test + public void testHobbySettersAndGetters() { + Hobby hobby = createHobby(); + int id = 10; + hobby.setId(id); + assertEquals("Minimum age", minimumAge, hobby.getMinimumAge()); + assertEquals("Description", description, hobby.getDescription()); + assertEquals("Name", hobbyName, hobby.getName()); + assertEquals("id", id, hobby.getId()); + + } + +} diff --git a/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/PersonHobbyTest.java b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/PersonHobbyTest.java new file mode 100644 index 0000000..b8e7223 --- /dev/null +++ b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/PersonHobbyTest.java @@ -0,0 +1,72 @@ +package com.origamisoftware.teach.advanced.model; + +import org.junit.Test; + +import java.sql.Timestamp; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Unit test for PersonHobby class + */ +public class PersonHobbyTest { + + /** + * Testing helper method for generating PersonHobby test data + * + * @return a PersonHobby object that uses Person and Hobby + * return from their respective create method. + */ + public static PersonHobby createPersonHobby() { + Person person = PersonTest.createPerson(); + Hobby hobby = HobbyTest.createHobby(); + return new PersonHobby(person, hobby); + } + + @Test + public void testPersonHobbiesGetterAndSetters() { + Hobby hobby = HobbyTest.createHobby(); + Person person = PersonTest.createPerson(); + PersonHobby personHobby = new PersonHobby(); + int id = 10; + personHobby.setId(id); + personHobby.setPerson(person); + personHobby.setHobby(hobby); + assertEquals("person matches", person, personHobby.getPerson()); + assertEquals("hobby matches", hobby, personHobby.getHobby()); + assertEquals("id matches", id, personHobby.getId()); + } + + @Test + public void testEqualsNegativeDifferentPerson() { + PersonHobby personHobby = createPersonHobby(); + personHobby.setId(10); + Hobby hobby = HobbyTest.createHobby(); + Person person = new Person(); + Timestamp birthDate = new Timestamp(PersonTest.birthDayCalendar.getTimeInMillis() + 10000); + person.setBirthDate(birthDate); + person.setFirstName(PersonTest.firstName); + person.setLastName(PersonTest.lastName); + PersonHobby personHobby2 = new PersonHobby(person, hobby); + assertFalse("Different person", personHobby.equals(personHobby2)); + } + + @Test + public void testEquals() { + PersonHobby personHobby = createPersonHobby(); + assertTrue("Same objects are equal", personHobby.equals(createPersonHobby())); + } + + @Test + public void testToString() { + PersonHobby personHobby = createPersonHobby(); + assertTrue("toString has lastName", personHobby.toString().contains(PersonTest.lastName)); + assertTrue("toString has person", personHobby.toString().contains(PersonTest.firstName)); + assertTrue("toString has hobby description", personHobby.toString().contains(HobbyTest.description)); + assertTrue("toString has hobby name", personHobby.toString().contains(HobbyTest.hobbyName)); + assertTrue("toString has hobby min age", personHobby.toString().contains(Integer.toString(HobbyTest.minimumAge))); + } + +} diff --git a/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/PersonTest.java b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/PersonTest.java new file mode 100644 index 0000000..6ec0aaa --- /dev/null +++ b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/model/PersonTest.java @@ -0,0 +1,50 @@ +package com.origamisoftware.teach.advanced.model; + +import org.junit.Test; + +import java.sql.Timestamp; +import java.util.Calendar; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for the Person class + */ +public class PersonTest { + + public static final Calendar birthDayCalendar = Calendar.getInstance(); + + static { + birthDayCalendar.set(1990, Calendar.JANUARY, 12); + } + + public static final String firstName = "Spencer"; + public static final String lastName = "Marks"; + public static final Timestamp birthDate = new Timestamp(birthDayCalendar.getTimeInMillis()); + + /** + * Testing helper method for generating Person test data + * + * @return a Person object that uses static constants for data. + */ + public static Person createPerson() { + Person person = new Person(); + person.setBirthDate(birthDate); + person.setFirstName(firstName); + person.setLastName(lastName); + return person; + } + + @Test + public void testPersonGettersAndSetters() { + Person person = createPerson(); + int id = 10; + person.setId(id); + assertEquals("first name matches", firstName, person.getFirstName()); + assertEquals("last name matches", lastName, person.getLastName()); + assertEquals("birthday matches", birthDate, person.getBirthDate()); + assertEquals("id matches", id, person.getId()); + + } + +} diff --git a/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/services/ActivitiesServiceFactoryTest.java b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/services/ActivitiesServiceFactoryTest.java new file mode 100644 index 0000000..5807920 --- /dev/null +++ b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/services/ActivitiesServiceFactoryTest.java @@ -0,0 +1,19 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.service.ActivitiesService; +import com.origamisoftware.teach.advanced.service.ActivitiesServiceFactory; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * JUnit test for activitiesService + */ +public class ActivitiesServiceFactoryTest { + + @Test + public void testFactory() { + ActivitiesService instance = ActivitiesServiceFactory.getInstance(); + assertNotNull("Make sure factory works", instance); + } +} diff --git a/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/services/DatabaseActivitiesServiceTest.java b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/services/DatabaseActivitiesServiceTest.java new file mode 100644 index 0000000..1e50efb --- /dev/null +++ b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/services/DatabaseActivitiesServiceTest.java @@ -0,0 +1,103 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.Hobby; +import com.origamisoftware.teach.advanced.model.Person; +import com.origamisoftware.teach.advanced.model.PersonTest; +import com.origamisoftware.teach.advanced.service.ActivitiesService; +import com.origamisoftware.teach.advanced.service.ActivitiesServiceException; +import com.origamisoftware.teach.advanced.service.ActivitiesServiceFactory; +import com.origamisoftware.teach.advanced.util.DatabaseUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.sql.Timestamp; +import java.util.Calendar; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Unit tests for the DatabaseActivitiesService + */ +public class DatabaseActivitiesServiceTest { + + private ActivitiesService activitiesService; + + private void initDb() throws Exception { + DatabaseUtils.initializeDatabase(DatabaseUtils.initializationFile); + } + + // do not assume db is correct + @Before + public void setUp() throws Exception { + // we could also copy db state here for later restore before initializing + initDb(); + activitiesService = ActivitiesServiceFactory.getInstance(); + } + + // clean up after ourselves. (this could also restore db from initial state + @After + public void tearDown() throws Exception { + initDb(); + } + + @Test + public void testGetInstance() { + assertNotNull("Make sure activitiesService is available", activitiesService); + } + + @Test + public void testGetPerson() throws ActivitiesServiceException { + List personList = activitiesService.getPerson(); + assertFalse("Make sure we get some Person objects from service", personList.isEmpty()); + } + + @Test + public void testAddOrUpdatePerson()throws ActivitiesServiceException { + Person newPerson = PersonTest.createPerson(); + activitiesService.addOrUpdatePerson(newPerson); + List personList = activitiesService.getPerson(); + boolean found = false; + for (Person person : personList) { + Timestamp returnedBirthDate = person.getBirthDate(); + Calendar returnCalendar = Calendar.getInstance(); + returnCalendar.setTimeInMillis(returnedBirthDate.getTime()); + if (returnCalendar.get(Calendar.MONTH) == PersonTest.birthDayCalendar.get(Calendar.MONTH) + && + returnCalendar.get(Calendar.YEAR) == PersonTest.birthDayCalendar.get(Calendar.YEAR) + && + returnCalendar.get(Calendar.DAY_OF_MONTH) == PersonTest.birthDayCalendar.get(Calendar.DAY_OF_MONTH) + && + person.getLastName().equals(PersonTest.lastName) + && + person.getFirstName().equals(PersonTest.firstName)) { + found = true; + break; + } + } + assertTrue("Found the person we added", found); + } + + @Test + public void testGetHobbiesByPerson() throws ActivitiesServiceException { + Person person = PersonTest.createPerson(); + List hobbies = activitiesService.getHobbies(person); + // make the person have all the hobbies + for (Hobby hobby : hobbies) { + activitiesService.addHobbyToPerson(hobby, person); + } + List hobbyList = activitiesService.getHobbies(person); + for (Hobby hobby : hobbies) { + boolean removed = hobbyList.remove(hobby); + assertTrue("Verify that the hobby was found on the list", removed); + } + // if hobbyList is empty then we know the lists matched. + assertTrue("Verify the list of returned hobbies match what was expected ", hobbyList.isEmpty()); + + } + + +} diff --git a/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/util/DatabaseUtilsTest.java b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/util/DatabaseUtilsTest.java new file mode 100644 index 0000000..06e08d2 --- /dev/null +++ b/WK6/ORM_Example/src/test/java/com/origamisoftware/teach/advanced/util/DatabaseUtilsTest.java @@ -0,0 +1,39 @@ +package com.origamisoftware.teach.advanced.util; + +import org.junit.Test; + +import java.sql.Connection; +import java.sql.Statement; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for the DatabaseUtils class + */ +public class DatabaseUtilsTest { + + @Test + public void testGoodInitFile() throws Exception { + DatabaseUtils.initializeDatabase(DatabaseUtils.initializationFile); + } + + @Test(expected = DatabaseInitializationException.class) + public void testBadInitFile() throws Exception { + DatabaseUtils.initializeDatabase("bogus"); + } + + @Test + public void testGetConnection() throws Exception{ + Connection connection = DatabaseUtils.getConnection(); + assertNotNull("verify that we can get a connection ok",connection); + } + + @Test + public void testGetConnectionWorks() throws Exception{ + Connection connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + boolean execute = statement.execute("select * from person"); + assertTrue("verify that we can execute a statement",execute); + } +} diff --git a/WK6/stockquote-week5-solved/lib/antlr-2.7.7.jar b/WK6/stockquote-week5-solved/lib/antlr-2.7.7.jar new file mode 100644 index 0000000..5e5f14b Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/antlr-2.7.7.jar differ diff --git a/WK6/stockquote-week5-solved/lib/byte-buddy-1.8.17.jar b/WK6/stockquote-week5-solved/lib/byte-buddy-1.8.17.jar new file mode 100644 index 0000000..45d3a29 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/byte-buddy-1.8.17.jar differ diff --git a/WK6/stockquote-week5-solved/lib/classmate-1.3.4.jar b/WK6/stockquote-week5-solved/lib/classmate-1.3.4.jar new file mode 100644 index 0000000..5be6d99 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/classmate-1.3.4.jar differ diff --git a/WK6/stockquote-week5-solved/lib/dom4j-1.6.1.jar b/WK6/stockquote-week5-solved/lib/dom4j-1.6.1.jar new file mode 100644 index 0000000..c8c4dbb Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/dom4j-1.6.1.jar differ diff --git a/WK6/stockquote-week5-solved/lib/hibernate-commons-annotations-5.0.4.Final.jar b/WK6/stockquote-week5-solved/lib/hibernate-commons-annotations-5.0.4.Final.jar new file mode 100644 index 0000000..4fa4af3 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/hibernate-commons-annotations-5.0.4.Final.jar differ diff --git a/WK6/stockquote-week5-solved/lib/hibernate-core-5.3.6.Final.jar b/WK6/stockquote-week5-solved/lib/hibernate-core-5.3.6.Final.jar new file mode 100644 index 0000000..df45bf4 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/hibernate-core-5.3.6.Final.jar differ diff --git a/WK6/stockquote-week5-solved/lib/jandex-2.0.5.Final.jar b/WK6/stockquote-week5-solved/lib/jandex-2.0.5.Final.jar new file mode 100644 index 0000000..fb9c235 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/jandex-2.0.5.Final.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javassist-3.23.1-GA.jar b/WK6/stockquote-week5-solved/lib/javassist-3.23.1-GA.jar new file mode 100644 index 0000000..fe85fd1 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javassist-3.23.1-GA.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.activation-api-1.2.0.jar b/WK6/stockquote-week5-solved/lib/javax.activation-api-1.2.0.jar new file mode 100644 index 0000000..986c365 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.activation-api-1.2.0.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.annotation.jar b/WK6/stockquote-week5-solved/lib/javax.annotation.jar new file mode 100644 index 0000000..52dca7f Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.annotation.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.ejb.jar b/WK6/stockquote-week5-solved/lib/javax.ejb.jar new file mode 100644 index 0000000..4ebf5ec Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.ejb.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.jms.jar b/WK6/stockquote-week5-solved/lib/javax.jms.jar new file mode 100644 index 0000000..d31451a Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.jms.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.persistence-api-2.2.jar b/WK6/stockquote-week5-solved/lib/javax.persistence-api-2.2.jar new file mode 100644 index 0000000..164ef3a Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.persistence-api-2.2.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.persistence.jar b/WK6/stockquote-week5-solved/lib/javax.persistence.jar new file mode 100644 index 0000000..21d80e0 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.persistence.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.resource.jar b/WK6/stockquote-week5-solved/lib/javax.resource.jar new file mode 100644 index 0000000..696a234 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.resource.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.servlet.jar b/WK6/stockquote-week5-solved/lib/javax.servlet.jar new file mode 100644 index 0000000..0519e4a Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.servlet.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.servlet.jsp.jar b/WK6/stockquote-week5-solved/lib/javax.servlet.jsp.jar new file mode 100644 index 0000000..9c0631c Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.servlet.jsp.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.servlet.jsp.jstl.jar b/WK6/stockquote-week5-solved/lib/javax.servlet.jsp.jstl.jar new file mode 100644 index 0000000..7be17cc Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.servlet.jsp.jstl.jar differ diff --git a/WK6/stockquote-week5-solved/lib/javax.transaction.jar b/WK6/stockquote-week5-solved/lib/javax.transaction.jar new file mode 100644 index 0000000..729c695 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/javax.transaction.jar differ diff --git a/WK6/stockquote-week5-solved/lib/jboss-logging-3.3.2.Final.jar b/WK6/stockquote-week5-solved/lib/jboss-logging-3.3.2.Final.jar new file mode 100644 index 0000000..67cde71 Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/jboss-logging-3.3.2.Final.jar differ diff --git a/WK6/stockquote-week5-solved/lib/jboss-transaction-api_1.2_spec-1.1.1.Final.jar b/WK6/stockquote-week5-solved/lib/jboss-transaction-api_1.2_spec-1.1.1.Final.jar new file mode 100644 index 0000000..627f7ce Binary files /dev/null and b/WK6/stockquote-week5-solved/lib/jboss-transaction-api_1.2_spec-1.1.1.Final.jar differ diff --git a/WK6/stockquote-week5-solved/pom.xml b/WK6/stockquote-week5-solved/pom.xml new file mode 100644 index 0000000..ed7ce3c --- /dev/null +++ b/WK6/stockquote-week5-solved/pom.xml @@ -0,0 +1,88 @@ + + 4.0.0 + com.origamisoftware.teach.effective + week-5-stock-ticker + jar + 1.0-SNAPSHOT + starter-app + http://maven.apache.org + + + UTF-8 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + -Xlint + ${project.build.sourceEncoding} + 1.8 + 1.8 + + + + + + + + + + junit + junit + 4.10 + jar + + + + mysql + mysql-connector-java + 5.1.6 + + + + javax.validation + validation-api + 1.0.0.GA + + + + org.mockito + mockito-all + 1.9.5 + + + + org.apache.httpcomponents + httpcore + 4.4 + + + + org.apache.ibatis + ibatis-sqlmap + 2.3.4.726 + + + org.jboss.shrinkwrap + shrinkwrap-api + 1.2.6 + + + org.codehaus.plexus + plexus-component-annotations + 1.6 + + + org.jboss.arquillian.config + arquillian-config-api + 1.4.0.Final + + + diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/apps/stockquote/BasicStockQuoteApplication.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/apps/stockquote/BasicStockQuoteApplication.java new file mode 100644 index 0000000..c1bb4c2 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/apps/stockquote/BasicStockQuoteApplication.java @@ -0,0 +1,150 @@ +package com.origamisoftware.teach.advanced.apps.stockquote; + +import com.origamisoftware.teach.advanced.model.StockQuery; +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.services.StockService; +import com.origamisoftware.teach.advanced.services.StockServiceException; +import com.origamisoftware.teach.advanced.services.ServiceFactory; +import com.origamisoftware.teach.advanced.util.Interval; + +import java.text.ParseException; +import java.util.List; + +/** + * A simple application that shows the StockService in action. + */ +public class BasicStockQuoteApplication { + + private StockService stockService; + + // an example of how to use enum - not part of assignment 3 but useful for assignment 4 + + /** + * An enumeration that indicates how the program terminates (ends) + */ + private enum ProgramTerminationStatusEnum { + + // for now, we just have normal or abnormal but could more specific ones as needed. + NORMAL(0), + ABNORMAL(-1); + + // when the program exits, this value will be reported to underlying OS + private int statusCode; + + /** + * Create a new ProgramTerminationStatusEnum + * + * @param statusCodeValue the value to return the OS. A value of 0 + * indicates success or normal termination. + * non 0 numbers indicate abnormal termination. + */ + private ProgramTerminationStatusEnum(int statusCodeValue) { + this.statusCode = statusCodeValue; + } + + /** + * @return The value sent to OS when the program ends. + */ + private int getStatusCode() { + return statusCode; + } + } + + /** + * Create a new Application. + * + * @param stockService the StockService this application instance should use for + * stock queries. + *

+ * NOTE: this is a example of Dependency Injection in action. + */ + public BasicStockQuoteApplication(StockService stockService) { + this.stockService = stockService; + } + + /** + * Given a stockQuery get back a the info about the stock to display to th user. + * + * @param stockQuery the stock to get data for. + * @return a String with the stock data in it. + * @throws StockServiceException If data about the stock can't be retrieved. This is a + * fatal error. + */ + public String displayStockQuotes(StockQuery stockQuery) throws StockServiceException { + StringBuilder stringBuilder = new StringBuilder(); + + List stockQuotes = + stockService.getQuote(stockQuery.getSymbol(), + stockQuery.getFrom(), + stockQuery.getUntil(), + Interval.DAY); // get one quote for each day in the from until date range. + + stringBuilder.append("Stock quotes for: " + stockQuery.getSymbol() + "\n"); + for (StockQuote stockQuote : stockQuotes) { + stringBuilder.append(stockQuote.toString()); + } + + return stringBuilder.toString(); + } + + /** + * Terminate the application. + * + * @param statusCode an enum value that indicates if the program terminated ok or not. + * @param diagnosticMessage A message to display to the user when the program ends. + * This should be an error message in the case of abnormal termination + *

+ * NOTE: This is an example of DRY in action. + * A program should only have one exit point. This makes it easy to do any clean up + * operations before a program quits from just one place in the code. + * It also makes for a consistent user experience. + */ + private static void exit(ProgramTerminationStatusEnum statusCode, String diagnosticMessage) { + if (statusCode == ProgramTerminationStatusEnum.NORMAL) { + System.out.println(diagnosticMessage); + } else if (statusCode == ProgramTerminationStatusEnum.ABNORMAL) { + System.err.println(diagnosticMessage); + } else { + throw new IllegalStateException("Unknown ProgramTerminationStatusEnum."); + } + System.exit(statusCode.getStatusCode()); + } + + /** + * Run the StockTicker application. + *

+ * + * @param args one or more stock symbols + */ + public static void main(String[] args) { + + // be optimistic init to positive values + ProgramTerminationStatusEnum exitStatus = ProgramTerminationStatusEnum.NORMAL; + String programTerminationMessage = "Normal program termination."; + if (args.length != 3) { + exit(ProgramTerminationStatusEnum.ABNORMAL, + "Please supply 3 arguments a stock symbol, a start date (MM/DD/YYYY) and end date (MM/DD/YYYY)"); + } + try { + + StockQuery stockQuery = new StockQuery(args[0], args[1], args[2]); + StockService stockService = ServiceFactory.getStockServiceInstance(); + BasicStockQuoteApplication basicStockQuoteApplication = + new BasicStockQuoteApplication(stockService); + basicStockQuoteApplication.displayStockQuotes(stockQuery); + + } catch (ParseException e) { + exitStatus = ProgramTerminationStatusEnum.ABNORMAL; + programTerminationMessage = "Invalid date data: " + e.getMessage(); + } catch (StockServiceException e) { + exitStatus = ProgramTerminationStatusEnum.ABNORMAL; + programTerminationMessage = "StockService failed: " + e.getMessage(); + } catch (Throwable t) { + exitStatus = ProgramTerminationStatusEnum.ABNORMAL; + programTerminationMessage = "General application error: " + t.getMessage(); + } + + exit(exitStatus, programTerminationMessage); + System.out.println("Oops could not parse a date"); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Person.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Person.java new file mode 100644 index 0000000..fd1d977 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Person.java @@ -0,0 +1,140 @@ +package com.origamisoftware.teach.advanced.model; + +import javax.persistence.Basic; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import java.sql.Timestamp; + +/** + * Models the Person table + */ +@Entity +@Table(name="person") +public class Person { + + private int id; + private String firstName; + private String lastName; + private Timestamp birthDate; + + public Person(String firstName, String lastName, Timestamp birthDay) { + this.firstName = firstName; + this.lastName = lastName; + this.birthDate = birthDay; + } + public Person(){} + + /** + * Primary Key - Unique ID for a particular row in the person table. + * + * @return an integer value + */ + @Id + @Column(name = "ID", nullable = false, insertable = true, updatable = true) + public int getId() { + return id; + } + + /** + * Set the unique ID for a particular row in the person table. + * This method should not be called by client code. The value is managed in internally. + * + * @param id a unique value. + */ + public void setId(int id) { + this.id = id; + } + + /** + * + * @return the person's first name + */ + @Basic + @Column(name = "first_name", nullable = false, insertable = true, updatable = true, length = 256) + public String getFirstName() { + return firstName; + } + + /** + * Specify the person's first name + * @param firstName a String value + */ + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + /** + * + * @return the person's last name + */ + @Basic + @Column(name = "last_name", nullable = false, insertable = true, updatable = true, length = 256) + public String getLastName() { + return lastName; + } + + /** + * Specify the person's last name + * @param lastName a String value + */ + public void setLastName(String lastName) { + this.lastName = lastName; + } + + /** + * + * @return the person's birthdate. + */ + @Basic + @Column(name = "birth_date", nullable = false, insertable = true, updatable = true) + public Timestamp getBirthDate() { + return birthDate; + } + + /** + * Specify the person's date of birth. + * @param birthDate the time the person was born. + */ + public void setBirthDate(Timestamp birthDate) { + this.birthDate = birthDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Person person = (Person) o; + + if (id != person.id) return false; + if (birthDate != null ? !birthDate.equals(person.birthDate) : person.birthDate != null) + return false; + if (firstName != null ? !firstName.equals(person.firstName) : person.firstName != null) + return false; + if (lastName != null ? !lastName.equals(person.lastName) : person.lastName != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (firstName != null ? firstName.hashCode() : 0); + result = 31 * result + (lastName != null ? lastName.hashCode() : 0); + result = 31 * result + (birthDate != null ? birthDate.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "Person{" + + "id=" + id + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", birthDate=" + birthDate + + '}'; + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Person_Quotes.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Person_Quotes.java new file mode 100644 index 0000000..2d00e4c --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Person_Quotes.java @@ -0,0 +1,127 @@ +package com.origamisoftware.teach.advanced.model; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * Models a table that combines person with their quotes. + */ +@Entity +@Table(name = "person_quotes", catalog = "stocks") +public class Person_Quotes { + private int id; + private Person person; + private Quotes quotes; + + /** + * Create a Person_Quotes that needs to be initialized + */ + public Person_Quotes() { + // this empty constructor is required by hibernate framework + + } + + /** + * Create a valid Person_Quotes + * + * @param person the person to assign the quotes to + * @param quotes the quotes to associate the person with + */ + public Person_Quotes(Person person, Quotes quotes) { + setQuotes(quotes); + setPerson(person); + } + + /** + * Primary Key - Unique ID for a particular row in the person_hobby table. + * + * @return an integer value + */ + @Id + @Column(name = "ID", nullable = false, insertable = true, updatable = true) + public int getId() { + return id; + } + + /** + * Set the unique ID for a particular row in the person_hobby table. + * This method should not be called by client code. The value is managed in internally. + * + * @param id a unique value. + */ + public void setId(int id) { + this.id = id; + } + + /** + * + * @return get the Person associated with this hobby + */ + @ManyToOne + @JoinColumn(name = "person_id", referencedColumnName = "ID", nullable = false) + public Person getPerson() { + return person; + } + + /** + * Specify the Person associated with the hobby. + * + * @param person a person instance + */ + public void setPerson(Person person) { + this.person = person; + } + + /** + * + * @return get the quotes associated with this Person + */ + @ManyToOne + @JoinColumn(name = "quotes_id", referencedColumnName = "ID", nullable = false) + public Quotes getQuotes() { + return quotes; + } + + /** + * Specify the quotes associated with the Person. + * + * @param quotes a person instance + */ + public void setQuotes(Quotes quotes) { + this.quotes = quotes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Person_Quotes that = (Person_Quotes) o; + + if (id != that.id) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + person.hashCode(); + result = 31 * result + quotes.hashCode(); + return result; + } + + @Override + public String toString() { + return "Person_Quotes{" + + "id=" + id + + ", person=" + person + + ", quotes=" + quotes + + '}'; + } +} + diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Quotes.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Quotes.java new file mode 100644 index 0000000..038a3e9 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/Quotes.java @@ -0,0 +1,127 @@ +package com.origamisoftware.teach.advanced.model; + +import javax.persistence.*; +import java.sql.Timestamp; + +/** + * Models the quotes table + */ +@Entity +@Table(name="quotes") +public class Quotes { + + private int id; + private String symbol; + private Timestamp time; + private int price; + + public Quotes(String symbol, int price, Timestamp time) { + } + public Quotes(){} + /** + * Primary Key - Unique ID for a particular row in the quotes table. + * + * @return an integer value + */ + @Id + @Column(name = "ID", nullable = false, insertable = true, updatable = true) + public int getId() { + return id; + } + + /** + * Set the unique ID for a particular row in the quotes table. + * This method should not be called by client code. The value is managed in internally. + * + * @param id a unique value. + */ + public void setId(int id) { + this.id = id; + } + + /** + * @return the symbol column as a String + */ + @Basic + @Column(name = "symbol", nullable = false, insertable = true, updatable = true, length = 256) + public String getSymbol() { + return symbol; + } + + /** + * Specify the stocks symbol + * + * @param name a String value + */ + public void setSymbol(String name) { + this.symbol = name; + } + + @Basic + @Column(name = "time", nullable = false, insertable = true, updatable = true) + public Timestamp getTime() { + return time; + } + + /** + * Specify the stocks date range. + * @param time the time the person was born. + */ + public void setTime(Timestamp time) { + this.time = time; + } + + /** + * The price for the stock, an int value + * @return the value of the price column as an int value + */ + @Basic + @Column(name = "price", nullable = false, insertable = true, updatable = true) + public int getPrice() { + return price; + } + + /** + * Specify the recommended $ amount for the price + * @param price an int value + */ + public void setPrice(int price) { + this.price = price; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Quotes quotes = (Quotes) o; + + if (id != quotes.id) return false; + if (time != null ? !time.equals(quotes.time) : quotes.time != null) + return false; + if (symbol != null ? !symbol.equals(quotes.symbol) : quotes.symbol != null) + return false; + if (price != quotes.price) return false; + + return true; + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + (symbol != null ? symbol.hashCode() : 0); + result = 31 * result + (time != null ? time.hashCode() : 0); + result = 31 * result + price; + return result; + } + + @Override + public String toString() { + return "Quotes{" + + "id=" + id + + ", symbol='" + symbol + '\'' + + ", time='" + time + '\'' + + ", price=" + price + + '}'; + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockData.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockData.java new file mode 100644 index 0000000..9d94f5b --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockData.java @@ -0,0 +1,27 @@ +package com.origamisoftware.teach.advanced.model; + +import java.text.SimpleDateFormat; + +/** + * Abstract Base class for classes that hold Stock data. + * Provides common code for such classes including date formatting. + */ +public abstract class StockData { + + /** + * Provide a single SimpleDateFormat for consistency + * and to avoid duplicated code. + */ + protected SimpleDateFormat simpleDateFormat; + + public static final String dateFormat = "yyyy-MM-dd HH:mm:ss"; + + /** + * Base constructor for StockData classes. + * Initialize member data that is shared with sub classes. + */ + public StockData() { + simpleDateFormat = new SimpleDateFormat(dateFormat); + } + +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockQuery.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockQuery.java new file mode 100644 index 0000000..750eb9f --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockQuery.java @@ -0,0 +1,58 @@ +package com.origamisoftware.teach.advanced.model; + + +import org.apache.http.annotation.Immutable; + +import javax.validation.constraints.NotNull; +import java.text.ParseException; +import java.util.Calendar; + +/** + * This class is used to a single query to stock service. + */ +@Immutable +public class StockQuery extends StockData{ + + private final String symbol; + private final Calendar from; + private final Calendar until; + + /** + * Create a new instance from string data. This constructor will convert + * dates described as a String to Date objects. + * + * @param symbol the stock symbol + * @param from the start date as a string in the form of yyyy/MM/dd + * @throws ParseException if the format of the date String is incorrect. If this happens + * the only recourse is to try again with a correctly formatted String. + */ + public StockQuery(@NotNull String symbol, @NotNull String from, @NotNull String until) throws ParseException { + super(); + this.symbol = symbol; + this.from = Calendar.getInstance(); + this.until = Calendar.getInstance(); + this.from.setTime(simpleDateFormat.parse(from)); + this.until.setTime(simpleDateFormat.parse(until)); + } + + /** + * @return get the stock symbol associated with this query + */ + public String getSymbol() { + return symbol; + } + + /** + * @return get the start Calendar associated with this query + */ + public Calendar getFrom() { + return from; + } + + /** + * @return get the end Calendar associated with this query + */ + public Calendar getUntil() { + return until; + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockQuote.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockQuote.java new file mode 100644 index 0000000..85e7847 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/model/StockQuote.java @@ -0,0 +1,59 @@ +package com.origamisoftware.teach.advanced.model; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * A container class that contains stock data. + */ +public class StockQuote extends StockData { + + private BigDecimal price; + private Date date; + private String symbol; + + /** + * Create a new instance of a StockQuote. + * + * @param price the share price for the given date + * @param date the date of the share price + * @param symbol the stock symbol. + */ + public StockQuote(BigDecimal price, Date date, String symbol) { + super(); + this.price = price; + this.date = date; + this.symbol = symbol; + } + + /** + * @return Get the share price for the given date. + */ + public BigDecimal getPrice() { + return price; + } + + /** + * @return The date of the share price + */ + public Date getDate() { + return date; + } + + /** + * @return The stock symbol. + */ + public String getSymbol() { + return symbol; + } + + @Override + public String toString() { + String dateString = simpleDateFormat.format(date); + return "StockQuote{" + + "price=" + price + + ", date=" + dateString + + ", symbol='" + symbol + '\'' + + '}'; + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/DatabasePersonService.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/DatabasePersonService.java new file mode 100644 index 0000000..a554890 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/DatabasePersonService.java @@ -0,0 +1,114 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.Person; +import com.origamisoftware.teach.advanced.model.Quotes; +import com.origamisoftware.teach.advanced.util.DatabaseConnectionException; +import com.origamisoftware.teach.advanced.util.DatabaseUtils; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +/** + * An implementation of the PersonService interface that gets + * Person/Quote data from a database. + */ +class DatabasePersonService implements PersonService { + + /**accesses database to retrieve person information + * + * @return a new person populated with database information + * @throws PersonServiceException + */ + + @Override + public List getPerson() throws PersonServiceException { + List personList = null; + Person person = new Person(); + String firstName, lastName; + Timestamp birthDay; + + firstName = person.getFirstName(); + lastName = person.getLastName(); + birthDay = person.getBirthDate(); + + try { + Connection connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + String queryString = "select * from person where first_name = '" + firstName + "' and last_name = '" + lastName + "'" + + "and birth_date = '" + birthDay + "'"; + + ResultSet resultSet = statement.executeQuery(queryString); + personList = new ArrayList<>(resultSet.getFetchSize()); + while (resultSet.next()) { + firstName = resultSet.getString("first_name"); + lastName = resultSet.getString("last_name"); + birthDay = resultSet.getTimestamp("time"); + + personList.add(new Person(firstName, lastName, birthDay)); + } + + } catch (DatabaseConnectionException | SQLException exception) { + throw new PersonServiceException(exception.getMessage(), exception); + } + if (personList.isEmpty()) { + throw new PersonServiceException("There is no person data for:" + firstName + lastName + birthDay, null); + } + return personList; + } + //seems redundant with what list getPerson does? If i delete i get an error for not using all of the + //interfaces methods, should they be left blank? + @Override + public void addOrUpdatePerson(Person person) throws PersonServiceException { + + } + + /**accesses database to get quote information for use with PersonQuote + * + * @param person + * @return new list of quote information from the database + * @throws PersonServiceException + */ + @Override + public List getQuotes(Person person) throws PersonServiceException { + List quotesList = null; + Quotes quotes = new Quotes(); + String symbol; + int price = 0; + Timestamp time; + + symbol = quotes.getSymbol(); + price = quotes.getPrice(); + time = quotes.getTime(); + + try { + Connection connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + String queryString = "select * from quotes where symbol = '" + symbol + "' and time = '" + time + "'" + + "and price = '" + price + "'"; + + ResultSet resultSet = statement.executeQuery(queryString); + quotesList = new ArrayList<>(resultSet.getFetchSize()); + while (resultSet.next()) { + symbol = resultSet.getString("symbol"); + price = resultSet.getInt("price"); + time = resultSet.getTimestamp("time"); + + quotesList.add(new Quotes(symbol, price, time)); + } + + } catch (DatabaseConnectionException | SQLException exception) { + throw new PersonServiceException(exception.getMessage(), exception); + } + if (quotesList.isEmpty()) { + throw new PersonServiceException("There is no stock data for:" + symbol + price + time, null); + } + + return quotesList; + } + //may not need to interact with the database? Information is aggregated in another method? + @Override + public void addQuotesToPerson(Quotes quotes, Person person) throws PersonServiceException { + + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/DatabaseStockService.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/DatabaseStockService.java new file mode 100644 index 0000000..309ba28 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/DatabaseStockService.java @@ -0,0 +1,175 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockData; +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.util.DatabaseConnectionException; +import com.origamisoftware.teach.advanced.util.DatabaseUtils; +import com.origamisoftware.teach.advanced.util.Interval; + +import java.math.BigDecimal; +import java.sql.Connection; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +/** + * An implementation of the StockService interface that gets + * stock data from a database. + */ +class DatabaseStockService implements StockService { + + /** + * Return the current price for a share of stock for the given symbol + * + * @param symbol the stock symbol of the company you want a quote for. + * e.g. APPL for APPLE + * @return a BigDecimal instance + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + @Override + public StockQuote getQuote(String symbol) throws StockServiceException { + // todo - this is a pretty lame implementation why? + List stockQuotes = null; + try { + Connection connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + String queryString = "select * from quotes where symbol = '" + symbol + "'"; + + ResultSet resultSet = statement.executeQuery(queryString); + stockQuotes = new ArrayList<>(resultSet.getFetchSize()); + while (resultSet.next()) { + String symbolValue = resultSet.getString("symbol"); + Date time = resultSet.getDate("time"); + BigDecimal price = resultSet.getBigDecimal("price"); + stockQuotes.add(new StockQuote(price, time, symbolValue)); + } + + } catch (DatabaseConnectionException | SQLException exception) { + throw new StockServiceException(exception.getMessage(), exception); + } + if (stockQuotes.isEmpty()) { + throw new StockServiceException("There is no stock data for:" + symbol); + } + return stockQuotes.get(0); + } + + /** + * Get a historical list of stock quotes for the provide symbol + * + * @param symbol the stock symbol to search for + * @param from the date of the first stock quote + * @param until the date of the last stock quote + * @param interval the number of stockquotes to get per a 24 hour period. + * @return a list of StockQuote instances + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + @Override + public List getQuote(String symbol, Calendar from, Calendar until, Interval interval) throws StockServiceException { + List stockQuotes = null; + try { + Connection connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(StockData.dateFormat); + + String fromString = simpleDateFormat.format(from.getTime()); + String untilString = simpleDateFormat.format(until.getTime()); + + String queryString = "select * from quotes where symbol = '" + symbol + "'" + + "and time BETWEEN '" + fromString + "' and '" + untilString + "'"; + + ResultSet resultSet = statement.executeQuery(queryString); + stockQuotes = new ArrayList<>(); + StockQuote previousStockQuote = null; + + /** + * + * Here is the general idea behind filtering on interval. + * It is not perfect, it would be better to filter using SQL and let the DBMS to it, + * but this way will work OK for small or medium data sets, and since this class + * (90.303 - not this actual Java class) is focused on Java and not SQL + * I thought this approach was appropriate. + * + * SIDE BAR: + * + * It also highlights a consistent tension which is the question of what should the + * DBMS be responsible for and what should the Java code be responsible. There is + * no one answer here as there are lots of factors to consider including: + * + * SQL compatibility (complex logic in the DBMS might not be portable to another DBMS) + * + * Where do you want your application's business logic to exist? + * In the DBMS only, in the Java code only? Spread out between the two? + * Keeping it in one place makes a lot more sense than spreading it out? But where is + * best - that depends on your application design, and capacity requirements, your teams' + * skill set (Do you have a lot of db experts on staff?) and whether or not DBMS vendor + * lock in is a concern or not. + * + * Performance and scalability + * DBMS intensive application will benefit from having the DBMS do the heavy data + * sorting. + * + * Ease of maintenance and test + * This questions really relates to your teams' skill set. + * + */ + Calendar calendar = Calendar.getInstance(); + while (resultSet.next()) { + String symbolValue = resultSet.getString("symbol"); + Timestamp timeStamp = resultSet.getTimestamp("time"); + calendar.setTimeInMillis(timeStamp.getTime()); + BigDecimal price = resultSet.getBigDecimal("price"); + java.util.Date time = calendar.getTime(); + StockQuote currentStockQuote = new StockQuote(price, time, symbolValue); + + if (previousStockQuote == null) { // first time through always add stockquote + + stockQuotes.add(currentStockQuote); + + } else if (isInterval(currentStockQuote.getDate(), + interval, + previousStockQuote.getDate())) { + + stockQuotes.add(currentStockQuote); + } + + previousStockQuote = currentStockQuote; + } + + } catch (DatabaseConnectionException | SQLException exception) { + throw new StockServiceException(exception.getMessage(), exception); + } + if (stockQuotes.isEmpty()) { + throw new StockServiceException("There is no stock data for:" + symbol); + } + + return stockQuotes; + } + + + /** + * Returns true of the currentStockQuote has a date that is later by the time + * specified in the interval value from the previousStockQuote time. + * + * @param endDate the end time + * @param interval the period of time that must exist between previousStockQuote and currentStockQuote + * in order for this method to return true. + * @param startDate the starting date + * @return + */ + private boolean isInterval(java.util.Date endDate, Interval interval, java.util.Date startDate) { + Calendar startDatePlusInterval = Calendar.getInstance(); + startDatePlusInterval.setTime(startDate); + startDatePlusInterval.add(Calendar.MINUTE, interval.getMinutes()); + return endDate.after(startDatePlusInterval.getTime()); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/PersonService.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/PersonService.java new file mode 100644 index 0000000..0c7b5df --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/PersonService.java @@ -0,0 +1,50 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.Quotes; +import com.origamisoftware.teach.advanced.model.Person; + +import java.util.List; + +/** + * + */ +public interface PersonService { + + /** + * Get a list of all people + * + * @return a list of Person instances + * @throws PersonServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + List getPerson() throws PersonServiceException; + + /** + * Add a new person or update an existing Person's data + * + * @param person a person object to either update or create + * @throws PersonServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + void addOrUpdatePerson(Person person) throws PersonServiceException; + + /** + * Get a list of all a person's quotes. + * + * @return a list of quote instances + * @throws PersonServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + List getQuotes(Person person) throws PersonServiceException; + + /** + * Assign quotes to a person. + * + * @param quotes The quotes to assign + * @param person The person to assign the quotes too. + * @throws PersonServiceException if a service can not read or write the requested data + * or otherwise perform the requested operation. + */ + public void addQuotesToPerson(Quotes quotes, Person person) throws PersonServiceException; + +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/PersonServiceException.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/PersonServiceException.java new file mode 100644 index 0000000..8be7290 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/PersonServiceException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.services; + +/** + * Used to signal an issue with ActivitiesService + */ +public class PersonServiceException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public PersonServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/ServiceFactory.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/ServiceFactory.java new file mode 100644 index 0000000..c737678 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/ServiceFactory.java @@ -0,0 +1,23 @@ +package com.origamisoftware.teach.advanced.services; + +/** + * A factory that returns a StockService instance. + */ +public class ServiceFactory { + + /** + * Prevent instantiations + */ + private ServiceFactory() {} + + /** + * + * @return get a StockService instance + */ + public static StockService getStockServiceInstance() { + return new SimpleStockService(); + } + public static PersonService getPersonServiceInstance() { + return new SimplePersonService(); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/SimplePersonService.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/SimplePersonService.java new file mode 100644 index 0000000..65b7152 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/SimplePersonService.java @@ -0,0 +1,70 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.Person; +import com.origamisoftware.teach.advanced.model.Person_Quotes; +import com.origamisoftware.teach.advanced.model.Quotes; +import com.sun.xml.internal.fastinfoset.util.StringArray; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +/** + * An implementation of the PersonService that returns hard coded data. + */ +class SimplePersonService implements PersonService { + + @Override + public List getPerson() throws PersonServiceException { + List personList = new ArrayList<>(); + Person_Quotes person_quotes = new Person_Quotes(); + + Person person = person_quotes.getPerson(); + + personList.add(person); + + return personList; + } + + @Override + public void addOrUpdatePerson(Person person) throws PersonServiceException { + String firstName, lastName; + int id; + Timestamp birthDate; + + if (person == null) + getPerson(); + else{ + firstName = person.getFirstName(); + person.setFirstName(firstName); + + lastName = person.getLastName(); + person.setLastName(lastName); + + id = person.getId(); + person.setId(id); + + birthDate = person.getBirthDate(); + person.setBirthDate(birthDate);} + } + + @Override + public List getQuotes(Person person) throws PersonServiceException { + List quotesList = new ArrayList<>(); + Person_Quotes person_quotes = new Person_Quotes(); + + Quotes quotes = person_quotes.getQuotes(); + + quotesList.add(quotes); + + return quotesList; + } + + @Override + public void addQuotesToPerson(Quotes quotes, Person person) throws PersonServiceException { + StringArray personQuotes = new StringArray(); + personQuotes.add(person.toString()); + personQuotes.add(quotes.toString()); + + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/SimpleStockService.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/SimpleStockService.java new file mode 100644 index 0000000..28649ca --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/SimpleStockService.java @@ -0,0 +1,56 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.util.Interval; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +/** + * An implementation of the StockService that returns hard coded data. + */ + class SimpleStockService implements StockService { + + /** + * Return the current price for a share of stock for the given symbol + * + * @param symbol the stock symbol of the company you want a quote for. + * e.g. APPL for APPLE + * @return a BigDecimal instance + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + @Override + public StockQuote getQuote(String symbol) { + // a dead simple implementation. + return new StockQuote(new BigDecimal(100), Calendar.getInstance().getTime(), symbol); + } + + /** + * Get a historical list of stock quotes for the provide symbol + * + * @param symbol the stock symbol to search for + * @param from the date of the first stock quote + * @param until the date of the last stock quote + * @return a list of StockQuote instances + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + @Override + public List getQuote(String symbol, Calendar from, Calendar until, Interval interval) { + // a dead simple implementation. + List stockQuotes = new ArrayList<>(); + Date aDay = from.getTime(); + while (until.after(aDay)) { + stockQuotes.add(new StockQuote(new BigDecimal(100), aDay, symbol)); + from.add(Calendar.DAY_OF_YEAR, 1); + aDay = from.getTime(); + } + return stockQuotes; + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/StockService.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/StockService.java new file mode 100644 index 0000000..73860ae --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/StockService.java @@ -0,0 +1,42 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.util.Interval; + +import java.util.Calendar; +import java.util.List; + +/** + * This API describes how to get stock data from an external resource. + */ +public interface StockService { + + + /** + * Return the current price for a share of stock for the given symbol + * + * @param symbol the stock symbol of the company you want a quote for. + * e.g. APPL for APPLE + * @return a BigDecimal instance + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + StockQuote getQuote(String symbol) throws StockServiceException; + + /** + * Get a historical list of stock quotes for the provide symbol + * + * @param symbol the stock symbol to search for + * @param from the date of the first stock quote + * @param until the date of the last stock quote + * @param interval the number of stockquotes to get per a 24 hour period. + * @return a list of StockQuote instances + * @throws StockServiceException if using the service generates an exception. + * If this happens, trying the service may work, depending on the actual cause of the + * error. + */ + List getQuote(String symbol, Calendar from, Calendar until, Interval interval) throws StockServiceException; + +} + diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/StockServiceException.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/StockServiceException.java new file mode 100644 index 0000000..efcfa21 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/services/StockServiceException.java @@ -0,0 +1,37 @@ +package com.origamisoftware.teach.advanced.services; + +/** + * Used to signal a problem with the StockService. + */ +public class StockServiceException extends Exception { + + /** + * Constructs a new exception with the specified detail message. The + * cause is not initialized, and may subsequently be initialized by + * a call to {@link #initCause}. + * + * @param message the detail message. The detail message is saved for + * later retrieval by the {@link #getMessage()} method. + */ + public StockServiceException(String message) { + super(message); + } + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public StockServiceException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseConnectionException.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseConnectionException.java new file mode 100644 index 0000000..ea8d91e --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseConnectionException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.util; + +/** + * This class is used to signal a problem connecting to a database. + */ +public class DatabaseConnectionException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public DatabaseConnectionException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseException.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseException.java new file mode 100644 index 0000000..912397b --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.util; + +/** + * A higher level exception that tells the client accessing the database failed. . + */ +public class DatabaseException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public DatabaseException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseInitializationException.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseInitializationException.java new file mode 100644 index 0000000..bc09f7d --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseInitializationException.java @@ -0,0 +1,25 @@ +package com.origamisoftware.teach.advanced.util; + +/** + * This class is used to signal a problem initializing to a database. + */ +public class DatabaseInitializationException extends Exception { + + /** + * Constructs a new exception with the specified detail message and + * cause.

Note that the detail message associated with + * {@code cause} is not automatically incorporated in + * this exception's detail message. + * + * @param message the detail message (which is saved for later retrieval + * by the {@link #getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the + * {@link #getCause()} method). (A null value is + * permitted, and indicates that the cause is nonexistent or + * unknown.) + * @since 1.4 + */ + public DatabaseInitializationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseUtils.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseUtils.java new file mode 100644 index 0000000..0ca0409 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/DatabaseUtils.java @@ -0,0 +1,107 @@ +package com.origamisoftware.teach.advanced.util; + +import com.ibatis.common.jdbc.ScriptRunner; +import org.hibernate.cfg.Configuration; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + + +/** + * A class that contains database related utility methods. + */ +public class DatabaseUtils { + + public static final String initializationFile = "./src/main/sql/stocks_db_initialization.sql" ; + + private static Configuration configuration; + + /** + * Create a new or return an existing database configuration object. + * + * @return a Hibernate Configuration instance. + */ + private static Configuration getConfiguration() { + + synchronized (DatabaseUtils.class) { + if (configuration == null) { + configuration = new Configuration(); + configuration.configure("hibernate.cfg.xml"); + } + } + return configuration; + } + + public static Connection getConnection() throws DatabaseConnectionException { + Connection connection = null; + Configuration configuration = getConfiguration(); + try { + + Class.forName("com.mysql.jdbc.Driver"); + String databaseUrl = configuration.getProperty("connection.url"); + String username = configuration.getProperty("hibernate.connection.username"); + String password = configuration.getProperty("hibernate.connection.password"); + connection = DriverManager.getConnection(databaseUrl, username, password); + + // an example of throwing an exception appropriate to the abstraction. + } catch (ClassNotFoundException | SQLException e) { + throw new DatabaseConnectionException("Could not connect to the database." + e.getMessage(), e); + } + return connection; + } + + /** + * A utility method that runs a db initialize script. + * + * @param initializationScript full path to the script to run to create the schema + * @throws DatabaseInitializationException + */ + public static void initializeDatabase(String initializationScript) throws DatabaseInitializationException { + + Connection connection = null; + try { + connection = getConnection(); + connection.setAutoCommit(false); + ScriptRunner runner = new ScriptRunner(connection, false, false); + InputStream inputStream = new FileInputStream(initializationScript); + + InputStreamReader reader = new InputStreamReader(inputStream); + + runner.runScript(reader); + reader.close(); + connection.commit(); + connection.close(); + + } catch (DatabaseConnectionException | SQLException | IOException e) { + throw new DatabaseInitializationException("Could not initialize db because of:" + + e.getMessage(), e); + } + } + + /** + * Execute SQL code + * @param someSQL the code to execute + * @return true if the operation succeeded. + * @throws DatabaseException if accessing and executing the sql failed in an unexpected way. + * + */ + public static boolean executeSQL(String someSQL) throws DatabaseException { + Connection connection = null; + boolean returnValue = false; + try { + connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + returnValue = statement.execute(someSQL); + } catch (DatabaseConnectionException | SQLException e) { + throw new DatabaseException(e.getMessage(), e); + } + return returnValue; + } + +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/Interval.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/Interval.java new file mode 100644 index 0000000..b814914 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/Interval.java @@ -0,0 +1,27 @@ +package com.origamisoftware.teach.advanced.util; + +/** + * An enumeration that represents a of unit of time + */ +public enum Interval { + + MINUTE(1), HOUR(60), HALF_DAY(60 * 6), DAY(60 * 24); + + private int minutes; + + /** + * Create a new Interval enumeration. + * @param minutes the number of minutes in the interval + */ + private Interval(int minutes) { + this.minutes = minutes; + } + + /** + * + * @return the number of minutes in the interval + */ + public int getMinutes() { + return minutes; + } +} diff --git a/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/ProgramConfiguration.java b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/ProgramConfiguration.java new file mode 100644 index 0000000..0b03a55 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/java/com/origamisoftware/teach/advanced/util/ProgramConfiguration.java @@ -0,0 +1,15 @@ +package com.origamisoftware.teach.advanced.util; + +import java.util.Properties; + +/** + * A Utility class that provides application properties and + * configuration settings. + */ +public class ProgramConfiguration { + + public static Properties getApplicationProperties() { + return null; + } + +} diff --git a/WK6/stockquote-week5-solved/src/main/sql/stocks_db_initialization.sql b/WK6/stockquote-week5-solved/src/main/sql/stocks_db_initialization.sql new file mode 100644 index 0000000..64022fb --- /dev/null +++ b/WK6/stockquote-week5-solved/src/main/sql/stocks_db_initialization.sql @@ -0,0 +1,9 @@ +CREATE TABLE stocks.person_quotes +(ID INT PRIMARY KEY NOT NULL AUTO_INCREMENT, + person_id INT NOT NULL, + quotes_id INT NOT NULL, + FOREIGN KEY (person_id) REFERENCES person (ID), + FOREIGN KEY (quotes_id) REFERENCES quotes (ID) +); + + diff --git a/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/apps/stockquote/BasicStockQuoteApplicationTest.java b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/apps/stockquote/BasicStockQuoteApplicationTest.java new file mode 100644 index 0000000..6a92157 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/apps/stockquote/BasicStockQuoteApplicationTest.java @@ -0,0 +1,72 @@ +package com.origamisoftware.teach.advanced.apps.stockquote; + +import com.origamisoftware.teach.advanced.model.StockQuery; +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.services.StockService; +import com.origamisoftware.teach.advanced.services.StockServiceException; +import com.origamisoftware.teach.advanced.util.Interval; +import org.junit.Before; +import org.junit.Test; + +import java.math.BigDecimal; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for BasicStockQuoteApplication + */ +public class BasicStockQuoteApplicationTest { + + private BasicStockQuoteApplication basicStockQuoteApplication; + private StockService stockServiceMock; + + @Before + public void setUp() { + stockServiceMock = mock(StockService.class); + } + + @Test + public void testValidConstruction() { + basicStockQuoteApplication = new BasicStockQuoteApplication(stockServiceMock); + assertNotNull("Basic construction works"); + } + + @Test + public void testDisplayResults() throws ParseException, StockServiceException { + basicStockQuoteApplication = new BasicStockQuoteApplication(stockServiceMock); + String symbol = "APPL"; + String from = "2011-10-29 12:12:12"; //yyyy-MM-dd HH:mm:ss + String until = "2014-11-29 12:12:12"; + StockQuery stockQuery = new StockQuery(symbol, from, until); + + List stockQuotes = new ArrayList<>(); + StockQuote stockQuoteFromDate = new StockQuote(new BigDecimal(100), stockQuery.getFrom().getTime(), stockQuery.getSymbol()); + stockQuotes.add(stockQuoteFromDate); + StockQuote stockQuoteUntilDate = new StockQuote(new BigDecimal(100), stockQuery.getUntil().getTime(), stockQuery.getSymbol()); + stockQuotes.add(stockQuoteUntilDate); + + when(stockServiceMock.getQuote(any(String.class), + any(Calendar.class), + any(Calendar.class), + any(Interval.class))).thenReturn(stockQuotes); + + String output = basicStockQuoteApplication.displayStockQuotes(stockQuery); + assertTrue("make sure symbol appears in output", output.contains(symbol)); + assertTrue("make sure from date appears in output", output.contains(from)); + assertTrue("make sure until date in output", output.contains(until)); + + } + + @Test(expected = NullPointerException.class) + public void testMainNegative() { + BasicStockQuoteApplication.main(null); + } +} diff --git a/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/model/StockQueryTest.java b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/model/StockQueryTest.java new file mode 100644 index 0000000..2294ca4 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/model/StockQueryTest.java @@ -0,0 +1,19 @@ +package com.origamisoftware.teach.advanced.model; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit test for StockQuery Class + */ +public class StockQueryTest { + + @Test + public void testBasicConstruction() throws Exception{ + String symbol = "APPL"; + StockQuery stockQuery = new StockQuery(symbol,"2011-10-29 12:12:1","2014-10-29 12:12:1"); + assertEquals("Verify construction", symbol, stockQuery.getSymbol()); + } + +} diff --git a/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/model/StockQuoteTest.java b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/model/StockQuoteTest.java new file mode 100644 index 0000000..ad47fb1 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/model/StockQuoteTest.java @@ -0,0 +1,44 @@ +package com.origamisoftware.teach.advanced.model; + +import org.junit.Before; +import org.junit.Test; + +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.Date; + +import static org.junit.Assert.assertEquals; + +/** + * JUnit test for StockQuote class + */ +public class StockQuoteTest { + + private BigDecimal price; + private Date date; + private String symbol; + private StockQuote stockQuote; + + @Before + public void setUp() { + price = new BigDecimal(100); + date = Calendar.getInstance().getTime(); + symbol = "APPL"; + stockQuote = new StockQuote(price, date, symbol); + } + + @Test + public void testGetPrice() { + assertEquals("Share price is correct", price, stockQuote.getPrice()); + } + + @Test + public void testGetDate() { + assertEquals("Share date is correct", date, stockQuote.getDate()); + } + + @Test + public void testGetSymbol() { + assertEquals("Symbol is correct", symbol, stockQuote.getSymbol()); + } +} diff --git a/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/services/DatabaseStockServiceTest.java b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/services/DatabaseStockServiceTest.java new file mode 100644 index 0000000..6ba3c94 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/services/DatabaseStockServiceTest.java @@ -0,0 +1,105 @@ +package com.origamisoftware.teach.advanced.services; + +import com.origamisoftware.teach.advanced.model.StockData; +import com.origamisoftware.teach.advanced.model.StockQuote; +import com.origamisoftware.teach.advanced.util.DatabaseInitializationException; +import com.origamisoftware.teach.advanced.util.DatabaseUtils; +import com.origamisoftware.teach.advanced.util.Interval; +import org.junit.Before; +import org.junit.Test; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +/** + * Unit tests for the DatabaseStockService + */ +public class DatabaseStockServiceTest { + + private DatabaseStockService databaseStockService; + + @Before + public void setUp() throws DatabaseInitializationException { + DatabaseUtils.initializeDatabase(DatabaseUtils.initializationFile); + databaseStockService = new DatabaseStockService(); + + } + + @Test + public void testGetQuote() throws Exception { + String symbol = "APPL"; + StockQuote stockQuote = databaseStockService.getQuote(symbol); + assertNotNull("Verify we can get a stock quote from the db", stockQuote); + assertEquals("Make sure the symbols match", symbol, stockQuote.getSymbol()); + } + + @Test + public void testGetQuoteWithIntervalBasic() throws Exception { + String symbol = "GOOG"; + String fromStringDate = "2000-02-10 00:00:01"; + String untilStringDate = "2015-02-03 00:00:01"; + + Calendar fromCalendar = makeCalendarFromString(fromStringDate); + Calendar untilCalendar = makeCalendarFromString(untilStringDate); + + List stockQuotes = databaseStockService.getQuote(symbol, fromCalendar, untilCalendar, Interval.DAY); + assertFalse("verify stock quotes where returned", stockQuotes.isEmpty()); + } + + @Test + public void testGetQuoteWithinRange() throws Exception { + + String fromDateString = "2015-02-10 00:01:01"; + String endDateString = "2015-02-10 00:08:01"; + String symbol = "AMZN"; + + String sql = "INSERT INTO quotes (symbol,time,price) VALUES ('AMZN','" + fromDateString + "','363.21');"; + DatabaseUtils.executeSQL(sql); + + sql = "INSERT INTO quotes (symbol,time,price) VALUES ('AMZN','2015-02-10 00:04:01','250.21');"; + DatabaseUtils.executeSQL(sql); + + sql = "INSERT INTO quotes (symbol,time,price) VALUES ('AMZN','2015-02-10 00:06:01','251.21');"; + DatabaseUtils.executeSQL(sql); + + sql = "INSERT INTO quotes (symbol,time,price) VALUES ('AMZN','" + endDateString + "','253.21');"; + DatabaseUtils.executeSQL(sql); + + Calendar fromCalendar = makeCalendarFromString(fromDateString); + Calendar untilCalendar = makeCalendarFromString(endDateString); + + List stockQuotes = databaseStockService.getQuote(symbol, fromCalendar, untilCalendar, Interval.DAY); + assertEquals("got back expected number of stockquotes for one day interval", 1, stockQuotes.size()); + + stockQuotes = databaseStockService.getQuote(symbol, fromCalendar, untilCalendar, Interval.MINUTE); + assertEquals("got back expected number of stockquotes for one minute interval", 4, stockQuotes.size()); + } + + /** + * Handy dandy helper method that converts Strings in the format of StockData.dateFormat + * to Calendar instances set to the date expressed in the string. + * + * @param dateString a data and time in this format: StockData.dateFormat + * @return a calendar instance set to the time in the string. + * @throws ParseException if the string is not in the correct format, we can't tell what + * time it is, and therefore can't make a calender set to that time. + */ + private Calendar makeCalendarFromString(String dateString) throws ParseException { + DateFormat format = new SimpleDateFormat(StockData.dateFormat, Locale.ENGLISH); + Date date = format.parse(dateString); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(date); + return calendar; + + } + +} diff --git a/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/services/StockServiceFactoryTest.java b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/services/StockServiceFactoryTest.java new file mode 100644 index 0000000..fd605e9 --- /dev/null +++ b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/services/StockServiceFactoryTest.java @@ -0,0 +1,17 @@ +package com.origamisoftware.teach.advanced.services; + +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; + +/** + * JUnit test for StockServiceFactory + */ +public class StockServiceFactoryTest { + + @Test + public void testGetInstance() { + StockService stockService = StockServiceFactory.getInstance(); + assertNotNull(stockService); + } +} diff --git a/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/util/DatabaseUtilsTest.java b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/util/DatabaseUtilsTest.java new file mode 100644 index 0000000..3e2dc9d --- /dev/null +++ b/WK6/stockquote-week5-solved/src/test/java/com/origamisoftware/teach/advanced/util/DatabaseUtilsTest.java @@ -0,0 +1,40 @@ +package com.origamisoftware.teach.advanced.util; + +import org.junit.Test; + +import java.sql.Connection; +import java.sql.Statement; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Tests for the DatabaseUtils class + */ +public class DatabaseUtilsTest { + + @Test + public void testGoodInitFile() throws Exception { + DatabaseUtils.initializeDatabase(DatabaseUtils.initializationFile); + } + + @Test(expected = DatabaseInitializationException.class) + public void testBadInitFile() throws Exception { + DatabaseUtils.initializeDatabase("bogus"); + } + + @Test + public void testGetConnection() throws Exception{ + Connection connection = DatabaseUtils.getConnection(); + assertNotNull("verify that we can get a connection ok",connection); + } + + @Test + public void testGetConnectionWorks() throws Exception{ + Connection connection = DatabaseUtils.getConnection(); + Statement statement = connection.createStatement(); + boolean execute = statement.execute("select * from quotes"); + assertTrue("verify that we can execute a statement",execute); + } + +} diff --git a/WK6/stockquote-week5-solved/week-5-stock-ticker.iml b/WK6/stockquote-week5-solved/week-5-stock-ticker.iml new file mode 100644 index 0000000..e61a793 --- /dev/null +++ b/WK6/stockquote-week5-solved/week-5-stock-ticker.iml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file