/* IppResponse.java --
Copyright (C) 2006 Free Software Foundation, Inc.
This file is part of GNU Classpath.
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
GNU Classpath is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
Linking this library statically or dynamically with other modules is
making a combined work based on this library. Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module. An independent module is a module which is not derived from
or based on this library. If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so. If you do not wish to do so, delete this
exception statement from your version. */
package gnu.javax.print.ipp;
import gnu.classpath.debug.Component;
import gnu.classpath.debug.SystemLogger;
import gnu.javax.print.ipp.attribute.UnknownAttribute;
import gnu.javax.print.ipp.attribute.defaults.DocumentFormatDefault;
import gnu.javax.print.ipp.attribute.defaults.JobHoldUntilDefault;
import gnu.javax.print.ipp.attribute.defaults.JobSheetsDefault;
import gnu.javax.print.ipp.attribute.defaults.MediaDefault;
import gnu.javax.print.ipp.attribute.defaults.PrinterResolutionDefault;
import gnu.javax.print.ipp.attribute.job.AttributesCharset;
import gnu.javax.print.ipp.attribute.job.AttributesNaturalLanguage;
import gnu.javax.print.ipp.attribute.job.JobMoreInfo;
import gnu.javax.print.ipp.attribute.job.JobPrinterUri;
import gnu.javax.print.ipp.attribute.job.JobUri;
import gnu.javax.print.ipp.attribute.printer.CharsetConfigured;
import gnu.javax.print.ipp.attribute.printer.DocumentFormat;
import gnu.javax.print.ipp.attribute.printer.NaturalLanguageConfigured;
import gnu.javax.print.ipp.attribute.printer.PrinterCurrentTime;
import gnu.javax.print.ipp.attribute.printer.PrinterDriverInstaller;
import gnu.javax.print.ipp.attribute.supported.CharsetSupported;
import gnu.javax.print.ipp.attribute.supported.DocumentFormatSupported;
import gnu.javax.print.ipp.attribute.supported.GeneratedNaturalLanguageSupported;
import gnu.javax.print.ipp.attribute.supported.JobHoldUntilSupported;
import gnu.javax.print.ipp.attribute.supported.JobSheetsSupported;
import gnu.javax.print.ipp.attribute.supported.MediaSupported;
import gnu.javax.print.ipp.attribute.supported.PrinterResolutionSupported;
import gnu.javax.print.ipp.attribute.supported.PrinterUriSupported;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.print.attribute.Attribute;
import javax.print.attribute.standard.CopiesSupported;
import javax.print.attribute.standard.DateTimeAtCompleted;
import javax.print.attribute.standard.DateTimeAtCreation;
import javax.print.attribute.standard.DateTimeAtProcessing;
import javax.print.attribute.standard.JobImpressionsSupported;
import javax.print.attribute.standard.JobKOctetsSupported;
import javax.print.attribute.standard.JobMediaSheetsSupported;
import javax.print.attribute.standard.JobStateReason;
import javax.print.attribute.standard.JobStateReasons;
import javax.print.attribute.standard.NumberUpSupported;
import javax.print.attribute.standard.PrinterMoreInfo;
import javax.print.attribute.standard.PrinterMoreInfoManufacturer;
import javax.print.attribute.standard.PrinterStateReason;
import javax.print.attribute.standard.PrinterStateReasons;
import javax.print.attribute.standard.Severity;
/**
* IppResponse
models a response received from an IPP
* compatible server as described in RFC 2910 IPP 1.1 Encoding and Transport.
*
* @author Wolfgang Baer (WBaer@gmx.de)
*/
public class IppResponse
{
/**
* ResponseReader
is responsible for parsing an IPP 1.1
* response stream. It provides access to the attribute groups after parsing
* via getter methods.
*
* The enconding of a response is structured as follows (for an official
* description please have a look at the RFC document mentioned above):
*
* - version-number - 2 bytes - required
* - status-code - 2 bytes - required
* - request-id - 4 bytes - required
* - attribute-group - n bytes - 0 or more
* - end-of-attributes-tag - 1 byte - required
* - data - q bytes - optional
*
*
* Where each attribute-group (if any) is encoded as follows:
*
* - begin-attribute-group-tag - 1 byte
* - attribute - p bytes - 0 or more
*
*
* Encoding of attributes:
*
* - attribute-with-one-value - q bytes
* - additional-value - r bytes - 0 or more
*
*
* Encoding of attribute-with-one-value:
*
* - value-tag - 1 byte
* - name-length (value is u) - 2 bytes
* - name - u bytes
* - value-length (value is v) - 2 bytes
* - value - v bytes
*
*
* Encoding of additional value:
*
* - value-tag - 1 byte
* - name-length (value is 0x0000) - 2 bytes
* - value-length (value is w) - 2 bytes
* - value - w bytes
*
*
*
* @author Wolfgang Baer (WBaer@gmx.de)
*/
class ResponseReader
{
/** The IPP version defaults to 1.1 */
private static final short VERSION = 0x0101;
/**
* Parses the inputstream containing the response of the IPP request.
* @param input the inputstream
* @throws IppException if unexpected exceptions occur.
* @throws IOException if IO problems with the underlying inputstream occur.
*/
public void parseResponse(InputStream input)
throws IppException, IOException
{
DataInputStream stream = new DataInputStream(input);
short version = stream.readShort();
status_code = stream.readShort();
request_id = stream.readInt();
if (VERSION != version)
throw new IppException("Version mismatch - "
+ "implementation does not support other versions than IPP 1.1");
logger.log(Component.IPP, "Statuscode: "
+ Integer.toHexString(status_code) + " Request-ID: " + request_id);
byte tag = 0;
boolean proceed = true;
HashMap, Set> tmp;
// iterate over attribute-groups until end-of-attributes-tag is found
while (proceed)
{
if (tag == 0) // only at start time
tag = stream.readByte();
logger.log(Component.IPP, "DelimiterTag: " + Integer.toHexString(tag));
// check if end of attributes
switch (tag)
{
case IppDelimiterTag.END_OF_ATTRIBUTES_TAG:
proceed = false;
break;
case IppDelimiterTag.OPERATION_ATTRIBUTES_TAG:
tmp = new HashMap, Set>();
tag = parseAttributes(tmp, stream);
operationAttributes.add(tmp);
break;
case IppDelimiterTag.JOB_ATTRIBUTES_TAG:
tmp = new HashMap, Set>();
tag = parseAttributes(tmp, stream);
jobAttributes.add(tmp);
break;
case IppDelimiterTag.PRINTER_ATTRIBUTES_TAG:
tmp = new HashMap, Set>();
tag = parseAttributes(tmp, stream);
printerAttributes.add(tmp);
break;
case IppDelimiterTag.UNSUPPORTED_ATTRIBUTES_TAG:
System.out.println("Called");
tmp = new HashMap, Set>();
tag = parseAttributes(tmp, stream);
unsupportedAttributes.add(tmp);
break;
default:
throw new IppException("Unknown tag with value "
+ Integer.toHexString(tag) + " occured.");
}
}
// if there are more bytes that has to be data.
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] readbuf = new byte[2048];
int len = 0;
while ((len = stream.read(readbuf)) > 0)
byteStream.write(readbuf, 0, len);
byteStream.flush();
data = byteStream.toByteArray();
}
/**
* The actual parsing of the attributes and further putting into the
* provided group maps.
* @param attributes the provided attribute group map.
* @param stream the provided stream to read from.
* @return The last read tag byte (normally a DelimiterTag)
* @throws IppException if unexpected exceptions occur.
* @throws IOException if IO problems with the underlying inputstream occur.
*/
private byte parseAttributes(Map, Set> attributes,
DataInputStream stream)
throws IppException, IOException
{
Attribute lastAttribute = null;
Attribute attribute = null;
// declaration of variables
short nameLength;
String name;
short valueLength;
byte[] value;
// tmp variables for parsing
// declared here so no name duplication occurs
URI uri;
String str;
while (true)
{
byte tag = stream.readByte();
if (IppDelimiterTag.isDelimiterTag(tag))
return tag;
// it must be a value tag now
// so we have either a attribute-with-one-value
// or (if setOf is possible) an additional-value
// (1) Length of the name
nameLength = stream.readShort();
// (2) The name itself
// may be an additional-value
if (nameLength == 0x0000)
name = lastAttribute.getName();
else
{
byte[] nameBytes = new byte[nameLength];
stream.read(nameBytes);
name = new String(nameBytes);
}
// (3) Length of the value
valueLength = stream.readShort();
// (4) The value itself
value = new byte[valueLength];
stream.read(value);
// the value itself
switch (tag)
{
// out-of-band values
case IppValueTag.UNSUPPORTED:
case IppValueTag.UNKNOWN:
// TODO implement out-of-band handling
// We currently throw an exception to see when it occurs - not yet :-)
throw new IppException(
"Unexpected name value for out-of-band value tag " + tag);
case IppValueTag.NO_VALUE:
attribute = null;
break;
case IppValueTag.INTEGER:
int intValue = IppUtilities.convertToInt(value);
attribute = IppUtilities.getIntegerAttribute(name, intValue);
break;
case IppValueTag.BOOLEAN:
// JPS API models boolean syntax type as enums
// 0x01 = true, 0x00 = false - all are enums
attribute = IppUtilities.getEnumAttribute(name, new Integer(value[0]));
break;
case IppValueTag.ENUM:
int intVal = IppUtilities.convertToInt(value);
attribute = IppUtilities.getEnumAttribute(name, new Integer(intVal));
break;
case IppValueTag.OCTECTSTRING_UNSPECIFIED:
// none exists according to spec
// so lets report as exception to see when it occurs
throw new IppException("Unspecified octet string occured.");
case IppValueTag.DATETIME:
Date date = parseDate(value);
if (name.equals("printer-current-time"))
attribute = new PrinterCurrentTime(date);
else if (name.equals("date-time-at-creation"))
attribute = new DateTimeAtCreation(date);
else if (name.equals("date-time-at-processing"))
attribute = new DateTimeAtProcessing(date);
else if (name.equals("date-time-at-completed"))
attribute = new DateTimeAtCompleted(date);
break;
case IppValueTag.RESOLUTION:
int crossFeed = IppUtilities.convertToInt(value[0], value[1], value[2], value[3]);
int feed = IppUtilities.convertToInt(value[4], value[5], value[6], value[7]);
int units = value[8];
if (name.equals("printer-resolution-default"))
attribute = new PrinterResolutionDefault(crossFeed, feed, units);
else if (name.equals("printer-resolution-supported")) // may be here also
attribute = new PrinterResolutionSupported(crossFeed, feed, units);
break;
case IppValueTag.RANGEOFINTEGER:
int lower = IppUtilities.convertToInt(value[0], value[1], value[2], value[3]);
int upper = IppUtilities.convertToInt(value[4], value[5], value[6], value[7]);
if (name.equals("copies-supported"))
attribute = new CopiesSupported(lower, upper);
else if (name.equals("number-up-supported"))
attribute = new NumberUpSupported(lower, upper);
else if (name.equals("job-k-octets-supported"))
attribute = new JobKOctetsSupported(lower, upper);
else if (name.equals("job-impressions-supported"))
attribute = new JobImpressionsSupported(lower, upper);
else if (name.equals("job-media-sheets-supported"))
attribute = new JobMediaSheetsSupported(lower, upper);
break;
case IppValueTag.TEXT_WITH_LANGUAGE:
case IppValueTag.TEXT_WITHOUT_LANGUAGE:
case IppValueTag.NAME_WITH_LANGUAGE:
case IppValueTag.NAME_WITHOUT_LANGUAGE:
attribute = IppUtilities.getTextAttribute(name, tag, value);
break;
case IppValueTag.KEYWORD:
str = new String(value);
if (name.equals("job-hold-until-supported")) // may also be name type
attribute = new JobHoldUntilSupported(str, null);
else if (name.equals("job-hold-until-default"))
attribute = new JobHoldUntilDefault(str, null);
else if (name.equals("media-supported"))
attribute = new MediaSupported(str, null);
else if (name.equals("media-default"))
attribute = new MediaDefault(str, null);
else if (name.equals("job-sheets-default"))
attribute = new JobSheetsDefault(str, null);
else if (name.equals("job-sheets-supported"))
attribute = new JobSheetsSupported(str, null);
else if (name.equals("job-state-reasons")) // setOf
attribute = parseJobStateReasons(value, lastAttribute);
else if (name.equals("printer-state-reasons")) // setOf
attribute = parsePrinterStateReasons(value, lastAttribute);
else
attribute = IppUtilities.getEnumAttribute(name, str);
// all other stuff is either an enum or needs to be mapped to an
// UnknownAttribute instance. Enums catched here are:
// ipp-versions-supported, pdl-override-supported, compression-supported
// uri-authentication-supported, uri-security-supported, sides-supported
// sides-default, multiple-document-handling-supported, multiple-document-handling-default
break;
case IppValueTag.URI:
try
{
uri = new URI(new String(value));
}
catch (URISyntaxException e)
{
throw new IppException("Wrong URI syntax encountered.", e);
}
if (name.equals("job-uri"))
attribute = new JobUri(uri);
else if (name.equals("job-printer-uri"))
attribute = new JobPrinterUri(uri);
else if (name.equals("job-more-info"))
attribute = new JobMoreInfo(uri);
else if (name.equals("printer-uri-supported")) // setOf
attribute = new PrinterUriSupported(uri);
else if (name.equals("printer-more-info"))
attribute = new PrinterMoreInfo(uri);
else if (name.equals("printer-driver-installer"))
attribute = new PrinterDriverInstaller(uri);
else if (name.equals("printer-more-info-manufacturer"))
attribute = new PrinterMoreInfoManufacturer(uri);
break;
case IppValueTag.URI_SCHEME:
// only one uri-scheme exists - and its an enum
if (name.equals("reference-uri-schemes-supported"))
attribute = IppUtilities.getEnumAttribute(name, new String(value));
break;
case IppValueTag.CHARSET:
str = new String(value);
if (name.equals("attributes-charset"))
attribute = new AttributesCharset(str);
else if (name.equals("charset-configured"))
attribute = new CharsetConfigured(str);
else if (name.equals("charset-supported")) // setOf
attribute = new CharsetSupported(str);
break;
case IppValueTag.NATURAL_LANGUAGE:
str = new String(value);
if (name.equals("attributes-natural-language"))
attribute = new AttributesNaturalLanguage(str);
else if (name.equals("natural-language-configured"))
attribute = new NaturalLanguageConfigured(str);
else if (name.equals("generated-natural-language-supported")) // setOf
attribute = new GeneratedNaturalLanguageSupported(str);
break;
case IppValueTag.MIME_MEDIA_TYPE:
str = new String(value);
if (name.equals("document-format-default"))
attribute = new DocumentFormatDefault(str, null);
else if (name.equals("document-format-supported")) // setOf
attribute = new DocumentFormatSupported(str, null);
else if (name.equals("document-format")) // setOf
attribute = new DocumentFormat(str, null);
break;
default:
throw new IppException("Unknown tag with value "
+ Integer.toHexString(tag) + " found.");
}
if (attribute == null)
attribute = new UnknownAttribute(tag, name, value);
addAttribute(attributes, attribute);
lastAttribute = attribute;
logger.log(Component.IPP, "Attribute: " + name
+ " Value: " + attribute.toString());
}
}
/**
* Adds a new attribute to the given attribute group. If this is the fist
* occurence of this attribute category a new set is created and associated
* with its category as key.
* @param attributeGroup
* the attribute group
* @param attribute
* the attribute to add
*/
private void addAttribute(Map, Set> attributeGroup,
Attribute attribute)
{
Class extends Attribute> clazz = attribute.getCategory();
Set attributeValues = attributeGroup.get(clazz);
if (attributeValues == null) // first attribute of this category
{
attributeValues = new HashSet();
attributeGroup.put(clazz, attributeValues);
}
attributeValues.add(attribute);
}
/**
* Parses a name with or without language attribute value from the byte[]
* and returns the result as an object[].
* @param value the byte[]
* @param lastAttr the last attribute
* @return The attribute.
*/
private PrinterStateReasons parsePrinterStateReasons(byte[] value, Attribute lastAttr)
{
String str = new String(value);
PrinterStateReasons attribute;
if (lastAttr instanceof PrinterStateReasons)
attribute = (PrinterStateReasons) lastAttr;
else
attribute = new PrinterStateReasons();
// special case indicating no reasons
if (str.equals("none"))
return attribute;
Severity severity = null;
PrinterStateReason reason = null;
if (str.endsWith(Severity.WARNING.toString()))
severity = Severity.WARNING;
else if (str.endsWith(Severity.REPORT.toString()))
severity = Severity.REPORT;
else if (str.endsWith(Severity.ERROR.toString()))
severity = Severity.ERROR;
if (severity != null)
str = str.substring(0, str.lastIndexOf('-'));
else // we must associate a severity
severity = Severity.REPORT;
reason = (PrinterStateReason)
IppUtilities.getEnumAttribute("printer-state-reason", str);
attribute.put(reason , severity);
return attribute;
}
/**
* Parses a name with or without language attribute value from the byte[]
* and returns the result as an object[].
* @param value the byte[]
* @param lastAttr the last attribute
* @return The attribute.
*/
private JobStateReasons parseJobStateReasons(byte[] value, Attribute lastAttr)
{
String str = new String(value);
JobStateReasons attribute;
if (lastAttr instanceof JobStateReasons)
attribute = (JobStateReasons) lastAttr;
else
attribute = new JobStateReasons();
// special case indicating no reasons
if (str.equals("none"))
return attribute;
JobStateReason reason = (JobStateReason)
IppUtilities.getEnumAttribute("job-state-reason", str);
attribute.add(reason);
return attribute;
}
/**
* Parses a DateTime syntax attribute and returns the constructed Date
* object.
*
* The syntax value is defined as 11 octets follwing the DateAndTime format
* of RFC 1903:
*
* - field | octets | contents | range
* - 1 | 1-2 | year | 0..65536
* - 2 | 3 | month | 1..12
* - 3 | 4 | day | 1..31
* - 4 | 5 | hour | 0..23
* - 5 | 6 | minutes | 0..59
* - 6 | 7 | seconds | 0..60 (use 60 for leap-second)
* - 7 | 8 | deci-seconds | 0..9
* - 8 | 9 | direction from UTC | '+' / '-'
* - 9 | 10 | hours from UTC | 0..11
* - 10 | 11 | minutes from UTC | 0..59
*
*
*
* @param value the byte[]
* @return The date object.
*/
private Date parseDate(byte[] value)
{
short year = IppUtilities.convertToShort(value[0], value[1]);
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, value[2]);
cal.set(Calendar.DAY_OF_MONTH, value[3]);
cal.set(Calendar.HOUR_OF_DAY, value[4]);
cal.set(Calendar.MINUTE, value[5]);
cal.set(Calendar.SECOND, value[6]);
cal.set(Calendar.MILLISECOND, value[7] * 100); // deci-seconds
// offset from timezone
int offsetMilli = value[9] * 3600000; // hours to millis
offsetMilli = offsetMilli + value[10] * 60000; // minutes to millis
if (((char) value[8]) == '-')
offsetMilli = offsetMilli * (-1);
cal.set(Calendar.ZONE_OFFSET, offsetMilli);
return cal.getTime();
}
}
/**
* Logger for tracing - enable by passing
* -Dgnu.classpath.debug.components=ipp to the vm.
*/
static final Logger logger = SystemLogger.SYSTEM;
URI uri;
short operation_id;
short status_code;
int request_id;
List