Source for gnu.javax.print.ipp.IppRequest

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