DocumentDescription Encapsulation and Sending Objects

the request or data. In older references, marshalling is sometimes referred to as pickling, and demarshalling is then called unpickling. No one really seems to know why or why the names changed.

3.3.1 Encapsulation and Sending Objects

The first step in solving this problem is figuring out how to send our objects, DocumentDescription and PrinterException , over the wire. There are two basic design options for doing this: The internal approach In this approach, the objects know how to push their state into, and read their state out of, a stream. That is, if you want to send an instance of DocumentDescription over a stream, call a method named something similar to writeToStream , and the instance writes itself out. The external approach In this approach, there is a third, external object that knows about both the object you want to send over the socket and the stream classes. This third object knows how to encode the object and put the relevant information into the stream. These approaches both have their good points. With the internal approach, data can be totally encapsulated within an object and still have the knowledge to send itself over the wire. Letting the object do the encoding and decoding makes the internal approach a more object-oriented way of doing things. In addition, the internal approach simplifies maintenance; colocating the marshalling code with the object to be marshalled makes it easier to keep the two synchronized or to tell when theyre out of synchronization someone whos changing the object can easily change the marshalling code at the same time. On the other hand, the external approach allows you to have more than one marshalling routine for a given object and to gracefully choose which protocol to use based on circumstances. The external approach also allows you to put all the marshalling code in one place, which makes the actual protocol easier to understand and improve upon. Note that the difference between these two approaches is not so much the code thats written™in either approach you still need to marshall the object™but where the marshalling code is placed within the application. In our case, weve chosen to use the internal approach.

3.3.1.1 DocumentDescription

Exam ple 3- 1 shows the source for DocumentDescription , including the marshalling code. Example 3-1. DocumentDescription.java public class DocumentDescription { public static final int FAST_PRINTING = 0; public static final int HIGH_QUALITY_PRINTING = 1; public static final int POSTSCRIPT = 0; public static final int PDF = 1; private DataInputStream _actualDocument; private int _documentType; private boolean _printTwoSided; private int _printQuality; private int _length; public DocumentDescriptionInputStream source throws IOException { readFromStreamsource; } public DocumentDescriptionInputStream actualDocument, int documentType, boolean printTwoSided, int printQuality throws IOException { _documentType = documentType; _printTwoSided = printTwoSided; _printQuality = printQuality; BufferedInputStream buffer = new BufferedInputStreamactualDocument; DataInputStream dataInputStream = new DataInputStreambuffer; ByteArrayOutputStream temporaryBuffer = new ByteArrayOutputStream ; _length = copydataInputStream, new DataOutputStreamtemporaryBuffer; _actualDocument = new DataInputStreamnew ByteArrayInputStreamtemporaryBuffer toByteArray ; } public DocumentDescriptionInputStream actualDocument, int documentType, boolean printTwoSided, int printQuality, int length { _actualDocument = new DataInputStreamactualDocument; _documentType = documentType; _printTwoSided = printTwoSided; _printQuality = printQuality; _length = length; } public int getLength { return _length; } public int getDocumentType { return _documentType; } public boolean isPrintTwoSided { return _printTwoSided; } public int getPrintQuality { return _printQuality; } public void writeToStreamOutputStream outputStream throws IOException { BufferedOutputStream buffer = new BufferedOutputStreamoutputStream; DataOutputStream dataOutputStream = new DataOutputStreambuffer; writeMetadataToStreamdataOutputStream; copy_actualDocument, dataOutputStream, _length; } public void readFromStreamInputStream inputStream throws IOException { BufferedInputStream buffer = new BufferedInputStreaminputStream; DataInputStream dataInputStream = new DataInputStreambuffer; readMetadataFromStreamdataInputStream; ByteArrayOutputStream temporaryBuffer = new ByteArrayOutputStream ; copydataInputStream, new DataOutputStreamtemporaryBuffer, _length; _actualDocument = new DataInputStreamnew ByteArrayInputStreamtemporaryBuffer toByteArray ; } private void writeMetadataToStreamDataOutputStream dataOutputStream throws IOException { dataOutputStream.writeInt_documentType; dataOutputStream.writeBoolean_printTwoSided; dataOutputStream.writeInt_printQuality; dataOutputStream.writeInt_length; } private void readMetadataFromStreamDataInputStream dataInputStream throws IOException { _documentType = dataInputStream.readInt ; _printTwoSided = dataInputStream.readBoolean ; _printQuality = dataInputStream.readInt ; _length = dataInputStream.readInt ; } private void copyInputStream source, Ou tputStream destination, int length throws IOException { int counter; int nextByte; for counter = 0; counter length; counter++ { nextByte = source.read ; destination.writenextByte; } destination.flush ; } private int copyInputStream source, OutputStream destination throws IOException { int nextByte; int numberOfBytesCopied = 0; while-1= nextByte = source.read { destination.writenextByte; numberOfBytesCopied++; } destination.flush ; return numberOfBytesCopied; } } The careful eye will note that metadata has once again crept into the picture. Namely, when DocumentDescription s writeToStream method is called, five pieces of information are sent: • The document type • Whether the print request is for a two-sided printout • What quality printing is desired • The length of the document • The actual document Sending the documents length along with the document is redundant. After all, if you have a copy of the document, you can compute the length of the document. However, sending the length helps out in demarshalling. For instance, the demarshalling code in the readFromStream method assumes that the stream contains: 1. An integer 2. A boolean 3. An integer 4. An integer 5. A number of bytes totaling the third integer. Put succinctlly, the demarshalling code relies on the metadata to help it know when it should stop reading from the stream. Of course, the overwhelming lesson of actually implementing or even just reading marshalling code is that it is boilerplate code. Once you have sockets and streams, marshalling is a snap. PrinterException is implemented in much the same way as DocumentDescription . Spooling Note that the client program here uses an extravagant amount of memory. In order to compute the length of the document, we make an in - memory copy of the entire document before we send it through the socket. If the document is 38 MB rather typical for a Powerpo int presentation these days, we make a 38 MB buffer and create an in - memory copy of the entire file there. A much better way to do this is to send the document in smaller pieces, each of which is preceded by an integer indicating the length of the following piece. That is, instead of sending the length of the document followed by the document, we can send the length of the first chunk followed by the first chunk, then the length of the second chunk followed by the second chunk. Conceptually, this is a lot like breaking the document into a linked list of content nodes. For example, suppose the document is 18,012 bytes, and we decide to send 1000-byte pieces. The information sent to the stream might be: 1000 , followed by the first set of a thousand bytes 1000 , followed by the second set of a thousand bytes .... 1000 , followed by the eighteenth set of a thousand bytes 12 , followed by the last twelve bytes Doing things this way uses slightly more bandwidth, but it also allows us to send arbitrarily large documents without exhausting the memory available on the client machine. This works because at any given time, we need to have only a small percentage of the file in memory. The downside to doing things this way is that we might run into an unexpected disk failure in the middle of our print request. The current implementation reads the entire file and then sends the request. The spooling implementation interweaves these two tasks. If the file suddenly becomes unavailable for example, if the file server gets rebooted, our application is going to have a problem. In practice, this means our printing protocol needs to be a little bit more complicated. After each chunk of content, we need to also include a status code: 1000 , followed by the first set of thousand bytes, followed by STATUS_OKAY 1000 , followed by the second set of thousand bytes, followed by STATUS_OKAY .... 1000 , followed by the eighteenth set of a thousand bytes, followed by STATUS_OKAY 12 , followed by the last twelve bytes, followed by STATUS_DONE

3.3.2 Network-Aware Wrapper Objects