Source for gnu.javax.print.ipp.IppPrintService

   1: /* IppPrintService.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.SystemProperties;
  42: import gnu.classpath.debug.Component;
  43: import gnu.classpath.debug.SystemLogger;
  44: import gnu.javax.print.ipp.attribute.DefaultValueAttribute;
  45: import gnu.javax.print.ipp.attribute.RequestedAttributes;
  46: import gnu.javax.print.ipp.attribute.defaults.CopiesDefault;
  47: import gnu.javax.print.ipp.attribute.defaults.FinishingsDefault;
  48: import gnu.javax.print.ipp.attribute.defaults.JobHoldUntilDefault;
  49: import gnu.javax.print.ipp.attribute.defaults.JobPriorityDefault;
  50: import gnu.javax.print.ipp.attribute.defaults.JobSheetsDefault;
  51: import gnu.javax.print.ipp.attribute.defaults.MediaDefault;
  52: import gnu.javax.print.ipp.attribute.defaults.MultipleDocumentHandlingDefault;
  53: import gnu.javax.print.ipp.attribute.defaults.NumberUpDefault;
  54: import gnu.javax.print.ipp.attribute.defaults.OrientationRequestedDefault;
  55: import gnu.javax.print.ipp.attribute.defaults.PrintQualityDefault;
  56: import gnu.javax.print.ipp.attribute.defaults.PrinterResolutionDefault;
  57: import gnu.javax.print.ipp.attribute.defaults.SidesDefault;
  58: import gnu.javax.print.ipp.attribute.printer.DocumentFormat;
  59: import gnu.javax.print.ipp.attribute.supported.CompressionSupported;
  60: import gnu.javax.print.ipp.attribute.supported.DocumentFormatSupported;
  61: import gnu.javax.print.ipp.attribute.supported.FinishingsSupported;
  62: import gnu.javax.print.ipp.attribute.supported.JobHoldUntilSupported;
  63: import gnu.javax.print.ipp.attribute.supported.JobSheetsSupported;
  64: import gnu.javax.print.ipp.attribute.supported.MediaSupported;
  65: import gnu.javax.print.ipp.attribute.supported.MultipleDocumentHandlingSupported;
  66: import gnu.javax.print.ipp.attribute.supported.OperationsSupported;
  67: import gnu.javax.print.ipp.attribute.supported.OrientationRequestedSupported;
  68: import gnu.javax.print.ipp.attribute.supported.PageRangesSupported;
  69: import gnu.javax.print.ipp.attribute.supported.PrintQualitySupported;
  70: import gnu.javax.print.ipp.attribute.supported.PrinterResolutionSupported;
  71: import gnu.javax.print.ipp.attribute.supported.PrinterUriSupported;
  72: import gnu.javax.print.ipp.attribute.supported.SidesSupported;
  73: 
  74: import java.io.IOException;
  75: import java.lang.reflect.Field;
  76: import java.net.URI;
  77: import java.util.ArrayList;
  78: import java.util.Arrays;
  79: import java.util.Date;
  80: import java.util.HashSet;
  81: import java.util.Iterator;
  82: import java.util.List;
  83: import java.util.Map;
  84: import java.util.Set;
  85: import java.util.logging.Logger;
  86: 
  87: import javax.print.DocFlavor;
  88: import javax.print.DocPrintJob;
  89: import javax.print.PrintService;
  90: import javax.print.ServiceUIFactory;
  91: import javax.print.attribute.Attribute;
  92: import javax.print.attribute.AttributeSet;
  93: import javax.print.attribute.AttributeSetUtilities;
  94: import javax.print.attribute.HashAttributeSet;
  95: import javax.print.attribute.HashPrintServiceAttributeSet;
  96: import javax.print.attribute.IntegerSyntax;
  97: import javax.print.attribute.PrintServiceAttribute;
  98: import javax.print.attribute.PrintServiceAttributeSet;
  99: import javax.print.attribute.standard.Compression;
 100: import javax.print.attribute.standard.Copies;
 101: import javax.print.attribute.standard.CopiesSupported;
 102: import javax.print.attribute.standard.Fidelity;
 103: import javax.print.attribute.standard.Finishings;
 104: import javax.print.attribute.standard.JobHoldUntil;
 105: import javax.print.attribute.standard.JobImpressions;
 106: import javax.print.attribute.standard.JobImpressionsSupported;
 107: import javax.print.attribute.standard.JobKOctets;
 108: import javax.print.attribute.standard.JobKOctetsSupported;
 109: import javax.print.attribute.standard.JobMediaSheets;
 110: import javax.print.attribute.standard.JobMediaSheetsSupported;
 111: import javax.print.attribute.standard.JobName;
 112: import javax.print.attribute.standard.JobPriority;
 113: import javax.print.attribute.standard.JobPrioritySupported;
 114: import javax.print.attribute.standard.JobSheets;
 115: import javax.print.attribute.standard.Media;
 116: import javax.print.attribute.standard.MultipleDocumentHandling;
 117: import javax.print.attribute.standard.NumberUp;
 118: import javax.print.attribute.standard.NumberUpSupported;
 119: import javax.print.attribute.standard.OrientationRequested;
 120: import javax.print.attribute.standard.PageRanges;
 121: import javax.print.attribute.standard.PrintQuality;
 122: import javax.print.attribute.standard.PrinterName;
 123: import javax.print.attribute.standard.PrinterResolution;
 124: import javax.print.attribute.standard.PrinterURI;
 125: import javax.print.attribute.standard.RequestingUserName;
 126: import javax.print.attribute.standard.Sides;
 127: import javax.print.event.PrintServiceAttributeListener;
 128: 
 129: 
 130: /**
 131:  * Implementation of the PrintService interface
 132:  * for IPP based printers.
 133:  *
 134:  * @author Wolfgang Baer (WBaer@gmx.de)
 135:  */
 136: public class IppPrintService implements PrintService
 137: {
 138:   /**
 139:    * A Map with sets of attributes.
 140:    * key: A attribute category
 141:    * value: A set with values
 142:    *
 143:    * IPP may return sets of attributes e.g. for supported
 144:    * compression methods so we need to map to sets here.
 145:    */
 146:   private Map<Class<? extends Attribute>, Set<Attribute>> printerAttr;
 147: 
 148:   /** The set of listeners.*/
 149:   private HashSet<PrintServiceAttributeListener> printServiceAttributeListener;
 150: 
 151:   /** The username. */
 152:   private transient String user;
 153: 
 154:   /** The password of the user. */
 155:   private transient String passwd;
 156: 
 157:   /** The name of this print service. */
 158:   private String name;
 159: 
 160:   /** The list of supported document flavors. */
 161:   private List<DocFlavor> flavors;
 162: 
 163:   /** The standard printer URI. */
 164:   private PrinterURI printerUri;
 165: 
 166:   /** The list of all supported printer URIs. */
 167:   private ArrayList<PrinterURI> printerUris;
 168: 
 169:   /**
 170:    * Logger for tracing - enable by passing
 171:    * -Dgnu.classpath.debug.components=ipp to the vm.
 172:    */
 173:   static final Logger logger = SystemLogger.SYSTEM;
 174: 
 175:   /**
 176:    * requesting-user-name defaults to the executing user.
 177:    */
 178:   public static final RequestingUserName REQUESTING_USER_NAME;
 179: 
 180:   /**
 181:    * job-name defaults to "Java Printing".
 182:    */
 183:   public static final JobName JOB_NAME;
 184: 
 185:   static
 186:   {
 187:     JOB_NAME = new JobName("Java Printing", null);
 188:     REQUESTING_USER_NAME = new RequestingUserName(
 189:       SystemProperties.getProperty("user.name", ""), null);
 190:   }
 191: 
 192:   // TODO Implement service listener notification and change detection.
 193: 
 194:   /**
 195:    * Creates a <code>IppPrintService</code> object.
 196:    *
 197:    * @param uri the URI of the IPP printer.
 198:    * @param username the user of this print service.
 199:    * @param password the password of the user.
 200:    *
 201:    * @throws IppException if an error during connection occurs.
 202:    */
 203:   public IppPrintService(URI uri, String username, String password)
 204:     throws IppException
 205:   {
 206:     printerUri = new PrinterURI(uri);
 207:     user = username;
 208:     passwd = password;
 209: 
 210:     printServiceAttributeListener =
 211:       new HashSet<PrintServiceAttributeListener>();
 212: 
 213:     printerAttr = getPrinterAttributes();
 214:     processResponse();
 215:   }
 216: 
 217:   /**
 218:    * Fetches all printer attributes from the IPP printer.
 219:    *
 220:    * @return The Map with the printer attributes.
 221:    * @throws IppException if an error occurs.
 222:    */
 223:   private Map<Class<? extends Attribute>, Set<Attribute>> getPrinterAttributes()
 224:     throws IppException
 225:   {
 226:     IppResponse response = null;
 227: 
 228:     try
 229:       {
 230:         IppRequest request = new IppRequest(printerUri.getURI(), user, passwd);
 231: 
 232:         int operation = OperationsSupported.GET_PRINTER_ATTRIBUTES.getValue();
 233:         request.setOperationID((short) operation);
 234:         request.setOperationAttributeDefaults();
 235:         request.addOperationAttribute(printerUri);
 236: 
 237:         response = request.send();
 238:       }
 239:     catch (IOException e)
 240:       {
 241:         throw new IppException("IOException in IPP request/response.", e);
 242:       }
 243: 
 244:     return response.getPrinterAttributes().get(0);
 245:   }
 246: 
 247:   /**
 248:    * Extracts the set of attribute values for a given
 249:    * attribute category from the printer attributes map.
 250:    *
 251:    * @param attributeClass the category
 252:    * @return The set of attributes of the category.
 253:    */
 254:   private <T extends Attribute> Set<T> getPrinterAttributeSet(Class<T> attributeClass)
 255:   {
 256:     Set<Attribute> set = printerAttr.get(attributeClass);
 257:     Set<T> attSet = new HashSet<T>();
 258:     for (Attribute att : set)
 259:       attSet.add(attributeClass.cast(att));
 260:     return attSet;
 261:   }
 262: 
 263:   /**
 264:    * Extracts the default attribute value for the given
 265:    * default attribute category from the printer attributes map.
 266:    *
 267:    * @param attributeClass the category
 268:    * @return The default attribute.
 269:    *
 270:    * @throws ClassCastException if attributClass is not an
 271:    * instance of <code>DefaultValueAttribute</code>.
 272:    */
 273:   private Attribute getPrinterDefaultAttribute(Class<? extends Attribute> attributeClass)
 274:   {
 275:     Set<Attribute> set = printerAttr.get(attributeClass);
 276:     return ((DefaultValueAttribute) set.toArray()[0]).getAssociatedAttribute();
 277:   }
 278: 
 279:   /**
 280:    * Processes the response, sorts and splits the attributes.
 281:    */
 282:   private void processResponse()
 283:   {
 284:     // printer name
 285:     PrinterName[] tmp = getPrinterAttributeSet(PrinterName.class).toArray(new PrinterName[1]);
 286:     name = tmp[0].getValue();
 287: 
 288:     // supported flavors
 289:     // TODO Check if charsets-supported are charsets that are actually supported
 290:     // for text doc flavors as cups doesn't send charset parameters
 291: 
 292:     // utf-8 is supported at least - so we go with this only for now
 293:     flavors = new ArrayList<DocFlavor>();
 294:     Set<DocumentFormatSupported> flavorAttributes = getPrinterAttributeSet(DocumentFormatSupported.class);
 295:     if (flavorAttributes != null)
 296:       {
 297:         for (DocumentFormatSupported dfs : flavorAttributes)
 298:           {
 299:             String mimeType = dfs.getValue();
 300: 
 301:             if (mimeType.equals("text/plain"))
 302:               {
 303:                 flavors.add(DocFlavor.CHAR_ARRAY.TEXT_PLAIN);
 304:                 flavors.add(DocFlavor.READER.TEXT_PLAIN);
 305:                 flavors.add(DocFlavor.STRING.TEXT_PLAIN);
 306: 
 307:                 // add utf-8
 308:                 mimeType = mimeType + "; charset=utf-8";
 309:               }
 310:             else if (mimeType.equals("text/html"))
 311:               {
 312:                 flavors.add(DocFlavor.CHAR_ARRAY.TEXT_HTML);
 313:                 flavors.add(DocFlavor.READER.TEXT_HTML);
 314:                 flavors.add(DocFlavor.STRING.TEXT_HTML);
 315: 
 316:                 // add utf-8
 317:                 mimeType = mimeType + "; charset=utf-8";
 318:               }
 319: 
 320:             // Process the predefined DocFlavors and if mimetype is
 321:             // equal put them into the flavors array - otherwise
 322:             // just build them as binarie class representation.
 323:             boolean changed = false;
 324:             try
 325:               {
 326:                 Class<?>[] clazzes = new Class<?>[] { DocFlavor.BYTE_ARRAY.class,
 327:                     DocFlavor.INPUT_STREAM.class,
 328:                     DocFlavor.URL.class
 329:                     };
 330: 
 331:                 for (int j = 0; j < clazzes.length; j++)
 332:                   {
 333:                     Field[] fields = clazzes[j].getDeclaredFields();
 334:                     for (int i = 0; i < fields.length; i++)
 335:                       {
 336:                         if (fields[i].getType().equals(clazzes[j]))
 337:                           {
 338:                             DocFlavor flavor = (DocFlavor) fields[i].get(null);
 339:                             if (flavor.getMimeType().equals(mimeType))
 340:                               changed = flavors.add(flavor);
 341:                           }
 342:                       }
 343:                   }
 344:                 if (!changed) // not in predefined constants of DocFlavor
 345:                   {
 346:                     // everything should be supported as binary stuff
 347:                     flavors.add(new DocFlavor(mimeType, "[B"));
 348:                     flavors.add(new DocFlavor(mimeType, "java.io.InputStream"));
 349:                     flavors.add(new DocFlavor(mimeType, "java.net.URL"));
 350:                   }
 351:               }
 352:             catch (SecurityException e)
 353:               {
 354:                 // should not happen
 355:               }
 356:             catch (IllegalArgumentException e)
 357:               {
 358:                 // should not happen
 359:               }
 360:             catch (IllegalAccessException e)
 361:               {
 362:                 // should not happen, all fields are public
 363:               }
 364:           }
 365: 
 366:         if (this.getClass()
 367:             .isAssignableFrom(gnu.javax.print.CupsPrintService.class))
 368:           {
 369: //          CUPS always provides filters to convert from Postscript.
 370: //          This logic looks odd, but it's what OpenJDK does.
 371:             flavors.add(DocFlavor.SERVICE_FORMATTED.PAGEABLE);
 372:             flavors.add(DocFlavor.SERVICE_FORMATTED.PRINTABLE);
 373:           }
 374:       }
 375: 
 376:     // printer uris
 377:     Set<PrinterUriSupported> uris = getPrinterAttributeSet(PrinterUriSupported.class);
 378:     printerUris = new ArrayList<PrinterURI>(uris.size());
 379:     for (PrinterUriSupported uri : uris)
 380:       {
 381:         printerUris.add( new PrinterURI(uri.getURI()));
 382:       }
 383:   }
 384: 
 385:   /**
 386:    * We always return a implementation implementing CancelablePrintJob.
 387:    *
 388:    * @see javax.print.PrintService#createPrintJob()
 389:    */
 390:   public DocPrintJob createPrintJob()
 391:   {
 392:     return new DocPrintJobImpl(this, user, passwd);
 393:   }
 394: 
 395: 
 396:   /**
 397:    * @see javax.print.PrintService#getAttribute(java.lang.Class)
 398:    */
 399:   public <T extends PrintServiceAttribute> T getAttribute(Class<T> category)
 400:   {
 401:     if (category == null)
 402:       throw new NullPointerException("category may not be null");
 403: 
 404:     if (! PrintServiceAttribute.class.isAssignableFrom(category))
 405:       throw new IllegalArgumentException(
 406:          "category must be of type PrintServiceAttribute");
 407: 
 408:     Set<T> set = getPrinterAttributeSet(category);
 409:     if (set != null && set.size() > 0)
 410:       return set.iterator().next();
 411: 
 412:     return null;
 413:   }
 414: 
 415:   /**
 416:    * @see javax.print.PrintService#getAttributes()
 417:    */
 418:   public PrintServiceAttributeSet getAttributes()
 419:   {
 420:     PrintServiceAttributeSet set = new HashPrintServiceAttributeSet();
 421: 
 422:     for (Set<Attribute> attrSet : printerAttr.values())
 423:       {
 424:         for (Attribute attr : attrSet)
 425:           {
 426:             if (attr instanceof PrintServiceAttribute)
 427:               set.add(attr);
 428:           }
 429:       }
 430: 
 431:     return AttributeSetUtilities.unmodifiableView(set);
 432:   }
 433: 
 434:   /**
 435:    * @see javax.print.PrintService#getDefaultAttributeValue(java.lang.Class)
 436:    */
 437:   public Object getDefaultAttributeValue(Class<? extends Attribute> category)
 438:   {
 439:     // required attributes
 440:     if (category.equals(Fidelity.class))
 441:       return Fidelity.FIDELITY_FALSE;
 442:     if (category.equals(JobName.class))
 443:       return JOB_NAME;
 444:     if (category.equals(RequestingUserName.class))
 445:       return REQUESTING_USER_NAME;
 446: 
 447:     // optional attributes
 448:     if (category.equals(JobPriority.class)
 449:         && printerAttr.containsKey(JobPriorityDefault.class))
 450:       return getPrinterDefaultAttribute(JobPriorityDefault.class);
 451:     if (category.equals(JobHoldUntil.class)
 452:         && printerAttr.containsKey(JobHoldUntilDefault.class))
 453:       return getPrinterDefaultAttribute(JobHoldUntilDefault.class);
 454:     if (category.equals(JobSheets.class)
 455:         && printerAttr.containsKey(JobSheetsDefault.class))
 456:       return getPrinterDefaultAttribute(JobSheetsDefault .class);
 457:     if (category.equals(MultipleDocumentHandling.class)
 458:         && printerAttr.containsKey(MultipleDocumentHandlingDefault.class))
 459:       return getPrinterDefaultAttribute(MultipleDocumentHandlingDefault.class);
 460:     if (category.equals(Copies.class)
 461:         && printerAttr.containsKey(CopiesDefault.class))
 462:       return getPrinterDefaultAttribute(CopiesDefault.class);
 463:     if (category.equals(Finishings.class)
 464:         && printerAttr.containsKey(FinishingsDefault.class))
 465:       return getPrinterDefaultAttribute(FinishingsDefault.class);
 466:     if (category.equals(Sides.class)
 467:         && printerAttr.containsKey(SidesDefault.class))
 468:       return getPrinterDefaultAttribute(SidesDefault.class);
 469:     if (category.equals(NumberUp.class)
 470:         && printerAttr.containsKey(NumberUpDefault.class))
 471:       return getPrinterDefaultAttribute(NumberUpDefault.class);
 472:     if (category.equals(OrientationRequested.class)
 473:         && printerAttr.containsKey(OrientationRequestedDefault.class))
 474:       return getPrinterDefaultAttribute(OrientationRequestedDefault.class);
 475:     if (category.equals(Media.class)
 476:         && printerAttr.containsKey(MediaDefault.class))
 477:       return getPrinterDefaultAttribute(MediaDefault.class);
 478:     if (category.equals(PrinterResolution.class)
 479:         && printerAttr.containsKey(PrinterResolutionDefault.class))
 480:       return getPrinterDefaultAttribute(PrinterResolutionDefault.class);
 481:     if (category.equals(PrintQuality.class)
 482:         && printerAttr.containsKey(PrintQualityDefault.class))
 483:       return getPrinterDefaultAttribute(PrintQualityDefault.class);
 484:     if (category.equals(Compression.class)
 485:         && printerAttr.containsKey(CompressionSupported.class))
 486:       return Compression.NONE;
 487:     if (category.equals(PageRanges.class))
 488:       return new PageRanges(1, Integer.MAX_VALUE);
 489: 
 490:     return null;
 491:   }
 492: 
 493:   /**
 494:    * We return the value of <code>PrinterName</code> here.
 495:    * @see javax.print.PrintService#getName()
 496:    */
 497:   public String getName()
 498:   {
 499:     return name;
 500:   }
 501: 
 502:   /**
 503:    * We currently provide no factories - just returns null.
 504:    * @see javax.print.PrintService#getServiceUIFactory()
 505:    */
 506:   public ServiceUIFactory getServiceUIFactory()
 507:   {
 508:     // SUN does not provide any service factory for
 509:     // print services (tested on linux/windows)
 510: 
 511:     // for the moment we do the same - just return null
 512:     // later on we could provide at least the about UI dialog
 513:     return null;
 514:   }
 515: 
 516:   /**
 517:    * @see javax.print.PrintService#getSupportedAttributeCategories()
 518:    */
 519:   public Class<?>[] getSupportedAttributeCategories()
 520:   {
 521:     Set<Class<? extends Attribute>> categories =
 522:       new HashSet<Class<? extends Attribute>>();
 523: 
 524:     // Should only be job template attributes as of section 4.2
 525:     if (printerAttr.containsKey(JobPrioritySupported.class))
 526:       categories.add(JobPriority.class);
 527:     if (printerAttr.containsKey(JobHoldUntilSupported.class))
 528:       categories.add(JobHoldUntil.class);
 529:     if (printerAttr.containsKey(JobSheetsSupported.class))
 530:       categories.add(JobSheets.class);
 531:     if (printerAttr.containsKey(MultipleDocumentHandlingSupported.class))
 532:       categories.add(MultipleDocumentHandling.class);
 533:     if (printerAttr.containsKey(CopiesSupported.class))
 534:       categories.add(Copies.class);
 535:     if (printerAttr.containsKey(FinishingsSupported.class))
 536:       {
 537:         // if only none finishing is supported - it does not count as supported
 538:         Set<FinishingsSupported> set = getPrinterAttributeSet(FinishingsSupported.class);
 539:         if (! (set.size() == 1 && set.contains(FinishingsSupported.NONE)))
 540:           categories.add(Finishings.class);
 541:       }
 542:     if (printerAttr.containsKey(PageRangesSupported.class))
 543:       categories.add(PageRanges.class);
 544:     if (printerAttr.containsKey(SidesSupported.class))
 545:       categories.add(Sides.class);
 546:     if (printerAttr.containsKey(NumberUpSupported.class))
 547:       categories.add(NumberUp.class);
 548:     if (printerAttr.containsKey(OrientationRequestedSupported.class))
 549:       categories.add(OrientationRequested.class);
 550:     if (printerAttr.containsKey(MediaSupported.class))
 551:       categories.add(Media.class);
 552:     if (printerAttr.containsKey(PrinterResolutionSupported.class))
 553:       categories.add(PrinterResolution.class);
 554:     if (printerAttr.containsKey(PrintQualitySupported.class))
 555:       categories.add(PrintQuality.class);
 556: 
 557:     // Chromaticity, Destination, MediaPrintableArea,
 558:     // SheetCollate, PresentationDirection - not IPP attributes
 559: 
 560:     // attributes outside section 4.2
 561:     if (printerAttr.containsKey(CompressionSupported.class))
 562:       categories.add(Compression.class);
 563:     if (printerAttr.containsKey(JobImpressionsSupported.class))
 564:       categories.add(JobImpressions.class);
 565:     if (printerAttr.containsKey(JobKOctetsSupported.class))
 566:       categories.add(JobKOctets.class);
 567:     if (printerAttr.containsKey(JobMediaSheetsSupported.class))
 568:       categories.add(JobMediaSheets.class);
 569: 
 570:     // always supported as required by IPP specification
 571:     categories.add(Fidelity.class);
 572:     categories.add(JobName.class);
 573:     categories.add(RequestingUserName.class);
 574: 
 575:     return categories.toArray(new Class[categories.size()]);
 576:   }
 577: 
 578:   /**
 579:    * Implemented by a GetPrinterAttributes request. Subclasses providing supported
 580:    * attribute values totally different may override this methods. Subclass only in
 581:    * need of handling the response differently may override the method
 582:    * <code>handleSupportedAttributeValuesResponse(IppResponse, Class)</code> only.
 583:    *
 584:    * @see PrintService#getSupportedAttributeValues(Class, DocFlavor, AttributeSet)
 585:    * @see #handleSupportedAttributeValuesResponse(IppResponse, Class)
 586:    */
 587:   public Object getSupportedAttributeValues(Class<? extends Attribute> category,
 588:                                             DocFlavor flavor, AttributeSet attributes)
 589:   {
 590:     // We currently ignore the attribute set - there is nothing in the IPP
 591:     // specification which would come closer to what we do here.
 592: 
 593:     if (category == null)
 594:       throw new NullPointerException("category may not be null");
 595: 
 596:     if (!Attribute.class.isAssignableFrom(category))
 597:       throw new IllegalArgumentException("category must be of type Attribute");
 598: 
 599:     if (flavor != null && !isDocFlavorSupported(flavor))
 600:       throw new IllegalArgumentException("flavor is not supported");
 601: 
 602:     if (!isAttributeCategorySupported(category))
 603:       return null;
 604: 
 605:     // always supported
 606:     if (category.equals(Fidelity.class))
 607:       return new Fidelity[] { Fidelity.FIDELITY_FALSE, Fidelity.FIDELITY_TRUE };
 608:     if (category.equals(JobName.class))
 609:       return JOB_NAME;
 610:     if (category.equals(RequestingUserName.class))
 611:       return REQUESTING_USER_NAME;
 612: 
 613:     // map category to category-supported
 614:     String categoryName = IppUtilities.getSupportedAttrName(category);
 615: 
 616:     IppResponse response = null;
 617:     try
 618:       {
 619:         IppRequest request = new IppRequest(printerUri.getURI(), user, passwd);
 620:         request.setOperationID(
 621:           (short) OperationsSupported.GET_PRINTER_ATTRIBUTES.getValue());
 622:         request.setOperationAttributeDefaults();
 623:         request.addOperationAttribute(new RequestedAttributes(categoryName));
 624:         request.addOperationAttribute(printerUri);
 625: 
 626:         if (flavor != null)
 627:           {
 628:             DocumentFormat f = DocumentFormat.createDocumentFormat(flavor);
 629:             request.addOperationAttribute(f);
 630:           }
 631: 
 632:         response = request.send();
 633: 
 634:         int status = response.getStatusCode();
 635:         if (! (status == IppStatusCode.SUCCESSFUL_OK
 636:              || status == IppStatusCode.SUCCESSFUL_OK_IGNORED_OR_SUBSTITUED_ATTRIBUTES
 637:              || status == IppStatusCode.SUCCESSFUL_OK_CONFLICTING_ATTRIBUTES) )
 638:           {
 639:             logger.log(Component.IPP, "Statuscode not OK - got:" + status);
 640:           }
 641:       }
 642:     catch (IOException e)
 643:       {
 644:         // method cannot throw exception - just log
 645:         logger.log(Component.IPP, "IOException", e);
 646:       }
 647:     catch (IppException e)
 648:       {
 649:         // method cannot throw exception - just log
 650:         logger.log(Component.IPP, "IPPException", e);
 651:       }
 652: 
 653:     return handleSupportedAttributeValuesResponse(response, category);
 654:   }
 655: 
 656:   /**
 657:    * Called to handle the supported attribute values response for the given
 658:    * category. This might be overridden by subclasses with different requirements
 659:    * for parsing/handling the response from the GetPrinterAttributes.
 660:    *
 661:    * @param response the response of the GetPrinterAttributes IPP request
 662:    * @param category the category for which the supported values are requested
 663:    * @return A object indicating the supported values for the given attribute
 664:    * category, or <code>null</code> if this print service doesn't support the
 665:    * given attribute category at all.
 666:    *
 667:    * @see #getSupportedAttributeValues(Class, DocFlavor, AttributeSet)
 668:    */
 669:   protected Object handleSupportedAttributeValuesResponse(IppResponse response,
 670:                                                           Class<? extends Attribute> category)
 671:   {
 672:     List<Map<Class<? extends Attribute>, Set<Attribute>>> printerAtts =
 673:       response.getPrinterAttributes();
 674: 
 675:     // only one will be returned
 676:     Map<Class<? extends Attribute>, Set<Attribute>> printerAttribute = printerAtts.get(0);
 677:     Class<? extends Attribute> suppCategory = IppUtilities.getSupportedCategory(category);
 678:     Set<Attribute> attr = printerAttribute.get(suppCategory);
 679: 
 680:     // We sometime assume its a single instance with arbritrary value just indicating
 681:     // support or an array which is returned. This is because I sometimes just choosed
 682:     // what sounds right to me - as I have yet to find a printer which supports every
 683:     // special category in the SUN implementation to see what they return :-)
 684: 
 685:     //  Map whats in the JSP API
 686:     if (suppCategory.equals(JobPrioritySupported.class))
 687:       return (JobPrioritySupported) attr.iterator().next();
 688:     if (suppCategory.equals(JobHoldUntilSupported.class))
 689:       return new JobHoldUntil(new Date());
 690:     if (suppCategory.equals(JobSheetsSupported.class))
 691:       return JobSheetsSupported.getAssociatedAttributeArray(attr);
 692:     if (suppCategory.equals(MultipleDocumentHandlingSupported.class))
 693:       return MultipleDocumentHandlingSupported.getAssociatedAttributeArray(attr);
 694:     if (suppCategory.equals(CopiesSupported.class))
 695:       return (CopiesSupported) attr.iterator().next();
 696:     if (suppCategory.equals(FinishingsSupported.class))
 697:       return FinishingsSupported.getAssociatedAttributeArray(attr);
 698:     if (suppCategory.equals(PageRangesSupported.class))
 699:       return new PageRanges[] { new PageRanges(1, Integer.MAX_VALUE) };
 700:     if (suppCategory.equals(OrientationRequestedSupported.class))
 701:       return OrientationRequestedSupported.getAssociatedAttributeArray(attr);
 702:     if (suppCategory.equals(MediaSupported.class))
 703:       return MediaSupported.getAssociatedAttributeArray(attr);
 704:     if (suppCategory.equals(PrinterResolutionSupported.class))
 705:       return PrinterResolutionSupported.getAssociatedAttributeArray(attr);
 706:     if (suppCategory.equals(PrintQualitySupported.class))
 707:       return PrintQualitySupported.getAssociatedAttributeArray(attr);
 708:     if (suppCategory.equals(CompressionSupported.class))
 709:       return CompressionSupported.getAssociatedAttributeArray(attr);
 710:     // Special handling as it might also be in range of integers
 711:     if (suppCategory.equals(NumberUpSupported.class))
 712:       {
 713:         if (attr.size() == 1) // number-up maybe in rangeofintegers
 714:           return attr.iterator().next();
 715: 
 716:         int[][] members = new int[attr.size()][2];
 717:         Iterator<Attribute> it = attr.iterator();
 718:         for (int j = 0; j < attr.size(); j++)
 719:           {
 720:             int value = ((NumberUpSupported) it.next()).getMembers()[0][0];
 721:             members[j] = new int[] { value, value };
 722:           }
 723: 
 724:         NumberUpSupported supported = new NumberUpSupported(members);
 725:         return supported;
 726:       }
 727: 
 728:     return null;
 729:   }
 730: 
 731:   /**
 732:    * @see javax.print.PrintService#getSupportedDocFlavors()
 733:    */
 734:   public DocFlavor[] getSupportedDocFlavors()
 735:   {
 736:     return flavors.toArray(new DocFlavor[flavors.size()]);
 737:   }
 738: 
 739:   /**
 740:    * This is done by a validate-job operation and actually implemented in
 741:    * this generic IPP reference implementation. Subclasses which does
 742:    * not correctly support Validate-Job operation might want to override this.
 743:    *
 744:    * @see PrintService#getUnsupportedAttributes(DocFlavor, AttributeSet)
 745:    */
 746:   public AttributeSet getUnsupportedAttributes(DocFlavor flavor,
 747:                                                AttributeSet attributes)
 748:   {
 749:     if (flavor != null && !isDocFlavorSupported(flavor))
 750:       throw new IllegalArgumentException("flavor is not supported");
 751: 
 752:     IppResponse response = null;
 753:     try
 754:       {
 755:         IppRequest request = new IppRequest(printerUri.getURI(), user, passwd);
 756:         short operationId = (short) OperationsSupported.VALIDATE_JOB.getValue();
 757:         request.setOperationID(operationId);
 758:         request.setOperationAttributeDefaults();
 759:         request.addOperationAttribute(printerUri);
 760:         request.addOperationAttribute(Fidelity.FIDELITY_TRUE);
 761: 
 762:         if (attributes != null && attributes.size() > 0)
 763:           {
 764:             request.addAndFilterJobOperationAttributes(attributes);
 765:             request.addAndFilterJobTemplateAttributes(attributes);
 766:           }
 767: 
 768:         if (flavor != null)
 769:           {
 770:             DocumentFormat f = DocumentFormat.createDocumentFormat(flavor);
 771:             request.addOperationAttribute(f);
 772:           }
 773: 
 774:         response = request.send();
 775: 
 776:         int status = response.getStatusCode();
 777:         if (! (status == IppStatusCode.SUCCESSFUL_OK
 778:              || status == IppStatusCode.SUCCESSFUL_OK_IGNORED_OR_SUBSTITUED_ATTRIBUTES
 779:              || status == IppStatusCode.SUCCESSFUL_OK_CONFLICTING_ATTRIBUTES) )
 780:           {
 781:             logger.log(Component.IPP, "Statuscode not OK - got:" + status);
 782:           }
 783:       }
 784:     catch (IOException e)
 785:       {
 786:         // method cannot throw exception - just log
 787:         logger.log(Component.IPP, "IOException", e);
 788:       }
 789:     catch (IppException e)
 790:       {
 791:         // method cannot throw exception - just log
 792:         logger.log(Component.IPP, "IPPException", e);
 793:       }
 794: 
 795:     // Validate Jobs returns only Unsupported and Operation
 796:     List<Map<Class<? extends Attribute>, Set<Attribute>>> unsupportedMaps =
 797:       response.getUnsupportedAttributes();
 798:     if (unsupportedMaps.size() == 0)
 799:       return null;
 800: 
 801:     Map<Class<? extends Attribute>, Set<Attribute>> unsupportedAttr = unsupportedMaps.get(0);
 802:     if (unsupportedAttr.size() == 0)
 803:       return null;
 804: 
 805:     // Convert the return map with unsupported attributes
 806:     // into an AttribueSet instance
 807:     HashAttributeSet set = new HashAttributeSet();
 808:     for (Set<Attribute> unsupported : unsupportedAttr.values())
 809:       {
 810:         for (Attribute att : unsupported)
 811:           set.add(att);
 812:       }
 813: 
 814:     return set;
 815:   }
 816: 
 817:   /**
 818:    * @see PrintService#isAttributeCategorySupported(Class)
 819:    */
 820:   public boolean isAttributeCategorySupported(Class<? extends Attribute> category)
 821:   {
 822:     if (category == null)
 823:       throw new NullPointerException("category may not be null");
 824: 
 825:     if (! Attribute.class.isAssignableFrom(category))
 826:       throw new IllegalArgumentException("category must be of type Attribute");
 827: 
 828:     return Arrays.asList(getSupportedAttributeCategories()).contains(category);
 829:   }
 830: 
 831:   /**
 832:    * @see PrintService#isAttributeValueSupported(Attribute, DocFlavor, AttributeSet)
 833:    */
 834:   public boolean isAttributeValueSupported(Attribute attrval, DocFlavor flavor,
 835:                                            AttributeSet attributes)
 836:   {
 837:     // just redirect to getSupportedAttributeValues
 838:     Object values = getSupportedAttributeValues(attrval.getCategory(),
 839:                                                 flavor, attributes);
 840:     // null means none supported
 841:     if (values == null)
 842:       return false;
 843: 
 844:     // object may be an array
 845:     if (values.getClass().isArray())
 846:       return Arrays.asList((Object[]) values).contains(attrval);
 847: 
 848:     // may be a single instance of the category (value is irrelevant)
 849:     if (values.getClass().equals(attrval.getCategory()))
 850:       return true;
 851: 
 852:     // a single instance of another class to give the bounds
 853:     // copies
 854:     if (values.getClass().equals(CopiesSupported.class))
 855:       return ((CopiesSupported) values).contains((IntegerSyntax) attrval);
 856:     // number up
 857:     if (values.getClass().equals(NumberUpSupported.class))
 858:       return ((NumberUpSupported) values).contains((IntegerSyntax) attrval);
 859:     // job priority
 860:     if (values.getClass().equals(JobPrioritySupported.class))
 861:       {
 862:         JobPriority priority = (JobPriority) attrval;
 863:         JobPrioritySupported maxSupported = (JobPrioritySupported) values;
 864:         if (priority.getValue() < maxSupported.getValue())
 865:           return true;
 866:       }
 867: 
 868:     // I am unsure if these might also show up - not yet found a printer where
 869:     // Suns implementation supports them:
 870:     // JobImpressionsSupported, JobKOctetsSupported, JobMediaSheetsSupported
 871: 
 872:     return false;
 873:   }
 874: 
 875: 
 876:   /**
 877:    * @see javax.print.PrintService#isDocFlavorSupported(DocFlavor)
 878:    */
 879:   public boolean isDocFlavorSupported(DocFlavor flavor)
 880:   {
 881:     if (flavor == null)
 882:       throw new NullPointerException("DocFlavor may not be null.");
 883: 
 884:     return flavors.contains(flavor);
 885:   }
 886: 
 887: 
 888:   /**
 889:    * @see PrintService#addPrintServiceAttributeListener(PrintServiceAttributeListener)
 890:    */
 891:   public void addPrintServiceAttributeListener(
 892:     PrintServiceAttributeListener listener)
 893:   {
 894:     printServiceAttributeListener.add(listener);
 895:   }
 896: 
 897:   /**
 898:    * @see PrintService#removePrintServiceAttributeListener(PrintServiceAttributeListener)
 899:    */
 900:   public void removePrintServiceAttributeListener(
 901:     PrintServiceAttributeListener listener)
 902:   {
 903:     printServiceAttributeListener.remove(listener);
 904:   }
 905: 
 906:   /**
 907:    * Returns "IppPrinter: " + <code>getName()</code>
 908:    * @return The string representation.
 909:    */
 910:   public String toString()
 911:   {
 912:     return "IppPrinter: " + getName();
 913:   }
 914: 
 915:   /**
 916:    * Returns the printer-uri of this print service.
 917:    *
 918:    * @return The printer-uri attribute.
 919:    */
 920:   public PrinterURI getPrinterURI()
 921:   {
 922:     return printerUri;
 923:   }
 924: }