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:       List 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(((String) values.get(0)).length());
 390:       out.write(((String) values.get(0)).getBytes());
 391:       
 392:       for (int i=1; i < values.size(); i++)
 393:         {
 394:           out.writeByte(IppValueTag.KEYWORD);
 395:           out.writeShort(0x0000); // length for additional value
 396:           out.writeShort(((String) values.get(i)).length());
 397:           out.write(((String) values.get(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:       if (printerUri != null && jobId == null && jobUri == null)
 438:         {
 439:           write(printerUri);
 440:           attributes.remove(PrinterURI.class);
 441:           logger.log(Component.IPP, "Attribute: Name: <" + printerUri
 442:             .getCategory().getName() + "> Value: <" + printerUri.toString() + ">");
 443:         }
 444:       else if (jobUri != null && jobId == null && printerUri == null)
 445:         {
 446:           write(jobUri);
 447:           attributes.remove(JobUri.class);
 448:           logger.log(Component.IPP, "Attribute: Name: <" + jobUri
 449:             .getCategory().getName() + "> Value: <" + jobUri.toString() + ">");
 450:         }
 451:       else if (printerUri != null && jobId != null && jobUri == null)
 452:         {
 453:           write(printerUri); // must be third
 454:           write(jobId);
 455:           attributes.remove(PrinterURI.class);
 456:           attributes.remove(JobId.class);
 457:           logger.log(Component.IPP, "Attribute: Name: <" + printerUri
 458:             .getCategory().getName() + "> Value: <" + printerUri.toString() + ">");
 459:           logger.log(Component.IPP, "Attribute: Name: <" + jobId.getCategory()
 460:             .getName() + "> Value: <" + jobId.toString() + ">");
 461:         }
 462:       else if (jobUri != null && jobId != null)
 463:         {
 464:           write(jobUri);
 465:           attributes.remove(JobUri.class);
 466:           attributes.remove(JobId.class); // MUST NOT redundant
 467:           logger.log(Component.IPP, "Attribute: Name: <" + jobUri.getCategory()
 468:             .getName() + "> Value: <" + jobUri.toString() + ">");
 469:         }
 470:       else
 471:         {
 472:           throw new IppException("Unknown target operation attribute combination.");
 473:         }      
 474:       
 475:       writeAttributes(attributes);
 476:     }
 477:     
 478:     /**
 479:      * Writes the given attribute groups of the given map instance
 480:      * (key=group, values=set of attributes) into the supplied data
 481:      * output stream.
 482:      * 
 483:      * @param attributes the set with the attributes.
 484:      * 
 485:      * @throws IOException if thrown by the used DataOutputStream.
 486:      * @throws IppException if unknown attributes occur.
 487:      */
 488:     public void writeAttributes(AttributeSet attributes)
 489:         throws IOException, IppException
 490:     {
 491:       Attribute[] attributeArray = attributes.toArray();
 492:       for (int i = 0; i < attributeArray.length; i++)
 493:         {
 494:           logger.log(Component.IPP, "Attribute: Name: <" + attributeArray[i]
 495:             .getCategory().getName() + "> Value: <" 
 496:             + attributeArray[i].toString() + ">");          
 497:           
 498:           if (attributeArray[i] instanceof IntegerSyntax)
 499:             write((IntegerSyntax) attributeArray[i]);
 500:           else if (attributeArray[i] instanceof TextSyntax)
 501:             write((TextSyntax) attributeArray[i]);
 502:           else if (attributeArray[i] instanceof DateTimeSyntax)
 503:             write((DateTimeSyntax) attributeArray[i]);
 504:           else if (attributeArray[i] instanceof ResolutionSyntax)
 505:             write((ResolutionSyntax) attributeArray[i]);
 506:           else if (attributeArray[i] instanceof SetOfIntegerSyntax)
 507:             write((SetOfIntegerSyntax) attributeArray[i]);
 508:           else if (attributeArray[i] instanceof EnumSyntax)
 509:             write((EnumSyntax) attributeArray[i]);
 510:           else if (attributeArray[i] instanceof URISyntax)
 511:             write((URISyntax) attributeArray[i]);
 512:           else if (attributeArray[i] instanceof CharsetSyntax)
 513:             write((CharsetSyntax) attributeArray[i]);
 514:           else if (attributeArray[i] instanceof NaturalLanguageSyntax)
 515:             write((NaturalLanguageSyntax) attributeArray[i]);
 516:           else if (attributeArray[i] instanceof RequestedAttributes)
 517:             write((RequestedAttributes) attributeArray[i]);
 518:           else
 519:             throw new IppException("Unknown syntax type");
 520:         }
 521:     }
 522: 
 523:   }
 524: 
 525:   /**
 526:    * Logger for tracing - enable by passing
 527:    * -Dgnu.classpath.debug.components=ipp to the vm.
 528:    */
 529:   static final Logger logger = SystemLogger.SYSTEM;
 530: 
 531:   /**
 532:    * The request id counter simply counts up
 533:    * to give unique request ids per JVM instance.
 534:    */
 535:   private static int requestIdCounter = 1;
 536: 
 537:   /** The IPP version defaults to 1.1 */
 538:   private static final short VERSION = 0x0101;
 539: 
 540:   /** Signals if the request is already on its way */
 541:   private boolean alreadySent = false;
 542: 
 543:   /** The operation type of this request. */
 544:   private short operation_id;
 545: 
 546:   /** 
 547:    * The request id of this request. This is 
 548:    * assigned automatically by the constructor.
 549:    */
 550:   private final int request_id;
 551: 
 552:   private AttributeSet operationAttributes;
 553: 
 554:   private AttributeSet printerAttributes;
 555: 
 556:   private AttributeSet jobAttributes;
 557: 
 558:   private Object data;
 559:   
 560:   private URI requestUri;
 561: 
 562:   /** The underlying connection - IPP is http based */
 563:   private HttpURLConnection  connection;
 564:   
 565:   /**
 566:    * Creates an IPPRequest instance.
 567:    * 
 568:    * @param uri the URI of the request
 569:    * @param user the user if any
 570:    * @param password the password of the supplied user
 571:    */
 572:   public IppRequest(URI uri, String user, String password)
 573:   {   
 574:     request_id = incrementRequestIdCounter();
 575:     requestUri = uri;
 576:     
 577:     try
 578:       {
 579:         URL url = new URL("http", 
 580:                       user == null 
 581:                       ? uri.getHost() : user + ":" 
 582:                       + password + "@" + uri.getHost(), 
 583:                       uri.getPort(), uri.getPath());
 584:        
 585:         connection = (HttpURLConnection) url.openConnection();
 586:         connection.setRequestMethod("POST");
 587:         connection.setDoOutput(true);
 588:         
 589:         connection.setRequestProperty("Content-type", "application/ipp");
 590:         connection.setRequestProperty("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2");        
 591:       }   
 592:     catch (IOException e)
 593:       {
 594:         // MalformedURLException - uri is already checked
 595:         // ProtocolException - POST is correct method type
 596:         // IOException -HTTPURLConnection constructor actually 
 597:         // does never throw this exception.
 598:         logger.log(Component.IPP, "Unexpected IOException", e);
 599:       }
 600:     
 601:     logger.log(Component.IPP, "[IppConnection] Host: " + uri.getHost()
 602:                               + " Port: " + uri.getPort() + " Path: "
 603:                               + uri.getPath());
 604:   }
 605: 
 606:   /**
 607:    * Synchronized method to be called by the constructor
 608:    * to assign a unique request id to this request.
 609:    * 
 610:    * @return The unique request id.
 611:    */
 612:   private synchronized int incrementRequestIdCounter()
 613:   {
 614:     return IppRequest.requestIdCounter++;
 615:   }
 616: 
 617:   /**
 618:    * Returns the id of this request.
 619:    * 
 620:    * @return The request ID.
 621:    */
 622:   public int getRequestID()
 623:   {
 624:     return request_id;
 625:   }
 626: 
 627:   /** 
 628:    * Sets the data of the request. The data used in this 
 629:    * request will be the one of the supplied inputstream
 630:    * instead of the alternative byte array possibility.
 631:    * 
 632:    * @param stream the input stream to use for the data.
 633:    */
 634:   public void setData(InputStream stream)
 635:   {
 636:     data = stream;
 637:   }
 638: 
 639:   /** 
 640:    * Sets the data of the request. The data used in this 
 641:    * request will be the one of the supplied byte[]
 642:    * instead of the alternative input stream possibility.
 643:    * 
 644:    * @param bytes the byte[] to use for the data.
 645:    */
 646:   public void setData(byte[] bytes)
 647:   {
 648:     data = bytes;
 649:   }
 650: 
 651:   /**
 652:    * Sets the operation id for this request.
 653:    * 
 654:    * @param id the operation id.
 655:    */
 656:   public void setOperationID(short id)
 657:   {
 658:     operation_id = id;
 659:   }
 660: 
 661:   /**
 662:    * Adds the default values for the operation
 663:    * attributes "attributes-charset" and 
 664:    * "attributes-natural-language"
 665:    */
 666:   public void setOperationAttributeDefaults()
 667:   {
 668:     if (operationAttributes == null)
 669:       operationAttributes = new HashAttributeSet();
 670: 
 671:     operationAttributes.add(AttributesCharset.UTF8);
 672:     operationAttributes.add(AttributesNaturalLanguage.EN);
 673:   }
 674:   
 675:   /**
 676:    * Add the job attribute of this request to the given
 677:    * attribute set.
 678:    * 
 679:    * @param attribute the job attribute.
 680:    */
 681:   public void addJobAttribute(Attribute attribute)
 682:   {
 683:     if (jobAttributes == null)
 684:       jobAttributes = new HashAttributeSet();
 685:     
 686:     jobAttributes.add(attribute);
 687:   }
 688:   
 689:   /**
 690:    * Sets the printer attribute of this request to the given
 691:    * attribute set.
 692:    * 
 693:    * @param attribute the printer attribute.
 694:    */
 695:   public void addPrinterAttributes(Attribute attribute)
 696:   {
 697:     if (printerAttributes == null)
 698:       printerAttributes = new HashAttributeSet();
 699:     
 700:     printerAttributes.add(attribute);
 701:   }
 702: 
 703:   /**
 704:    * Adds the given attribute to the operation attributes set.
 705:    * 
 706:    * @param attribute the operation attribute to add.
 707:    */
 708:   public void addOperationAttribute(Attribute attribute)
 709:   {
 710:     if (operationAttributes == null)
 711:       operationAttributes = new HashAttributeSet();
 712:     
 713:     operationAttributes.add(attribute);
 714:   }
 715:   
 716:   /**
 717:    * Filters from the given attribute set the job operation out
 718:    * and adds them to the operation attributes set.
 719:    * 
 720:    * @param set the attributes to filter, may not be <code>null</code>.
 721:    */
 722:   public void addAndFilterJobOperationAttributes(AttributeSet set)
 723:   {
 724:     if (operationAttributes == null)
 725:       operationAttributes = new HashAttributeSet();
 726:     
 727:     // document-natural-language - not defined in JPS attributes
 728:     // document-format - specified outside, special treatment
 729:     Attribute[] tmp = set.toArray();
 730:     for (int i = 0; i < tmp.length; i++) 
 731:       {        
 732:         if (tmp[i].getCategory().equals(JobName.class)
 733:             || tmp[i].getCategory().equals(Fidelity.class)
 734:             || tmp[i].getCategory().equals(JobImpressions.class)
 735:             || tmp[i].getCategory().equals(JobKOctets.class)
 736:             || tmp[i].getCategory().equals(JobMediaSheets.class)
 737:             || tmp[i].getCategory().equals(Compression.class)
 738:             || tmp[i].getCategory().equals(DocumentName.class)
 739:             || tmp[i].getCategory().equals(RequestingUserName.class))
 740:                 
 741:           operationAttributes.add(tmp[i]);            
 742:       }    
 743:   }
 744:   
 745:   /**
 746:    * Filters from the given attribute set the job template attributes
 747:    * out and adds them to the job attributes set.
 748:    * 
 749:    * @param set the attributes to filter, may not be <code>null</code>.
 750:    */
 751:   public void addAndFilterJobTemplateAttributes(AttributeSet set)
 752:   {
 753:     if (jobAttributes == null)
 754:       jobAttributes = new HashAttributeSet();
 755:     
 756:     // document-natural-language - not defined in JPS attributes
 757:     // document-format - specified outside, special treatment
 758:     Attribute[] tmp = set.toArray();
 759:     for (int i = 0; i < tmp.length; i++) 
 760:       {        
 761:         if (tmp[i].getCategory().equals(JobPriority.class)
 762:             || tmp[i].getCategory().equals(JobHoldUntil.class)
 763:             || tmp[i].getCategory().equals(JobSheets.class)
 764:             || tmp[i].getCategory().equals(MultipleDocumentHandling.class)
 765:             || tmp[i].getCategory().equals(Copies.class)
 766:             || tmp[i].getCategory().equals(Finishings.class)
 767:             || tmp[i].getCategory().equals(PageRanges.class)
 768:             || tmp[i].getCategory().equals(NumberUp.class)
 769:             || tmp[i].getCategory().equals(OrientationRequested.class)
 770:             || tmp[i].getCategory().equals(Media.class)
 771:             || tmp[i].getCategory().equals(PrinterResolution.class)
 772:             || tmp[i].getCategory().equals(PrintQuality.class)
 773:             || tmp[i].getCategory().equals(SheetCollate.class)
 774:             || tmp[i].getCategory().equals(Sides.class))
 775:                 
 776:           jobAttributes.add(tmp[i]);            
 777:       }    
 778:   }
 779: 
 780:   /**
 781:    * Does some validation of the supplied parameters and then
 782:    * sends the request to the ipp server or service.
 783:    * 
 784:    * @return The response if any.
 785:    * 
 786:    * @throws IllegalStateException if request is already sent
 787:    * @throws IppException if connection or request failed.
 788:    * @throws IOException if writing of the header, attributes or footer fails. 
 789:    */
 790:   public IppResponse send() throws IppException, IOException
 791:   {
 792:     if (alreadySent)
 793:       throw new IllegalStateException("Request is already sent");
 794:     
 795:     alreadySent = true;
 796:       
 797:     OutputStream stream = connection.getOutputStream();   
 798:     DataOutputStream out = new DataOutputStream(stream);
 799:         
 800:     //  the header 8 bytes long
 801:     out.writeShort(VERSION);
 802:     out.writeShort(operation_id);
 803:     out.writeInt(request_id);
 804:         
 805:     logger.log(Component.IPP, "OperationID: " + Integer.toHexString(operation_id) 
 806:       + " RequestID: " + request_id); 
 807:         
 808:     // Pass stuff the the attribute writer which knows how to
 809:     // write the attributes in correct order
 810:     logger.log(Component.IPP, "Operation Attributes");
 811:         
 812:     RequestWriter writer = new RequestWriter(out);
 813:     writer.writeOperationAttributes(operationAttributes);       
 814:         
 815:     if (jobAttributes != null)
 816:       {
 817:         logger.log(Component.IPP, "Job Attributes");
 818:         out.write(IppDelimiterTag.JOB_ATTRIBUTES_TAG);
 819:         writer.writeAttributes(jobAttributes);
 820:       }           
 821:     if (printerAttributes != null)
 822:       {
 823:         logger.log(Component.IPP, "Printer Attributes");
 824:         out.write(IppDelimiterTag.PRINTER_ATTRIBUTES_TAG);
 825:         writer.writeAttributes(printerAttributes);
 826:       }          
 827: 
 828:     // write the delimiter to the data
 829:     out.write(IppDelimiterTag.END_OF_ATTRIBUTES_TAG);               
 830: 
 831:     // check if data is byte[] or inputstream
 832:     if (data instanceof InputStream)
 833:       {
 834:         byte[] readbuf = new byte[2048];
 835:         int len = 0;            
 836:         while( (len = ((InputStream) data).read(readbuf)) > 0)
 837:           out.write(readbuf, 0, len);
 838:       }
 839:     else if (data != null)
 840:       {
 841:         out.write((byte[]) data);
 842:       }
 843:              
 844:     out.flush();
 845:     stream.flush();  
 846:   
 847:     // Set the connection timeout, for if the printer is offline.
 848:     // FIXME: The print services polling should probably be done in its
 849:     // own thread.
 850:     connection.setConnectTimeout( timeout );
 851: 
 852:     int responseCode = connection.getResponseCode();
 853:     
 854:     if (responseCode == HttpURLConnection.HTTP_OK)
 855:       { 
 856:         IppResponse response = new IppResponse(requestUri, operation_id);        
 857:         response.setResponseData(connection.getInputStream());     
 858:         return response;
 859:       }
 860: 
 861:     logger.log(Component.IPP, "HTTP-Statuscode: " + responseCode);
 862: 
 863:     throw new IppException("Request failed got HTTP status code " 
 864:                            + responseCode);
 865:   }
 866: 
 867: }