CS636 Class 12. domain classes in music1, Getting started on Project 1, HTTP

Questions on hw3, part 2? Register.java

Note that moving it to a different package just needs one line edit.  But running it means using its long name.


Last time: Using transactions in a  database-backed program (pizza3, music3, etc. Also via JPA in pizza2/music2):

The Tricky Part with  JDBC: Cleanly aborting a transaction

Some DB problem --> SQL exception

Ex. Insert fails. The DB changes we’ve made are now in an indeterminate state

So we have to do a rollback to get to a determined DB state

Further complication: JDBC rollback() can throw SQLException

Suppose something (like an insert) has thrown a SQLException e1.  We try a rollback and it throws SQLException e2.

What to do with e2???  It’s usually a lost connection. So we’ll discard e2 and preserve e1, hopefully a more useful indication of the real problem.  A rare case of justifiable exception-squelching.  If we had a log, we could print its message there.

We looked at Transfer.java, now available in $cs636/jdbc along with JdbcCheckup.java.  This program turns off auto-commit, sets serializable isolation, and handles transfers between accounts. Note that the method doCustomerTransfers has presentation code, the method doTransfer works like a service-layer method, defining when transactions begin and end, and calling transfer to do the actual JDBC actions (other than commit and rollback), so transfer is like a DAO method.

Rollbacks are handled with rollbackAfterException(Connection conn)which does the "extra" try-catch needed to handle possible throws by rollback:

// The caller should issue its own exception based on the
    // original exception (or do retry)
    public static void rollbackAfterException(Connection conn) {
        try {
            conn.rollback();
        } catch (Exception e) {
            // discard secondary exception--probably server can't be reached
        }
    }

A similar method is used in pizza2 and pizza3, in their DAOs.

Note that money is represented by double here, no a good practice.

Here is another snippet, showing what we will be doing with a DAO-generated Exception: create and throw a service-layer exception object whenever the DAO throws up to this code.

// starting with good Connection conn, in service-layer code

          try {
            dao.doDBWork(conn); // this DAO method can throw SQLException     
          }
          catch (SQLException e) {        
            dao.rollbackAfterException(conn); 
            throw new ServiceException(“DBWork failed”, e);
          } finally {
             conn.close();
          }
In pizza2, we will be using JPA, but we still have the problem that rollback() can throw. To make the code look better than the above, we use the rollback-handling code in a similar “rollbackAfterException”. This gets called from various catch clauses. 

In pizza1, the DAO uses JDBC directly, so the DAO code throws SQLException when there’s a DB problem of any kind.

See pattern in service layer (aka business layer BL) in pizza1/pizza3:  catch SQLException, throw Service Exception

Here, as in pizza1, e is created by new ServiceException( “ useful message”, e0), where e0 is the SQLException

That means the ServiceException has the original SQLException attached to it as its "cause".

End up with a Service Exception, with cause e0, in the presentation layer code that called the service method that failed

At that point, in the presentation layer, we can get e0, the SQLException, from e, the ServiceException, by e0 = e.getCause();

Note that getCause() is a method of Exception, so this is a general mechanism to retrieve the exception provided in the constructor of the Exception.

The presentation code can print out the useful message to the user, and, while we’re developing, the more internal SQL problem explained in e0’s message.

See method exceptionReport(Exception e) in PizzaSystemConfig: it reports on a ServiceException and its cause, the SQLException, if any.

Getting started on Project 1, Tour of provided sources

domain class objects of music1: lack of equals/hashCode/compareTo.

As provided, the music project domain classes have no equals/hashCode/compareTo methods, and thus rely on Object equals and hashCode.  These are based on the value of the ref to an object, i.e., the memory address of the object in the JVM, a number that is unique to the object.  So if refA == refB, it’s the same object being ref’d.

The default equals and hashCode are consistent, so that if a and b are equals, they have the same hashCodes.  That’s what HashSet and HashMap need for their proper working.

If you overload equals or hashCode, you must overload the other one consistently, or HashSet/HashMap may fail, and anything else that uses both equals and hashCode.

Moral : it’s absolutely fine to leave equals and hashCode unimplemented in almost  any class. If you feel the urge to overload one, proceed cautiously!

