Commit 32f686cf authored by Arne Styve's avatar Arne Styve
Browse files

Merge branch 'release/v0.5'

parents 757036f6 0f33aa24
# Default ignored files
/workspace.xml
/out/
addressbook.dat
.idea/sonarlint
/.idea/sonarlint
/dataSources/
/dataSources.local.xml
/vpproject/
/Contacts.vpdm/
/derby.log
/contactsdb/
\ No newline at end of file
/contactsdb/
/doc/
/contacts.log
/addressbook.dat
# Default ignored files
/workspace.xml
\ No newline at end of file
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
......@@ -7,9 +7,6 @@
<entry key="copy-libs" value="false" />
<entry key="location-0" value="BUNDLED:(bundled):Sun Checks" />
<entry key="location-1" value="BUNDLED:(bundled):Google Checks" />
<entry key="location-2" value="LOCAL_FILE:/Users/Shared/Dropbox/NTNU/Undervisning/IDATA2001 Programmering 2/CheckStyle files/idata2001_checks.xml:IDATA2001 Checks" />
<entry key="property-2.org.checkstyle.google.suppressionfilter.config" value="" />
<entry key="property-2.org.checkstyle.google.suppressionxpathfilter.config" value="" />
<entry key="scan-before-checkin" value="false" />
<entry key="scanscope" value="JavaOnly" />
<entry key="suppress-errors" value="false" />
......
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Apache Derby (Embedded) -" uuid="6f00763e-3a69-4367-8591-27393851cd60">
<driver-ref>derby.embedded</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.apache.derby.jdbc.EmbeddedDriver</jdbc-driver>
</data-source>
<data-source source="LOCAL" name="Apache Derby (Remote) - jdbc:derby://localhost:1527/contactsdb" uuid="519b1631-2014-4273-9a73-dc67a189ce14">
<driver-ref>derby.remote</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.apache.derby.jdbc.ClientDriver</jdbc-driver>
<jdbc-url>jdbc:derby://localhost:1527/contactsdb</jdbc-url>
</data-source>
<data-source source="LOCAL" name="MySQL@IDI" uuid="01d24ffe-dde5-4cf7-80ed-2a4f158faf6c">
<driver-ref>mysql.8</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://mysql-ait.stud.idi.ntnu.no/asty</jdbc-url>
</data-source>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FrameworkDetectionExcludesConfiguration" detection-enabled="false" />
<component name="JavadocGenerationManager">
<option name="OUTPUT_DIRECTORY" value="$PROJECT_DIR$/doc" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8.0_201" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
......
......@@ -5,12 +5,24 @@
<map />
</option>
</component>
<component name="FacetManager">
<facet type="jpa" name="JPA">
<configuration>
<setting name="validation-enabled" value="true" />
<datasource-mapping>
<factory-entry name="contacts-pu" />
</datasource-mapping>
<naming-strategy-map />
<deploymentDescriptor name="persistence.xml" url="file://$MODULE_DIR$/src/META-INF/persistence.xml" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="1.8.0_201" jdkType="JavaSDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="libs" level="project" />
</component>
......
......@@ -12,6 +12,7 @@ The project was developed for use in teaching in the course "IDATx2001 Programme
**Version** | **Description**
--------|------------
v0.5 | Added support for MySQL-DB at IDI (https://mysql-ait.stud.idi.ntnu.no/phpmyadmin/) through a separate *persistence unit (PU)* in the persistence.xml-file. NOTE: You must set your own **username**, **password** and **database name**. Also support has been added for a locally installed Apache Derby Server.
v0.4.1 | Renamed the class AddressBookDAO to AddressBookDBHandler, since DAO is a general term that also could be used for the AddressBookFileHandler. Also altered slightly the use of EntityManager.
v0.4 | Added Relational Database support, using the embedded Apache Derby server. For details of the changes made, se below.
v0.3 | Adds object serialization of the entire address book.
......@@ -39,8 +40,7 @@ is the same folder that the **MANIFEST.MF** should be.
## Description of the project
The project is made to demonstrate a typical application with a graphical user interface (GUI) implemented in JavaFX.
It's a classic address book example, where you can create contacts to be added to the address book, edit existing
contacts, and delete contacts.
It's a classic address book example, where you can create contacts to be added to the address book, edit existing contacts, and delete contacts.
The project does **not** make use of FXML and SceneBuilder, but builds the GUI from within the Java code.
#### JavaFX concepts demonstrated in the project
......
......@@ -2,6 +2,14 @@
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<!--
The persistence unit (pu) to be used to connect to a Derby Embedded database. En embedded database
is really just a file-system, not a running server, but you interact with the embedded database
in the same way you would a RDB-server.
Note that using an embedded database should only be used when there never is more than one
application (client) accessing the database.
-->
<persistence-unit name="contacts-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
......@@ -17,7 +25,7 @@
<!-- Alternatives: create-tables, drop-and-create-tables-->
<property name="eclipselink.ddl-generation" value="create-tables"/>
<!-- Alternatives: FINE (logs all SQL), ALL, CONFIG, INFO, WARNING..., OFF -->
<property name="eclipselink.logging.level" value="FINE"/>
<property name="eclipselink.logging.level" value="OFF"/>
<!--
The Database can be pre-filled with entries during startup. This would be very useful during testing
......@@ -30,4 +38,62 @@
</properties>
</persistence-unit>
<!--
This persistence unit connects to a locally installed Derby Database server. To use this, we need to add
the derbyclient.jar to the path (the libs-folder).
Also, the DB-server must have been started (from a terminal window using "startNetworkServer").
-->
<persistence-unit name="contacts-localdb-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.target-database" value="Derby"/>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/contactsdb;create=true"/>
<property name="javax.persistence.jdbc.user" value="app"/>
<property name="javax.persistence.jdbc.password" value="app"/>
<!-- Alternatives: create-tables, drop-and-create-tables-->
<property name="eclipselink.ddl-generation" value="create-tables"/>
<!-- Alternatives: FINE (logs all SQL), ALL, CONFIG, INFO, WARNING..., OFF -->
<property name="eclipselink.logging.level" value="INFO"/>
<!--
The Database can be pre-filled with entries during startup. This would be very useful during testing
of the application, used in conjunction with "drop-and-create-tables". The SQL-statements for inserting
entries in the DB-table can be stored in a text-file. The line below, when un-commented, will
read SQL-statements from the file "META-INF/sql/data.sql" during application startup.
-->
<!--property name="javax.persistence.sql-load-script-source" value="META-INF/sql/data.sql"/-->
</properties>
</persistence-unit>
<!--
This persistence-unit connect to the MySQL-server installed at IDI in Trondheim. Prior to connecting,
you must contact stab-tk@idi.ntnu.no to have a user account and a database setup for you.
The URL of the server is: mysql-ait.stud.idi.ntnu.no
-->
<persistence-unit name="contacts-mysql-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>no.ntnu.idata2001.contacts.model.ContactDetails</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://mysql-ait.stud.idi.ntnu.no/asty" />
<property name="javax.persistence.jdbc.user" value="asty" />
<property name="javax.persistence.jdbc.password" value="SOME_PASSWORD" />
<property name="eclipselink.target-database" value="MySQL"/>
<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.ddl-generation.output-mode" value="database" />
<property name="eclipselink.logging.level" value="INFO"/>
</properties>
</persistence-unit>
</persistence>
\ No newline at end of file
......@@ -4,6 +4,10 @@ import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
/**
* Defines the interface to an address book. Provides methods for adding contacts,
* deleting contacts and iterating over contacts.
*/
public interface AddressBook extends Serializable, Iterable<ContactDetails> {
/**
......
......@@ -3,6 +3,8 @@ package no.ntnu.idata2001.contacts.model;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
......@@ -10,22 +12,22 @@ import javax.persistence.Query;
/**
* An addressbook holding contacts, stored in a relational database, implemented using
* <p>An addressbook holding contacts, stored in a relational database, implemented using
* the JPA implementation EclipseLink.
* Using the JPA standard for Object Relational Mapping (ORM) removes database specifics
* from the Java code, and hides the underlying RDB solution, making it easy to change between
* different suppliers of Relational Database management Systems (RDBMS), and between locally
* embedded database and server based database systems.
* JPA makes use of <b>EntityManager</b> (EMF) and <b>EntityManagerFactory</b> (EM) classes for
* communication with the underlying RDBMS. Creating an EntityManagerFactory instance is costly:
* </p>
* (<a href="https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html_single/#d0e980">
* "An entity manager factory is typically create at application initialization time and closed at
* * application end. It's creation is an expensive process."</a>
* </p>
* Hence the EMF is implemented as a field in this class.
* communication with the underlying RDBMS. Creating an EntityManagerFactory instance is costly:</p>
*
* <b>NOTE:</b> While the EMF is <b>thread safe</b>, the EM is <b>not thread safe</b>.
* <p>(<a href="https://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html_single/#d0e980">)
* "An entity manager factory is typically create at application initialization time and closed at
* * application end. It's creation is an expensive process."</a></p>
* <p>
* Hence the EMF is implemented as a field in this class.
* </p>
* <p><b>NOTE:</b> While the EMF is <b>thread safe</b>, the EM is <b>not thread safe</b>.</p>
*/
public class AddressBookDBHandler implements AddressBook {
......@@ -36,11 +38,17 @@ public class AddressBookDBHandler implements AddressBook {
// communicate with the database, and should hence not be serialized.
private final transient EntityManagerFactory efact;
// Create a logger to be used within this class
private final transient Logger logger = Logger.getLogger(this.getClass().getName());
/**
* Creates an instance of the AddressBookDBHandler.
*/
public AddressBookDBHandler() {
this.efact = Persistence.createEntityManagerFactory("contacts-pu");
// this.efact = Persistence.createEntityManagerFactory("contacts-mysql-pu");
// this.efact = Persistence.createEntityManagerFactory("contacts-localdb-pu");
}
@Override
......@@ -50,26 +58,55 @@ public class AddressBookDBHandler implements AddressBook {
eman.persist(contact);
eman.getTransaction().commit();
eman.close();
this.logger.log(Level.INFO, () ->
"A contact was added to the DB. Name = "
+ contact.getName() + " Phone = "
+ contact.getPhone());
}
@Override
public void removeContact(String phoneNumber) {
//TODO: To be implemented later...
EntityManager eman = this.efact.createEntityManager();
eman.getTransaction().begin();
String sql = "DELETE FROM ContactDetails c WHERE c.phone LIKE :contactPhone";
Query query = eman.createQuery(sql).setParameter("contactPhone", phoneNumber);
int n = query.executeUpdate();
eman.getTransaction().commit();
// A quick note on this way of logging a message where we need to use
// string concatenation: String concatenation is "expensive". Hence we should
// avoid having to build the string if it is not going to be used, i.e. if the
// logging level is set to lower than INFO, this log-message will never be logged
// even though the string concatenation always will been performed.
// One solution, is to use lambda like shown below. This way we ensure that the lambda
// expression will not be executed unless the logging level is set to INFO or higher.
// Another alternative is to use a formatter.
this.logger.log(Level.INFO, () -> "Removed contact with phone = "
+ phoneNumber + ". EM returned " + n + " object deleted.");
eman.close();
}
@Override
public Collection<ContactDetails> getAllContacts() {
EntityManager eman = this.efact.createEntityManager();
List<ContactDetails> contactsList = null;
String sql = "SELECT c FROM ContactDetails c";
Query query = eman.createQuery(sql);
contactsList = query.getResultList();
List<ContactDetails> contactsList = query.getResultList();
eman.close();
this.logger.log(Level.INFO, () ->
"Read all contacts from the DB, a total of " + contactsList.size());
return contactsList;
}
......
......@@ -7,11 +7,11 @@ import java.util.TreeMap;
/**
* Represents an Address book containing contacts with contact details.
* Based on the example in the book "Objects first with Java" by David J. Barnes
* and Michael Kölling.
* and Michael K&ouml;lling.
*
* <p>Each contact is stored in a TreeMap using the phone number as the key.
*
* @author David J. Barnes and Michael Kölling and Arne Styve
* @author David J. Barnes and Michael K&ouml;lling and Arne Styve
* @version 2020.03.16
*/
public class AddressBookPlain implements AddressBook {
......@@ -23,13 +23,13 @@ public class AddressBookPlain implements AddressBook {
// TreeMap is a bit less efficient than a HashMap in terms of searching, du to the
// sorted order. For more details on the difference:
// https://javatutorial.net/difference-between-hashmap-and-treemap-in-java
private TreeMap<String, ContactDetails> book;
private final TreeMap<String, ContactDetails> book;
/**
* Creates an instance of the AddressBook, initialising the instance.
*/
public AddressBookPlain() {
book = new TreeMap<>();
this.book = new TreeMap<>();
}
/**
......@@ -64,7 +64,7 @@ public class AddressBookPlain implements AddressBook {
@Override
public void close() {
// Left empty intentionally. Nothing to close in this class.
}
@Override
......
......@@ -8,9 +8,9 @@ import javax.persistence.Id;
/**
* Holds details about a contact, like name, address and phone number.
* Based on the example in the book "Objects first with Java" by David J. Barnes
* and Michael Kölling.
* and Michael K&ouml;lling.
*
* @author David J. Barnes and Michael Kölling and Arne Styve
* @author David J. Barnes and Michael K&ouml;lling and Arne Styve
* @version 2020.03.16
*/
@Entity
......
package no.ntnu.idata2001.contacts.views;
import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
......@@ -32,7 +37,7 @@ import no.ntnu.idata2001.contacts.model.ContactDetails;
*/
public class ContactsApp extends Application {
private static final String VERSION = "0.4.2";
private static final String VERSION = "0.5";
private MainController mainController;
private AddressBook addressBook;
......@@ -50,6 +55,15 @@ public class ContactsApp extends Application {
* @param args command line arguments provided during startup. Not used in this app.
*/
public static void main(String[] args) {
// Setup logging to file, and the level to FINEST
try {
Handler fh = new FileHandler("./contacts.log");
Logger.getLogger("no.ntnu.idata2001.contacts").addHandler(fh);
Logger.getLogger("no.ntnu.idata2001.contacts").setLevel(Level.FINEST);
} catch (IOException e) {
Logger.getLogger(ContactsApp.class.getName()).log(Level.SEVERE,
"Could not create a log-file.");
}
launch(args);
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment