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 }