If you want to use TreeSet<domainclass>, you need to make the domain class implement Comparable<domainclass>, and thus overload compareTo.  But you must also overload equals consistently, and because of that, overload hashCode consistently too.  See Topping.java for an example. In Topping, it’s nice to use a TreeSet<Topping> with equals based on the string name so that toppings are listed in alphabetic order. 

Another pointer : use @Overload when you do overload these methods, to make sure you’re actually overloading what you think you are. It’s really easy to miss the mark.

DAO: DbDAO, AdminDAO, DownloadDAO, InvoiceDAO, LineItemDAO: you need to add UserDAO, ProductDAO (and TrackDAO, or handle Tracks in ProductDAO)
Service: Just ServiceException, AdminService, you need to add UserService
Presentation: UserApp, SystemTest: mostly written, you add service calls
Config: MusicSystemConfig: mostly written, uncomment as progress

In DAO, needed next: UserDAO.java, with needed support for registering a user.

For UserDAO class setup, look at InvoiceDAO: package, import, private Connection connection, constructor

Can set UserDAO up with no methods, similarly UserService, build little object graph

Goal 1. Write class-level code for UserService and UserDAO, uncomment them in MusicSystemConfig, run SystemTest without crash.
Problem: are you sure they are really up and working?

How to check that: Write stubs for methods that just print out when they are called:
UserDAO
void insertUser(String email) throws SQLException
{
    System.out.println("insertUser called, email = "+email);
}

UserService
void registerUser(String email) throws ServiceException
{
     System.out.println("registerUser called, email = " +email);
     userDAO.insertUser(email);
}

SystemTest:  call registerUser now for ureg

Goal 2.
SystemTest prints above messages. Now we know we're in business!

Real Code for DAO, Service
What we need: insert a new user. DAO should be generic support as far as possible, so suggest insertUser or createUser
But note that somewhere we need to check if a certain user is already there.  The emails are supposed to be unique, so that's a good thing to check.
So need findUser, given email, plus insertUser and let caller check

You need to do an insert, as you must have done in Register.java, but now we need to use real values and a good PK.
New PK: use col in music_sys_tab:
create table music_sys_tab (
                invoice_id integer not null,   <--see code for using this in InvoiceDAO.java
                user_id integer not null,    <---next user id
                download_id integer not null,
                lineitem_id integer not null);
Want portable id generation (no auto-increment!)

What we want to happen in the DB:
select user_id from music_sys_tab; 
update music_sys_tab set user_id = user_id + 1;

Looking at InvoiceDAO, see separate method for this part, so here

int findNextUserId () {...}

insertUser, two ways

insertUser(User user) throws SQLException;
or insertUser(String firstName, String lastName, ...) throws SQLException;  

void insertUser(User user)throws SQLException;
--look at insertInvoice in InvoiceDAO

Need finder too

User findUser (String email) throws SQLException;

Service Layer: here have more app-specific names
UserService.java in package service

void registerUser(String firstName, String lastName, ...) throws ServiceException
call findUser, then insertUser if needed

Goal 3: SystemTest shows expected output (shouldn't show stub messages), and ant show-oradb shows new users

HTTP: read Chap. 18 to pg. 555.

Look at slides for Chap. 18.

Note error in book, pg. 549 : The « host » request header specifies the server name in request URL, i.e., the recipient’s hostname, not the sender’s hostname.

HTTP example of webpage with an image

MusicProjectUI.html  has:

 <img alt="music project page flow" src="MusicProjectUI_files/music_page_flow2.jpg"
        height="600px" width="750">

Browser accesses URL: http://www.cs.umb.edu, using 2 steps:

1. Connects to host www.cs.umb.edu on Port: 80 (default port for http)

2. Uses HTTP Command: GET /cs636/MusicProjectUI.html  HTTP/1.1 over that TCP connection, followed by header lines, then blank line to end

(as shown in Murach, pg. 545. Note that the blank line at the end for the GET doesn’t really show.)

Gets response:

Status line: HTTP/1.0 200 OK.
Response headers
blank line after headers
<Contents of index.html>

Then the connection is closed.

Browser parses the HTML

Finds <img….>

Browser reconnects to www.cs.umb.edu on port 80   (at least effectively, in fact this reconnect is optimized away.)

GET  /cs636/MusicProjectUI_files/music_page_flow2.jpg  HTTP/1.1
<headers>

Response:

HTTP/1.0 200 O.K
headers

<image data>

Browser display the image