Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: gnu/javax/print/ipp/IppRequest.java


1   /* IppRequest.java -- 
2    Copyright (C) 2006 Free Software Foundation, Inc.
3   
4    This file is part of GNU Classpath.
5   
6    GNU Classpath is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2, or (at your option)
9    any later version.
10  
11   GNU Classpath is distributed in the hope that it will be useful, but
12   WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   General Public License for more details.
15  
16   You should have received a copy of the GNU General Public License
17   along with GNU Classpath; see the file COPYING.  If not, write to the
18   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19   02110-1301 USA.
20  
21   Linking this library statically or dynamically with other modules is
22   making a combined work based on this library.  Thus, the terms and
23   conditions of the GNU General Public License cover the whole
24   combination.
25  
26   As a special exception, the copyright holders of this library give you
27   permission to link this library with independent modules to produce an
28   executable, regardless of the license terms of these independent
29   modules, and to copy and distribute the resulting executable under
30   terms of your choice, provided that you also meet, for each linked
31   independent module, the terms and conditions of the license of that
32   module.  An independent module is a module which is not derived from
33   or based on this library.  If you modify this library, you may extend
34   this exception to your version of the library, but you are not
35   obligated to do so.  If you do not wish to do so, delete this
36   exception statement from your version. */
37  
38  
39  package gnu.javax.print.ipp;
40  
41  import gnu.classpath.debug.Component;
42  import gnu.classpath.debug.SystemLogger;
43  import gnu.javax.print.ipp.attribute.CharsetSyntax;
44  import gnu.javax.print.ipp.attribute.NaturalLanguageSyntax;
45  import gnu.javax.print.ipp.attribute.RequestedAttributes;
46  import gnu.javax.print.ipp.attribute.job.AttributesCharset;
47  import gnu.javax.print.ipp.attribute.job.AttributesNaturalLanguage;
48  import gnu.javax.print.ipp.attribute.job.JobId;
49  import gnu.javax.print.ipp.attribute.job.JobUri;
50  import gnu.javax.print.ipp.attribute.printer.DocumentFormat;
51  
52  import java.io.DataOutputStream;
53  import java.io.IOException;
54  import java.io.InputStream;
55  import java.io.OutputStream;
56  import java.net.HttpURLConnection;
57  import java.net.URI;
58  import java.net.URL;
59  import java.util.Calendar;
60  import java.util.Date;
61  import java.util.GregorianCalendar;
62  import java.util.List;
63  import java.util.logging.Logger;
64  
65  import javax.print.attribute.Attribute;
66  import javax.print.attribute.AttributeSet;
67  import javax.print.attribute.DateTimeSyntax;
68  import javax.print.attribute.EnumSyntax;
69  import javax.print.attribute.HashAttributeSet;
70  import javax.print.attribute.IntegerSyntax;
71  import javax.print.attribute.ResolutionSyntax;
72  import javax.print.attribute.SetOfIntegerSyntax;
73  import javax.print.attribute.TextSyntax;
74  import javax.print.attribute.URISyntax;
75  import javax.print.attribute.standard.Compression;
76  import javax.print.attribute.standard.Copies;
77  import javax.print.attribute.standard.DocumentName;
78  import javax.print.attribute.standard.Fidelity;
79  import javax.print.attribute.standard.Finishings;
80  import javax.print.attribute.standard.JobHoldUntil;
81  import javax.print.attribute.standard.JobImpressions;
82  import javax.print.attribute.standard.JobKOctets;
83  import javax.print.attribute.standard.JobMediaSheets;
84  import javax.print.attribute.standard.JobName;
85  import javax.print.attribute.standard.JobOriginatingUserName;
86  import javax.print.attribute.standard.JobPriority;
87  import javax.print.attribute.standard.JobSheets;
88  import javax.print.attribute.standard.Media;
89  import javax.print.attribute.standard.MultipleDocumentHandling;
90  import javax.print.attribute.standard.NumberUp;
91  import javax.print.attribute.standard.OrientationRequested;
92  import javax.print.attribute.standard.PageRanges;
93  import javax.print.attribute.standard.PrintQuality;
94  import javax.print.attribute.standard.PrinterResolution;
95  import javax.print.attribute.standard.PrinterURI;
96  import javax.print.attribute.standard.RequestingUserName;
97  import javax.print.attribute.standard.SheetCollate;
98  import javax.print.attribute.standard.Sides;
99  
100 /**
101  * <code>IppRequest</code> models a request to an IPP compatible
102  * server as described in RFC 2910 - IPP/1.1: Encoding and Transport.
103  * <p>
104  * The byte stream is structured as follows (for an official description
105  * please have a look at the RFC document mentioned above):
106  * <ul>
107  * <li>version-number          - 2 bytes - required</li>
108  * <li>operation-id            - 2 bytes - required</li>
109  * <li>request-id              - 4 bytes - required</li>
110  * <li>attribute-group         - n bytes - 0 or more</li>
111  * <li>end-of-attributes-tag   - 1 byte  - required</li>
112  * <li>data                    - q bytes - optional</li>
113  * </ul>
114  * </p>
115  * 
116  * @author Wolfgang Baer (WBaer@gmx.de)
117  */
118 public class IppRequest
119 {
120 
121   /**
122    * Helper class used to write the attributes of a request
123    * into the supplied data output stream in the correct way.
124    * 
125    * @author Wolfgang Baer (WBaer@gmx.de)
126    */
127   class RequestWriter
128   {    
129     private DataOutputStream out;
130     
131     /**
132      * Creates a RequestWriter.
133      * 
134      * @param stream the stream to write to.
135      */
136     RequestWriter(DataOutputStream stream)
137     {
138       out = stream;
139     }
140     
141     /**
142      * Writes an attribute in IntegerSyntax into the stream.
143      * @param attribute the attribute
144      * @throws IOException if thrown by the stream
145      */
146     private void write(IntegerSyntax attribute) throws IOException
147     {
148       String name = ((Attribute) attribute).getName();
149       out.writeByte(IppValueTag.INTEGER);
150       out.writeShort(name.length());
151       out.write(name.getBytes());
152       out.writeShort(4); // length, integer is 4 bytes
153       out.writeInt(attribute.getValue());
154     }
155 
156     /**
157      * Writes an attribute in EnumSyntax into the stream.
158      * @param attribute the attribute
159      * @throws IOException if thrown by the stream
160      */
161     private void write(EnumSyntax attribute) throws IOException
162     {
163       // in JPS API enum syntax is used for enums, keyword and boolean types
164       String name = ((Attribute) attribute).getName();
165 
166       // the enum value types
167       if (attribute instanceof Finishings
168           || attribute instanceof OrientationRequested
169           || attribute instanceof PrintQuality)
170         {
171           out.writeByte(IppValueTag.ENUM);
172           out.writeShort(name.length());
173           out.write(name.getBytes());
174           out.writeShort(4); // length, enum is 4 bytes
175           out.writeInt(attribute.getValue());
176         }
177       // the boolean value type
178       else if (attribute instanceof Fidelity)
179         {
180           out.writeByte(IppValueTag.BOOLEAN);
181           out.writeShort(name.length());
182           out.write(name.getBytes());
183           out.writeShort(1); // length, boolean is 1 bytes
184           out.writeByte(attribute.getValue() == 0 ? 0x00 : 0x01);
185         }
186       // the keyword value types
187       else
188         {
189           String keyword = attribute.toString();
190           out.writeByte(IppValueTag.KEYWORD);
191           out.writeShort(name.length());
192           out.write(name.getBytes());
193           out.writeShort(keyword.length());
194           out.write(keyword.getBytes());
195         }
196     }
197 
198     /**
199      * Writes an attribute in SetOfIntegerSyntax into the stream.
200      * @param attribute the attribute
201      * @throws IOException if thrown by the stream
202      */
203     private void write(SetOfIntegerSyntax attribute) throws IOException
204     {
205       String name = ((Attribute) attribute).getName();
206       int[][] ranges = attribute.getMembers();
207       for (int i = 0; i < ranges.length; i++)
208         {
209           out.writeByte(IppValueTag.RANGEOFINTEGER);
210           if (i == 0)
211             {
212               out.writeShort(name.length());
213               out.write(name.getBytes());
214             }
215           else
216             out.writeShort(0x0000); // only name-length 
217 
218           out.writeShort(8); // range is 8 bytes
219           out.writeInt(ranges[i][0]);
220           out.writeInt(ranges[i][1]);
221         }
222     }
223 
224     /**
225      * Writes an attribute in ResolutionSyntax into the stream.
226      * @param attribute the attribute
227      * @throws IOException if thrown by the stream
228      */
229     private void write(ResolutionSyntax attribute) throws IOException
230     {
231       String name = ((Attribute) attribute).getName();
232       out.writeByte(IppValueTag.RESOLUTION);
233       out.writeShort(name.length());
234       out.write(name.getBytes());
235       out.writeShort(9); // length fixed to 9
236       out.writeInt(attribute.getCrossFeedResolution(ResolutionSyntax.DPI));
237       out.writeInt(attribute.getFeedResolution(ResolutionSyntax.DPI));
238       out.writeByte(ResolutionSyntax.DPI);
239     }
240 
241     /**
242      * Writes an attribute in DateTimeSyntax into the stream.
243      * <p>
244      * The syntax value is defined as 11 octets follwing the
245      * DateAndTime format of RFC 1903. (see IppResponse)
246      * </p>
247      *
248      * @param attribute the attribute
249      * @throws IOException if thrown by the stream
250      */
251     private void write(DateTimeSyntax attribute) throws IOException
252     {
253       String name = ((Attribute) attribute).getName();
254       out.writeByte(IppValueTag.DATETIME);
255       out.writeShort(name.length());
256       out.write(name.getBytes());
257       out.writeShort(11); // length fixed to 11
258 
259       Date date = attribute.getValue();
260       Calendar cal = new GregorianCalendar();
261       cal.setTime(date);
262 
263       out.writeShort(cal.get(Calendar.YEAR));
264       out.writeByte(cal.get(Calendar.MONTH));
265       out.writeByte(cal.get(Calendar.DAY_OF_MONTH));
266       out.writeByte(cal.get(Calendar.HOUR_OF_DAY));
267       out.writeByte(cal.get(Calendar.MINUTE));
268       int second = cal.get(Calendar.SECOND);
269       out.writeByte(second == 0 ? 60 : second);
270       out.writeByte(cal.get(Calendar.MILLISECOND) / 100);
271 
272       int offsetInMillis = cal.get(Calendar.ZONE_OFFSET);
273       char directionFromUTC = '+';
274       if (offsetInMillis < 0)
275         {
276           directionFromUTC = '-';
277           offsetInMillis = offsetInMillis * (-1);
278         }
279 
280       out.writeByte(directionFromUTC);
281       out.writeByte(offsetInMillis / 3600000); // hours    
282       out.writeByte((offsetInMillis % 3600000) / 60000); // minutes
283     }
284 
285     /**
286      * Writes an attribute in TextSyntax into the stream.
287      * <p>
288      * By default attributes are qritten as TEXT_WITHOUT_LANGUAGE value-tag.
289      * As some attributes in the JPS are TextSyntax attributes but actually
290      * of NAME value-tag in IPP this method checks for these attributes and
291      * writes them as NAME_WITHOUT_LANGUAGE value-tag into the stream.
292      * </p>
293      * 
294      * @param attribute the attribute
295      * @param out the stream to write to
296      * @throws IOException if thrown by the stream
297      */
298     private void write(TextSyntax attribute) throws IOException
299     {
300       // We only use *WithoutLanguage, correct according to spec.
301       String name = ((Attribute) attribute).getName();
302 
303       if (attribute instanceof RequestingUserName
304           || attribute instanceof JobName
305           || attribute instanceof DocumentName
306           || attribute instanceof JobOriginatingUserName)
307         out.writeByte(IppValueTag.NAME_WITHOUT_LANGUAGE);
308       else if (attribute instanceof DocumentFormat)
309         out.writeByte(IppValueTag.MIME_MEDIA_TYPE);
310       else
311         out.writeByte(IppValueTag.TEXT_WITHOUT_LANGUAGE);
312       
313       out.writeShort(name.length());
314       out.write(name.getBytes());
315       out.writeShort(attribute.getValue().length());
316       out.write(attribute.getValue().getBytes());     
317     }
318 
319     /**
320      * Writes an attribute in URISyntax into the stream.
321      * @param attribute the attribute
322      * @param out the stream to write to
323      * @throws IOException if thrown by the stream
324      */
325     private void write(URISyntax attribute) throws IOException
326     {
327       // only uriScheme syntax type should not appear
328       // in a request (reference-uri-schemes-supported)
329       String name = ((Attribute) attribute).getName();
330       String uriAscii = attribute.getURI().toASCIIString();
331       out.writeByte(IppValueTag.URI);
332       out.writeShort(name.length());
333       out.write(name.getBytes());
334       out.writeShort(uriAscii.length());
335       out.write(uriAscii.getBytes());
336     }
337 
338     /**
339      * Writes an attribute in CharsetSyntax into the stream.
340      * @param attribute the attribute
341      * @param out the stream to write to
342      * @throws IOException if thrown by the stream
343      */
344     private void write(CharsetSyntax attribute) throws IOException
345     {      
346       String name = ((Attribute) attribute).getName();      
347       out.writeByte(IppValueTag.CHARSET);
348       out.writeShort(name.length());
349       out.write(name.getBytes());
350       out.writeShort(attribute.getValue().length());
351       out.write(attribute.getValue().getBytes());
352     }
353 
354     /**
355      * Writes an attribute in NaturalLanguageSyntax into the stream.
356      * @param attribute the attribute
357      * @param out the stream to write to
358      * @throws IOException if thrown by the stream
359      */
360     private void write(NaturalLanguageSyntax attribute) throws IOException
361     {
362       String name = ((Attribute) attribute).getName();
363       out.writeByte(IppValueTag.NATURAL_LANGUAGE);
364       out.writeShort(name.length());
365       out.write(name.getBytes());
366       out.writeShort(attribute.getValue().length());
367       out.write(attribute.getValue().getBytes());
368     }
369     
370     /**
371      * Writes an attribute in RequestedAttributes into the stream.
372      * @param attribute the attribute
373      * @param out the stream to write to
374      * @throws IOException if thrown by the stream
375      */
376     private void write(RequestedAttributes attribute) throws IOException
377     {
378       List values = attribute.getValues();
379       
380       String name = ((Attribute) attribute).getName();
381       out.writeByte(IppValueTag.KEYWORD);
382       out.writeShort(name.length());
383       out.write(name.getBytes()); 
384       out.writeShort(((String) values.get(0)).length());
385       out.write(((String) values.get(0)).getBytes());
386       
387       for (int i=1; i < values.size(); i++)
388         {
389           out.writeByte(IppValueTag.KEYWORD);
390           out.writeShort(0x0000); // length for additional value
391           out.writeShort(((String) values.get(i)).length());
392           out.write(((String) values.get(i)).getBytes());  
393         }
394     } 
395 
396     
397     /**
398      * Writes the given operation attribute group of the given map instance
399      * (key=group, values=set of attributes) into the supplied data
400      * output stream.
401      * 
402      * @param attributes the set with the attributes.
403      * 
404      * @throws IOException if thrown by the used DataOutputStream.
405      * @throws IppException if unknown attributes occur.
406      */
407     public void writeOperationAttributes(AttributeSet attributes)
408         throws IOException, IppException
409     {
410       out.write(IppDelimiterTag.OPERATION_ATTRIBUTES_TAG);
411       
412       // its essential to write these two in this order and as first ones
413       Attribute att = attributes.get(AttributesCharset.class);
414       write((CharsetSyntax) att);
415       
416       logger.log(Component.IPP, "Attribute: Name: <" 
417         + att.getCategory().getName() + "> Value: <" + att.toString() + ">");  
418       
419       attributes.remove(AttributesCharset.class);
420       
421       att = attributes.get(AttributesNaturalLanguage.class);
422       write((NaturalLanguageSyntax) att);
423       attributes.remove(AttributesNaturalLanguage.class);
424       
425       logger.log(Component.IPP, "Attribute: Name: <" 
426         + att.getCategory().getName() + "> Value: <" + att.toString() + ">");
427       
428       // furthermore its essential to now write out the target attribute
429       PrinterURI printerUri = (PrinterURI) attributes.get(PrinterURI.class);
430       JobUri jobUri = (JobUri) attributes.get(JobUri.class);
431       JobId jobId = (JobId) attributes.get(JobId.class);
432       if (printerUri != null && jobId == null && jobUri == null)
433         {
434           write(printerUri);
435           attributes.remove(PrinterURI.class);
436           logger.log(Component.IPP, "Attribute: Name: <" + printerUri
437             .getCategory().getName() + "> Value: <" + printerUri.toString() + ">");
438         }
439       else if (jobUri != null && jobId == null && printerUri == null)
440         {
441           write(jobUri);
442           attributes.remove(JobUri.class);
443           logger.log(Component.IPP, "Attribute: Name: <" + jobUri
444             .getCategory().getName() + "> Value: <" + jobUri.toString() + ">");
445         }
446       else if (printerUri != null && jobId != null && jobUri == null)
447         {
448           write(printerUri); // must be third
449           write(jobId);
450           attributes.remove(PrinterURI.class);
451           attributes.remove(JobId.class);
452           logger.log(Component.IPP, "Attribute: Name: <" + printerUri
453             .getCategory().getName() + "> Value: <" + printerUri.toString() + ">");
454           logger.log(Component.IPP, "Attribute: Name: <" + jobId.getCategory()
455             .getName() + "> Value: <" + jobId.toString() + ">");
456         }
457       else if (jobUri != null && jobId != null)
458         {
459           write(jobUri);
460           attributes.remove(JobUri.class);
461           attributes.remove(JobId.class); // MUST NOT redundant
462           logger.log(Component.IPP, "Attribute: Name: <" + jobUri.getCategory()
463             .getName() + "> Value: <" + jobUri.toString() + ">");
464         }
465       else
466         {
467           new IppException("Unknown target operation attribute combination.");
468         }      
469       
470       writeAttributes(attributes);
471     }
472     
473     /**
474      * Writes the given attribute groups of the given map instance
475      * (key=group, values=set of attributes) into the supplied data
476      * output stream.
477      * 
478      * @param attributes the set with the attributes.
479      * 
480      * @throws IOException if thrown by the used DataOutputStream.
481      * @throws IppException if unknown attributes occur.
482      */
483     public void writeAttributes(AttributeSet attributes)
484         throws IOException, IppException
485     {
486       Attribute[] attributeArray = attributes.toArray();
487       for (int i = 0; i < attributeArray.length; i++)
488         {
489           logger.log(Component.IPP, "Attribute: Name: <" + attributeArray[i]
490             .getCategory().getName() + "> Value: <" 
491             + attributeArray[i].toString() + ">");          
492           
493           if (attributeArray[i] instanceof IntegerSyntax)
494             write((IntegerSyntax) attributeArray[i]);
495           else if (attributeArray[i] instanceof TextSyntax)
496             write((TextSyntax) attributeArray[i]);
497           else if (attributeArray[i] instanceof DateTimeSyntax)
498             write((DateTimeSyntax) attributeArray[i]);
499           else if (attributeArray[i] instanceof ResolutionSyntax)
500             write((ResolutionSyntax) attributeArray[i]);
501           else if (attributeArray[i] instanceof SetOfIntegerSyntax)
502             write((SetOfIntegerSyntax) attributeArray[i]);
503           else if (attributeArray[i] instanceof EnumSyntax)
504             write((EnumSyntax) attributeArray[i]);
505           else if (attributeArray[i] instanceof URISyntax)
506             write((URISyntax) attributeArray[i]);
507           else if (attributeArray[i] instanceof CharsetSyntax)
508             write((CharsetSyntax) attributeArray[i]);
509           else if (attributeArray[i] instanceof NaturalLanguageSyntax)
510             write((NaturalLanguageSyntax) attributeArray[i]);
511           else if (attributeArray[i] instanceof RequestedAttributes)
512             write((RequestedAttributes) attributeArray[i]);
513           else
514             throw new IppException("Unknown syntax type");
515         }
516     }
517 
518   }
519 
520   /**
521    * Logger for tracing - enable by passing
522    * -Dgnu.classpath.debug.components=ipp to the vm.
523    */
524   static final Logger logger = SystemLogger.SYSTEM;
525 
526   /**
527    * The request id counter simply counts up
528    * to give unique request ids per JVM instance.
529    */
530   private static int requestIdCounter = 1;
531 
532   /** The IPP version defaults to 1.1 */
533   private static final short VERSION = 0x0101;
534 
535   /** Signals if the request is already on its way */
536   private boolean alreadySent = false;
537 
538   /** The operation type of this request. */
539   private short operation_id;
540 
541   /** 
542    * The request id of this request. This is 
543    * assigned automatically by the constructor.
544    */
545   private final int request_id;
546 
547   private AttributeSet operationAttributes;
548 
549   private AttributeSet printerAttributes;
550 
551   private AttributeSet jobAttributes;
552 
553   private Object data;
554   
555   private URI requestUri;
556 
557   /** The underlying connection - IPP is http based */
558   private HttpURLConnection  connection;
559   
560   /**
561    * Creates an IPPRequest instance.
562    * 
563    * @param uri the URI of the request
564    * @param user the user if any
565    * @param password the password of the supplied user
566    */
567   public IppRequest(URI uri, String user, String password)
568   {   
569     request_id = incrementRequestIdCounter();
570     requestUri = uri;
571     
572     try
573       {
574         URL url = new URL("http", 
575                       user == null 
576                       ? uri.getHost() : user + ":" 
577                       + password + "@" + uri.getHost(), 
578                       uri.getPort(), uri.getPath());
579        
580         connection = (HttpURLConnection) url.openConnection();
581         connection.setRequestMethod("POST");
582         connection.setDoOutput(true);
583         
584         connection.setRequestProperty("Content-type", "application/ipp");
585         connection.setRequestProperty("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");        
586       }   
587     catch (IOException e)
588       {
589         // MalformedURLException - uri is already checked
590         // ProtocolException - POST is correct method type
591         // IOException -HTTPURLConnection constructor actually 
592         // does never throw this exception.
593         logger.log(Component.IPP, "Unexpected IOException", e);
594       }
595     
596     logger.log(Component.IPP, "[IppConnection] Host: " + uri.getHost()
597                               + " Port: " + uri.getPort() + " Path: "
598                               + uri.getPath());
599   }
600 
601   /**
602    * Synchronized method to be called by the constructor
603    * to assign a unique request id to this request.
604    * 
605    * @return The unique request id.
606    */
607   private synchronized int incrementRequestIdCounter()
608   {
609     return IppRequest.requestIdCounter++;
610   }
611 
612   /**
613    * Returns the id of this request.
614    * 
615    * @return The request ID.
616    */
617   public int getRequestID()
618   {
619     return request_id;
620   }
621 
622   /** 
623    * Sets the data of the request. The data used in this 
624    * request will be the one of the supplied inputstream
625    * instead of the alternative byte array possibility.
626    * 
627    * @param stream the input stream to use for the data.
628    */
629   public void setData(InputStream stream)
630   {
631     data = stream;
632   }
633 
634   /** 
635    * Sets the data of the request. The data used in this 
636    * request will be the one of the supplied byte[]
637    * instead of the alternative input stream possibility.
638    * 
639    * @param bytes the byte[] to use for the data.
640    */
641   public void setData(byte[] bytes)
642   {
643     data = bytes;
644   }
645 
646   /**
647    * Sets the operation id for this request.
648    * 
649    * @param id the operation id.
650    */
651   public void setOperationID(short id)
652   {
653     operation_id = id;
654   }
655 
656   /**
657    * Adds the default values for the operation
658    * attributes "attributes-charset" and 
659    * "attributes-natural-language"
660    */
661   public void setOperationAttributeDefaults()
662   {
663     if (operationAttributes == null)
664       operationAttributes = new HashAttributeSet();
665 
666     operationAttributes.add(AttributesCharset.UTF8);
667     operationAttributes.add(AttributesNaturalLanguage.EN);
668   }
669   
670   /**
671    * Add the job attribute of this request to the given
672    * attribute set.
673    * 
674    * @param attribute the job attribute.
675    */
676   public void addJobAttribute(Attribute attribute)
677   {
678     if (jobAttributes == null)
679       jobAttributes = new HashAttributeSet();
680     
681     jobAttributes.add(attribute);
682   }
683   
684   /**
685    * Sets the printer attribute of this request to the given
686    * attribute set.
687    * 
688    * @param attribute the printer attribute.
689    */
690   public void addPrinterAttributes(Attribute attribute)
691   {
692     if (printerAttributes == null)
693       printerAttributes = new HashAttributeSet();
694     
695     printerAttributes.add(attribute);
696   }
697 
698   /**
699    * Adds the given attribute to the operation attributes set.
700    * 
701    * @param attribute the operation attribute to add.
702    */
703   public void addOperationAttribute(Attribute attribute)
704   {
705     if (operationAttributes == null)
706       operationAttributes = new HashAttributeSet();
707     
708     operationAttributes.add(attribute);
709   }
710   
711   /**
712    * Filters from the given attribute set the job operation out
713    * and adds them to the operation attributes set.
714    * 
715    * @param set the attributes to filter, may not be <code>null</code>.
716    */
717   public void addAndFilterJobOperationAttributes(AttributeSet set)
718   {
719     if (operationAttributes == null)
720       operationAttributes = new HashAttributeSet();
721     
722     // document-natural-language - not defined in JPS attributes
723     // document-format - specified outside, special treatment
724     Attribute[] tmp = set.toArray();
725     for (int i = 0; i < tmp.length; i++) 
726       {        
727         if (tmp[i].getCategory().equals(JobName.class)
728             || tmp[i].getCategory().equals(Fidelity.class)
729             || tmp[i].getCategory().equals(JobImpressions.class)
730             || tmp[i].getCategory().equals(JobKOctets.class)
731             || tmp[i].getCategory().equals(JobMediaSheets.class)
732             || tmp[i].getCategory().equals(Compression.class)
733             || tmp[i].getCategory().equals(DocumentName.class)
734             || tmp[i].getCategory().equals(RequestingUserName.class))
735                 
736           operationAttributes.add(tmp[i]);            
737       }    
738   }
739   
740   /**
741    * Filters from the given attribute set the job template attributes
742    * out and adds them to the job attributes set.
743    * 
744    * @param set the attributes to filter, may not be <code>null</code>.
745    */
746   public void addAndFilterJobTemplateAttributes(AttributeSet set)
747   {
748     if (jobAttributes == null)
749       jobAttributes = new HashAttributeSet();
750     
751     // document-natural-language - not defined in JPS attributes
752     // document-format - specified outside, special treatment
753     Attribute[] tmp = set.toArray();
754     for (int i = 0; i < tmp.length; i++) 
755       {        
756         if (tmp[i].getCategory().equals(JobPriority.class)
757             || tmp[i].getCategory().equals(JobHoldUntil.class)
758             || tmp[i].getCategory().equals(JobSheets.class)
759             || tmp[i].getCategory().equals(MultipleDocumentHandling.class)
760             || tmp[i].getCategory().equals(Copies.class)
761             || tmp[i].getCategory().equals(Finishings.class)
762             || tmp[i].getCategory().equals(PageRanges.class)
763             || tmp[i].getCategory().equals(NumberUp.class)
764             || tmp[i].getCategory().equals(OrientationRequested.class)
765             || tmp[i].getCategory().equals(Media.class)
766             || tmp[i].getCategory().equals(PrinterResolution.class)
767             || tmp[i].getCategory().equals(PrintQuality.class)
768             || tmp[i].getCategory().equals(SheetCollate.class)
769             || tmp[i].getCategory().equals(Sides.class))
770                 
771           jobAttributes.add(tmp[i]);            
772       }    
773   }
774 
775   /**
776    * Does some validation of the supplied parameters and then
777    * sends the request to the ipp server or service.
778    * 
779    * @return The response if any.
780    * 
781    * @throws IllegalStateException if request is already sent
782    * @throws IppException if connection or request failed.
783    * @throws IOException if writing of the header, attributes or footer fails. 
784    */
785   public IppResponse send() throws IppException, IOException
786   {
787     if (alreadySent)
788       throw new IllegalStateException("Request is already sent");
789     
790     alreadySent = true;
791       
792     OutputStream stream = stream = connection.getOutputStream();   
793     DataOutputStream out = new DataOutputStream(stream);
794         
795     //  the header 8 bytes long
796     out.writeShort(VERSION);
797     out.writeShort(operation_id);
798     out.writeInt(request_id);
799         
800     logger.log(Component.IPP, "OperationID: " + Integer.toHexString(operation_id) 
801       + " RequestID: " + request_id); 
802         
803     // Pass stuff the the attribute writer which knows how to
804     // write the attributes in correct order
805     logger.log(Component.IPP, "Operation Attributes");
806         
807     RequestWriter writer = new RequestWriter(out);
808     writer.writeOperationAttributes(operationAttributes);       
809         
810     if (jobAttributes != null)
811       {
812         logger.log(Component.IPP, "Job Attributes");
813         out.write(IppDelimiterTag.JOB_ATTRIBUTES_TAG);
814         writer.writeAttributes(jobAttributes);
815       }           
816     if (printerAttributes != null)
817       {
818         logger.log(Component.IPP, "Printer Attributes");
819         out.write(IppDelimiterTag.PRINTER_ATTRIBUTES_TAG);
820         writer.writeAttributes(printerAttributes);
821       }          
822 
823     // write the delimiter to the data
824     out.write(IppDelimiterTag.END_OF_ATTRIBUTES_TAG);               
825 
826     // check if data is byte[] or inputstream
827     if (data instanceof InputStream)
828       {
829         byte[] readbuf = new byte[2048];
830         int len = 0;            
831         while( (len = ((InputStream) data).read(readbuf)) > 0)
832           out.write(readbuf, 0, len);
833       }
834     else if (data != null)
835       {
836         out.write((byte[]) data);
837       }
838              
839     out.flush();
840     stream.flush();  
841     
842     int responseCode = responseCode = connection.getResponseCode();
843     
844     if (responseCode == HttpURLConnection.HTTP_OK)
845       { 
846         IppResponse response = new IppResponse(requestUri, operation_id);        
847         response.setResponseData(connection.getInputStream());     
848         return response;
849       }
850 
851     logger.log(Component.IPP, "HTTP-Statuscode: " + responseCode);
852 
853     throw new IppException("Request failed got HTTP status code " 
854                            + responseCode);
855   }
856 
857 }