From 8320e90e09b4187f205dac909744ddc9525daab3 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 9 Sep 2023 13:27:59 +0300 Subject: [PATCH 01/47] eliminate package logging, replaced with java util logging --- build.gradle.kts | 19 +----- .../de/gmuth/http/HttpURLConnectionClient.kt | 32 +++++----- .../kotlin/de/gmuth/http/JavaHttpClient.kt | 5 +- .../kotlin/de/gmuth/ipp/client/CupsClient.kt | 21 +++---- .../kotlin/de/gmuth/ipp/client/CupsMarker.kt | 8 +-- .../de/gmuth/ipp/client/CupsPrinterType.kt | 14 ++--- .../kotlin/de/gmuth/ipp/client/IppClient.kt | 9 ++- .../de/gmuth/ipp/client/IppColorMode.kt | 9 ++- .../kotlin/de/gmuth/ipp/client/IppConfig.kt | 17 +++-- .../kotlin/de/gmuth/ipp/client/IppDocument.kt | 7 +-- .../gmuth/ipp/client/IppEventNotification.kt | 6 +- .../gmuth/ipp/client/IppExchangeException.kt | 10 +-- src/main/kotlin/de/gmuth/ipp/client/IppJob.kt | 7 ++- .../kotlin/de/gmuth/ipp/client/IppMedia.kt | 45 +++++++------ .../kotlin/de/gmuth/ipp/client/IppPrinter.kt | 63 ++++++++++--------- .../de/gmuth/ipp/client/IppSubscription.kt | 11 ++-- .../kotlin/de/gmuth/ipp/core/IppAttribute.kt | 10 +-- .../de/gmuth/ipp/core/IppAttributesGroup.kt | 8 +-- .../kotlin/de/gmuth/ipp/core/IppCollection.kt | 8 +-- .../de/gmuth/ipp/core/IppInputStream.kt | 18 +++--- .../kotlin/de/gmuth/ipp/core/IppMessage.kt | 9 ++- .../de/gmuth/ipp/core/IppOutputStream.kt | 7 +-- .../ipp/iana/IppRegistrationsSection2.kt | 8 ++- .../ipp/iana/IppRegistrationsSection6.kt | 4 +- .../kotlin/de/gmuth/log/AndroidLogAdapter.kt | 45 ------------- src/main/kotlin/de/gmuth/log/ConsoleLogger.kt | 33 ---------- .../de/gmuth/log/JavaUtilLoggingExtensions.kt | 53 ++++++++++++++++ src/main/kotlin/de/gmuth/log/JulAdapter.kt | 59 ----------------- src/main/kotlin/de/gmuth/log/JulHandler.kt | 28 --------- src/main/kotlin/de/gmuth/log/LogEvent.kt | 13 ---- src/main/kotlin/de/gmuth/log/Logger.kt | 51 --------------- src/main/kotlin/de/gmuth/log/Logging.kt | 34 ---------- src/main/kotlin/de/gmuth/log/Slf4jAdapter.kt | 41 ------------ .../kotlin/de/gmuth/http/HttpClientMock.kt | 7 +-- .../de/gmuth/ipp/client/CupsClientTests.kt | 10 +-- .../de/gmuth/ipp/client/IppClientTests.kt | 5 -- .../kotlin/de/gmuth/ipp/client/IppJobTests.kt | 9 +-- .../de/gmuth/ipp/client/IppPrinterTests.kt | 20 ++---- .../kotlin/de/gmuth/ipp/client/issueNo11.kt | 14 ++--- .../kotlin/de/gmuth/ipp/client/issueNo3.kt | 15 +++-- .../de/gmuth/ipp/core/IppAttributeTests.kt | 7 +-- .../gmuth/ipp/core/IppAttributesGroupTests.kt | 2 - .../ipp/core/IppExchangeExceptionTests.kt | 5 +- .../de/gmuth/ipp/core/IppInputStreamTests.kt | 8 +-- .../de/gmuth/ipp/core/IppOutputStreamTests.kt | 8 --- .../de/gmuth/ipp/core/IppRequestTests.kt | 12 +--- .../kotlin/de/gmuth/ipp/iana/CSVReader.kt | 7 ++- .../kotlin/de/gmuth/log/ConsoleHandler.kt | 16 +++++ src/test/kotlin/de/gmuth/log/Logging.kt | 31 +++++++++ .../de/gmuth/log/SimpleClassNameFormatter.kt | 26 ++++++++ src/test/kotlin/de/gmuth/log/StdoutHandler.kt | 21 +++++++ 51 files changed, 359 insertions(+), 576 deletions(-) delete mode 100644 src/main/kotlin/de/gmuth/log/AndroidLogAdapter.kt delete mode 100644 src/main/kotlin/de/gmuth/log/ConsoleLogger.kt create mode 100644 src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt delete mode 100644 src/main/kotlin/de/gmuth/log/JulAdapter.kt delete mode 100644 src/main/kotlin/de/gmuth/log/JulHandler.kt delete mode 100644 src/main/kotlin/de/gmuth/log/LogEvent.kt delete mode 100644 src/main/kotlin/de/gmuth/log/Logger.kt delete mode 100644 src/main/kotlin/de/gmuth/log/Logging.kt delete mode 100644 src/main/kotlin/de/gmuth/log/Slf4jAdapter.kt create mode 100644 src/test/kotlin/de/gmuth/log/ConsoleHandler.kt create mode 100644 src/test/kotlin/de/gmuth/log/Logging.kt create mode 100644 src/test/kotlin/de/gmuth/log/SimpleClassNameFormatter.kt create mode 100644 src/test/kotlin/de/gmuth/log/StdoutHandler.kt diff --git a/build.gradle.kts b/build.gradle.kts index e56cc27f..245ccf3d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,7 +14,7 @@ plugins { } group = "de.gmuth" -version = "2.5-SNAPSHOT" +version = "3.0-SNAPSHOT" repositories { mavenCentral() @@ -29,25 +29,8 @@ repositories { // } //} -// -> gradle.properties -val slf4jVersion: String by project -val androidVersion: String by project - dependencies { - //implementation(platform("org.jetbrains.kotlin:kotlin-bom")) - //implementation("org.jetbrains.kotlin:kotlin-stdlib") - //implementation("org.jetbrains.kotlin:kotlin-reflect") - //implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2") - - testImplementation("org.jetbrains.kotlin:kotlin-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit") - - //"slf4jSupportImplementation"("org.slf4j:slf4j-api:1.7.32") // pom.xml: scope=compile, optional=true - compileOnly("org.slf4j:slf4j-api:$slf4jVersion") - compileOnly("com.google.android:android:$androidVersion") // android.util.Log - - testRuntimeOnly("org.slf4j:slf4j-simple:$slf4jVersion") - //testRuntimeOnly("ch.qos.logback:logback-classic:1.2.3") } // gradlew clean -x test build publishToMavenLocal diff --git a/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt b/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt index d577341d..46bc8973 100644 --- a/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt +++ b/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt @@ -4,30 +4,30 @@ package de.gmuth.http * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.JulAdapter -import de.gmuth.log.JulHandler -import de.gmuth.log.Logging -import de.gmuth.log.Logging.LogLevel.* -import de.gmuth.log.Logging.createLogger +import de.gmuth.log.debug +import de.gmuth.log.error import java.io.OutputStream import java.net.HttpURLConnection import java.net.URI +import java.util.logging.Level +import java.util.logging.Logger.getLogger import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection class HttpURLConnectionClient(config: Http.Config = Http.Config()) : Http.Client(config) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) init { log.debug { "HttpURLConnectionClient created" } - if (config.debugLogging && createLogger != ::JulAdapter) { - // The JulHandler forwards ALL jul message to Logging - JulHandler.addToJulLogger("sun.net.www.protocol.http") - // The JulHandler does NOT use the jul output config - Logging.getLogger("sun.net.www.protocol.http.HttpURLConnection").logLevel = TRACE + /** if (config.debugLogging && createLogger != ::JulAdapter) { + // The JulHandler forwards ALL jul message to Logging + JulRedirectHandler.addToJulLogger("sun.net.www.protocol.http") + // The JulHandler does NOT use the jul output config + Logging.getLogger("sun.net.www.protocol.http.HttpURLConnection").logLevel = TRACE + } **/ + if (config.debugLogging) { + getLogger("sun.net.www.protocol.http.HttpURLConnection").level = Level.FINER } } @@ -53,9 +53,9 @@ class HttpURLConnectionClient(config: Http.Config = Http.Config()) : Http.Client writeContent(outputStream) for ((key, values) in headerFields) { val logLevel = when { - responseCode < 300 -> DEBUG - responseCode in 400..499 -> INFO - else -> WARN + responseCode < 300 -> Level.FINE + responseCode in 400..499 -> Level.INFO + else -> Level.WARNING } log.log(logLevel) { "$key = $values" } } diff --git a/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt b/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt index 804c65a4..57483b50 100644 --- a/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt +++ b/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt @@ -4,7 +4,7 @@ package de.gmuth.http * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.Logging +import de.gmuth.log.debug import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.OutputStream @@ -15,12 +15,13 @@ import java.net.http.HttpRequest import java.net.http.HttpRequest.BodyPublishers import java.net.http.HttpResponse.BodyHandlers import java.time.Duration +import java.util.logging.Logger.getLogger // requires Java >=11 class JavaHttpClient(config: Http.Config = Http.Config()) : Http.Client(config) { companion object { - val log = Logging.getLogger {} + val log = getLogger(JavaHttpClient::javaClass.name) fun isSupported() = try { HttpClient.newHttpClient() true diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index aed6d337..edc3166d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -11,12 +11,14 @@ import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging +import de.gmuth.log.debug +import de.gmuth.log.warn import java.io.File import java.io.InputStream import java.net.URI import java.time.Duration import java.util.concurrent.atomic.AtomicInteger +import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html open class CupsClient( @@ -26,10 +28,7 @@ open class CupsClient( ) { constructor(host: String = "localhost") : this(URI.create("ipp://$host")) - companion object { - val log = Logging.getLogger { } - } - + val log = getLogger(javaClass.name) var userName: String? by ippConfig::userName val httpConfig: Http.Config by httpClient::config var cupsClientWorkDirectory = File("cups-${cupsUri.host}") @@ -220,12 +219,12 @@ open class CupsClient( // https://github.com/apple/cups/issues/5919 log.info { "Waiting for CUPS to generate IPP Everywhere PPD." } - log.info { this } + log.info { this.toString() } do { Thread.sleep(1000) updateAttributes("printer-make-and-model") } while (!makeAndModel.text.lowercase().contains("everywhere")) - log.info { this } + log.info { this.toString() } // make printer permanent exchange( @@ -240,7 +239,7 @@ open class CupsClient( enable() resume() updateAttributes() - log.info { this } + log.info { this.toString() } } } @@ -264,7 +263,7 @@ open class CupsClient( "job-name", "job-state", "job-state-reasons", "number-of-documents", "document-count" ) ) - .onEach { log.info { it } } // job overview + .onEach { log.info { it.toString() } } // job overview .onEach { job -> // update attributes and lookup job owners if (updateJobAttributes) job.updateAttributes() job.getOriginatingUserNameOrAppleJobOwnerOrNull()?.let { jobOwners.add(it) } @@ -296,10 +295,10 @@ open class CupsClient( ) { createPrinterSubscription(whichJobEvents, notifyLeaseDuration = leaseDuration) .pollAndHandleNotifications(pollEvery, autoRenewSubscription = autoRenewLease) { event -> - log.info { event } + log.info { event.toString() } with(event.getJob()) { while (jobIsIncoming()) { - log.info { this } + log.info { toString() } Thread.sleep(1000) updateAttributes() } diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt index 9049cb2b..fe03926d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt @@ -4,7 +4,8 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.Logging +import de.gmuth.log.warn +import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html class CupsMarker( @@ -15,10 +16,6 @@ class CupsMarker( val highLevel: Int, val colorCode: String ) { - companion object { - val log = Logging.getLogger {} - } - val color: Color = Color.fromString(colorCode) fun levelPercent() = 100 * level / highLevel @@ -38,6 +35,7 @@ class CupsMarker( UNKNOWN("?"); companion object { + val log = getLogger(javaClass.name) fun fromString(code: String) = values().find { it.code == code } ?: UNKNOWN.apply { log.warn { "unknown color code: $code" } } } diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt index d3ad90fc..4ea4a2e5 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt @@ -1,17 +1,15 @@ package de.gmuth.ipp.client /** - * Copyright (c) 2020-2021 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.Logging +import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html class CupsPrinterType(val value: Int) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) enum class Capability(val bit: Int, val description: String) { IsAPrinterClass(0, "Is a printer class."), @@ -45,9 +43,9 @@ class CupsPrinterType(val value: Int) { } fun toSet(): Set = Capability - .values() - .filter { (value shr it.bit) and 1 == 1 } - .toSet() + .values() + .filter { (value shr it.bit) and 1 == 1 } + .toSet() fun contains(capability: Capability) = toSet().contains(capability) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt index 9873c328..22b34aed 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt @@ -14,10 +14,13 @@ import de.gmuth.ipp.core.IppStatus.ClientErrorBadRequest import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.Unsupported import de.gmuth.ipp.iana.IppRegistrationsSection2 -import de.gmuth.log.Logging +import de.gmuth.log.debug +import de.gmuth.log.trace +import de.gmuth.log.warn import java.io.File import java.net.URI import java.util.concurrent.atomic.AtomicInteger +import java.util.logging.Logger.getLogger typealias IppResponseInterceptor = (request: IppRequest, response: IppResponse) -> Unit @@ -26,6 +29,7 @@ open class IppClient( val httpConfig: Http.Config = Http.Config(), val httpClient: Http.Client = Http.defaultImplementation.createClient(httpConfig) ) { + val log = getLogger(javaClass.name) var saveMessages: Boolean = false var saveMessagesDirectory = File("ipp-messages") var responseInterceptor: IppResponseInterceptor? = null @@ -36,7 +40,6 @@ open class IppClient( } companion object { - val log = Logging.getLogger {} const val APPLICATION_IPP = "application/ipp" const val version = "2.5-SNAPSHOT" const val build = "2023" @@ -138,7 +141,7 @@ open class IppClient( "user '$requestingUserName' is unauthorized for operation '$operation' (status=$status)" } exceptionMessage?.run { - config.logDetails() + config.log(log) request.logDetails("IPP REQUEST: ") log.warn { "http response status: $status" } server?.let { log.warn { "ipp-server: $it" } } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt b/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt index db7ee31b..98bd0779 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt @@ -1,14 +1,15 @@ package de.gmuth.ipp.client /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppAttribute import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppTag.Keyword -import de.gmuth.log.Logging +import de.gmuth.log.warn +import java.util.logging.Logger.getLogger enum class IppColorMode(private val keyword: String) : IppAttributeBuilder { @@ -16,9 +17,7 @@ enum class IppColorMode(private val keyword: String) : IppAttributeBuilder { Color("color"), Monochrome("monochrome"); - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) override fun buildIppAttribute(printerAttributes: IppAttributesGroup): IppAttribute { // use job-creation-attributes-supported? // 5100.11 diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index 3be9e171..7ca5421e 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -4,8 +4,9 @@ package de.gmuth.ipp.client * Copyright (c) 2021-2023 Gerhard Muth */ -import de.gmuth.log.Logging import java.nio.charset.Charset +import java.util.logging.Logger +import java.util.logging.Logger.getLogger class IppConfig( var userName: String? = System.getProperty("user.name"), @@ -13,14 +14,12 @@ class IppConfig( var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", ) { - companion object { - val log = Logging.getLogger {} - } + //val log = getLogger(javaClass.name) - fun logDetails() { - log.info { "userName: $userName" } - log.info { "ippVersion: $ippVersion" } - log.info { "charset: ${charset.name().lowercase()}" } - log.info { "naturalLanguage: $naturalLanguage" } + fun log(log: Logger) = log.run { + info { "userName: $userName" } + info { "ippVersion: $ippVersion" } + info { "charset: ${charset.name().lowercase()}" } + info { "naturalLanguage: $naturalLanguage" } } } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index 5ff08218..14d70c16 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -6,10 +6,11 @@ package de.gmuth.ipp.client import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppString -import de.gmuth.log.Logging +import de.gmuth.log.debug import java.io.File import java.io.IOException import java.io.InputStream +import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory class IppDocument( @@ -17,9 +18,7 @@ class IppDocument( val attributes: IppAttributesGroup, val inputStream: InputStream ) { - companion object { - val log = Logging.getLogger { } - } + val log = getLogger(javaClass.name) val number: Int get() = attributes.getValue("document-number") diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt index 0813ae09..f4eaf917 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt @@ -6,15 +6,13 @@ package de.gmuth.ipp.client import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppString -import de.gmuth.log.Logging +import java.util.logging.Logger.getLogger class IppEventNotification( val subscription: IppSubscription, val attributes: IppAttributesGroup ) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) val sequenceNumber: Int get() = attributes.getValue("notify-sequence-number") diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt index 3daaeedb..8036a8d1 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt @@ -9,8 +9,9 @@ import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound -import de.gmuth.log.Logging import java.io.File +import java.util.logging.Logger +import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory import kotlin.io.path.pathString @@ -30,8 +31,9 @@ open class IppExchangeException( } } + val log = getLogger(javaClass.name) + companion object { - val log = Logging.getLogger {} fun defaultMessage(request: IppRequest, response: IppResponse?) = StringBuilder().apply { append("${request.operation} failed") response?.run { @@ -47,8 +49,8 @@ open class IppExchangeException( fun statusIs(status: IppStatus) = response?.status == status - fun logDetails() { - if (httpStatus != null) log.info { "HTTP-STATUS: $httpStatus" } + fun log(logger: Logger) = logger.run { + if (httpStatus != null) info { "HTTP-STATUS: $httpStatus" } request.logDetails(" REQUEST: ") response?.logDetails("RESPONSE: ") } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index 1ad00325..8494f4ae 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -8,11 +8,13 @@ import de.gmuth.ipp.client.IppJobState.* import de.gmuth.ipp.core.* import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging.getLogger +import de.gmuth.log.debug +import de.gmuth.log.warn import java.io.File import java.io.FileInputStream import java.io.InputStream import java.net.URI +import java.util.logging.Logger.getLogger class IppJob( val printer: IppPrinter, @@ -21,11 +23,12 @@ class IppJob( ) { companion object { - val log = getLogger {} + var defaultDelayMillis: Long = 3000 var useJobOwnerAsUserName: Boolean = false } + val log = getLogger(javaClass.name) var subscription: IppSubscription? = subscriptionAttributes?.let { IppSubscription(printer, it) } val ippConfig = printer.ippConfig diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt b/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt index c718eaa5..725d7712 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt @@ -9,23 +9,28 @@ import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppCollection import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging +import de.gmuth.log.debug +import de.gmuth.log.warn +import java.util.logging.Logger.getLogger object IppMedia { - val log = Logging.getLogger {} + val log = getLogger(javaClass.name) // unit: 1/100 mm, e.g. 2540 = 1 inch class Size(val xDimension: Int, val yDimension: Int) : IppAttributeBuilder { override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = - IppAttribute("media-size", BegCollection, IppCollection( - IppAttribute("x-dimension", Integer, xDimension), - IppAttribute("y-dimension", Integer, yDimension) - )) + IppAttribute( + "media-size", BegCollection, IppCollection( + IppAttribute("x-dimension", Integer, xDimension), + IppAttribute("y-dimension", Integer, yDimension) + ) + ) } - class Margins(left: Int? = null, right: Int? = null, top: Int? = null, bottom: Int? = null) : ArrayList>() { + class Margins(left: Int? = null, right: Int? = null, top: Int? = null, bottom: Int? = null) : + ArrayList>() { constructor(margin: Int) : this(margin, margin, margin, margin) init { @@ -38,23 +43,23 @@ object IppMedia { // PWG 5100.3, 3.13 class Collection( - var size: Size? = null, - var margins: Margins? = null, - var source: String? = null, - var type: String? = null + var size: Size? = null, + var margins: Margins? = null, + var source: String? = null, + var type: String? = null ) : IppAttributeBuilder { override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = - IppAttribute("media-col", BegCollection, IppCollection().apply { - if (source != null) { - checkIfSourceIsSupported(printerAttributes) - addAttribute("media-source", Keyword, source!!) - } - type?.let { addAttribute("media-type", Keyword, it) } - size?.let { add(it.buildIppAttribute(printerAttributes)) } - margins?.let { addAll(it) } - }) + IppAttribute("media-col", BegCollection, IppCollection().apply { + if (source != null) { + checkIfSourceIsSupported(printerAttributes) + addAttribute("media-source", Keyword, source!!) + } + type?.let { addAttribute("media-type", Keyword, it) } + size?.let { add(it.buildIppAttribute(printerAttributes)) } + margins?.let { addAll(it) } + }) private fun checkIfSourceIsSupported(printerAttributes: IppAttributesGroup) { val mediaSourceSupported = printerAttributes["media-source-supported"] diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 4a723b31..50e2135d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -11,14 +11,17 @@ import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2 -import de.gmuth.log.Logging -import de.gmuth.log.Logging.LogLevel.ERROR +import de.gmuth.log.* +//import de.gmuth.log.Logger.Level.* +import java.util.logging.Logger.getLogger +//import de.gmuth.log.JavaUtilLogging.getLogger import java.io.* import java.net.URI import java.time.Duration +import java.util.logging.Logger @SuppressWarnings("kotlin:S1192") -open class IppPrinter( +class IppPrinter( val printerUri: URI, var attributes: IppAttributesGroup = IppAttributesGroup(Printer), httpConfig: Http.Config = Http.Config(), @@ -28,6 +31,32 @@ open class IppPrinter( requestedAttributesOnInit: List? = null ) { var workDirectory: File = File("work") + //val log = getLogger(javaClass.canonicalName) + val log = getLogger(javaClass.name) + + companion object { + //val log = JUL2.getLogger {} + + val printerStateAttributes = listOf( + "printer-is-accepting-jobs", "printer-state", "printer-state-reasons" + ) + + val printerClassAttributes = listOf( + "printer-name", + "printer-make-and-model", + "printer-is-accepting-jobs", + "printer-state", + "printer-state-reasons", + "document-format-supported", + "operations-supported", + "color-supported", + "sides-supported", + "media-supported", + "media-ready", + "media-default", + "ipp-versions-supported" + ) + } init { log.debug { "create IppPrinter for $printerUri" } @@ -46,11 +75,11 @@ open class IppPrinter( if (ippExchangeException.statusIs(ClientErrorNotFound)) log.error { ippExchangeException.message } else { - log.logWithCauseMessages(ippExchangeException, ERROR) + //log.logWithCauseMessages(ippExchangeException, ERROR) log.error { "failed to get printer attributes on init" } ippExchangeException.response?.let { if (it.containsGroup(Printer)) log.info { "${it.printerGroup.size} attributes parsed" } - else log.warn { it } + else log.warn { it.toString() } } try { fetchRawPrinterAttributes("getPrinterAttributesFailed.bin") @@ -74,30 +103,6 @@ open class IppPrinter( constructor(printerUri: String) : this(URI.create(printerUri)) constructor(printerUri: String, ippConfig: IppConfig) : this(URI.create(printerUri), ippConfig = ippConfig) - companion object { - val log = Logging.getLogger {} - - val printerStateAttributes = listOf( - "printer-is-accepting-jobs", "printer-state", "printer-state-reasons" - ) - - val printerClassAttributes = listOf( - "printer-name", - "printer-make-and-model", - "printer-is-accepting-jobs", - "printer-state", - "printer-state-reasons", - "document-format-supported", - "operations-supported", - "color-supported", - "sides-supported", - "media-supported", - "media-ready", - "media-default", - "ipp-versions-supported" - ) - } - val ippConfig: IppConfig get() = ippClient.config diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt index 433c6dab..94f2c759 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt @@ -11,19 +11,18 @@ import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppString import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging +import de.gmuth.log.warn import java.time.Duration import java.time.Duration.ofSeconds import java.time.LocalDateTime import java.time.LocalDateTime.now +import java.util.logging.Logger.getLogger class IppSubscription( val printer: IppPrinter, var attributes: IppAttributesGroup ) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) private var lastSequenceNumber: Int = 0 private var leaseStartedAt = now() @@ -130,7 +129,7 @@ class IppSubscription( fun pollAndHandleNotifications( pollEvery: Duration = ofSeconds(5), // should be larger than 1s autoRenewSubscription: Boolean = false, - handleNotification: (event: IppEventNotification) -> Unit = { log.info { it } } + handleNotification: (event: IppEventNotification) -> Unit = { log.info { it.toString() } } ) { fun expiresAfterDelay() = !leaseDuration.isZero && now().plus(pollEvery).isAfter(expiresAt.minusSeconds(2)) try { @@ -142,7 +141,7 @@ class IppSubscription( Thread.sleep(pollEvery.toMillis()) } while (pollHandlesNotifications) } catch (clientErrorNotFoundException: ClientErrorNotFoundException) { - log.info { clientErrorNotFoundException.response!!.statusMessage } + log.info { clientErrorNotFoundException.response!!.statusMessage.toString() } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt index 01f29cc3..415da351 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt @@ -7,14 +7,13 @@ package de.gmuth.ipp.core import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2.attributeIs1setOf import de.gmuth.ipp.iana.IppRegistrationsSection6.getEnumName -import de.gmuth.log.Logging +import de.gmuth.log.warn import java.nio.charset.Charset +import java.util.logging.Logger.getLogger data class IppAttribute constructor(val name: String, val tag: IppTag) : IppAttributeBuilder { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) val values: MutableCollection = mutableListOf() @@ -68,7 +67,8 @@ data class IppAttribute constructor(val name: String, val tag: IppTag) : IppA else -> enumNameOrValue(value as Any).toString() } - fun enumNameOrValue(value: Any) = if (tag == IppTag.Enum) getEnumName(name, value) else value + fun enumNameOrValue(value: Any) = + if (tag == IppTag.Enum) getEnumName(name, value) else value fun logDetails(prefix: String = "") { val string = toString() diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index afd7cb70..73e0fd17 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -4,14 +4,14 @@ package de.gmuth.ipp.core * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.Logging +import de.gmuth.log.debug +import de.gmuth.log.warn import java.io.File +import java.util.logging.Logger.getLogger open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap>() { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) init { if (!tag.isGroupTag()) throw IppException("'$tag' is not a group tag") diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt index 80f016fc..b663a864 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt @@ -1,17 +1,15 @@ package de.gmuth.ipp.core +import java.util.logging.Logger.getLogger + /** * Copyright (c) 2020-2022 Gerhard Muth */ -import de.gmuth.log.Logging - // RFC8010 3.1.6. data class IppCollection(val members: MutableCollection> = mutableListOf()) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) constructor(vararg attributes: IppAttribute<*>) : this(attributes.toMutableList()) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index 79546b50..24d3bad5 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -6,20 +6,20 @@ package de.gmuth.ipp.core import de.gmuth.io.hexdump import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging -import de.gmuth.log.Logging.LogLevel.DEBUG -import de.gmuth.log.Logging.LogLevel.WARN +import de.gmuth.log.debug +import de.gmuth.log.trace +import de.gmuth.log.warn import java.io.BufferedInputStream import java.io.DataInputStream import java.io.EOFException import java.net.URI import java.nio.charset.Charset +import java.util.logging.Level +import java.util.logging.Logger.getLogger class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputStream) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) // character encoding for text and name attributes, RFC 8011 4.1.4.1 internal lateinit var attributesCharset: Charset @@ -45,14 +45,14 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt tag.isGroupTag() -> { currentGroup = message.createAttributesGroup(tag) } - tag.isValueTag() -> { val attribute = readAttribute(tag) - log.debug { "$attribute" } if (attribute.name.isNotEmpty()) { + log.debug { attribute.toString() } currentGroup.put(attribute, onReplaceWarn = true) currentAttribute = attribute } else { // name.isEmpty() -> 1setOf + log.debug { IppAttribute(currentAttribute.name, attribute.tag, attribute.value).toString() } currentAttribute.additionalValue(attribute) } } @@ -173,7 +173,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt else -> { // ByteArray - possibly empty readLengthAndValue().apply { if (isNotEmpty()) { - val level = if (tag == Unsupported_) DEBUG else WARN + val level = if (tag == Unsupported_) Level.FINE else Level.WARNING log.log(level) { "ignore $size value bytes tagged '$tag'" } hexdump { log.log(level) { it } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index aded5f1c..89be7df7 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -7,11 +7,14 @@ package de.gmuth.ipp.core import de.gmuth.io.ByteArraySavingInputStream import de.gmuth.io.ByteArraySavingOutputStream import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging +import de.gmuth.log.debug +import de.gmuth.log.warn import java.io.* +import java.util.logging.Logger.getLogger abstract class IppMessage() { + val log = getLogger(javaClass.name) var code: Short? = null var requestId: Int? = null var version: String? = null @@ -26,10 +29,6 @@ abstract class IppMessage() { abstract val codeDescription: String // request operation or response status - companion object { - val log = Logging.getLogger {} - } - constructor(version: String, requestId: Int, charset: java.nio.charset.Charset, naturalLanguage: String) : this() { this.version = version this.requestId = requestId diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt index d524cded..1a9a8f2e 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt @@ -5,17 +5,16 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging +import de.gmuth.log.debug import java.io.DataOutputStream import java.io.OutputStream import java.net.URI import java.nio.charset.Charset +import java.util.logging.Logger.getLogger class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStream) { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) // charset for text and name attributes, rfc 8011 4.1.4.1 internal lateinit var attributesCharset: Charset diff --git a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt index 4c3c0d6c..2262fa58 100644 --- a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt +++ b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt @@ -10,13 +10,17 @@ import de.gmuth.ipp.core.IppCollection import de.gmuth.ipp.core.IppMessage import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging +import de.gmuth.log.trace +import de.gmuth.log.warn +import java.util.logging.Logger.getLogger /** * https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-2 */ object IppRegistrationsSection2 { + val log = getLogger(javaClass.name) + data class Attribute( val collection: String, val name: String, @@ -74,8 +78,6 @@ object IppRegistrationsSection2 { } - val log = Logging.getLogger {} - // source: https://www.iana.org/assignments/ipp-registrations/ipp-registrations-2.csv val allAttributes = CSVTable("/ipp-registrations-2.csv", ::Attribute).rows diff --git a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt index 50107c34..a74bb26f 100644 --- a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt +++ b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.iana import de.gmuth.csv.CSVTable -import de.gmuth.log.Logging +import java.util.logging.Logger.getLogger /** * https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml#ipp-registrations-6 @@ -26,7 +26,7 @@ object IppRegistrationsSection6 { override fun toString() = "$attribute/$value ($syntax) = $name $reference " } - val log = Logging.getLogger {} + val log = getLogger(javaClass.name) // source: https://www.iana.org/assignments/ipp-registrations/ipp-registrations-6.csv val allEnumAttributeValues = CSVTable("/ipp-registrations-6.csv", ::EnumAttributeValue).rows diff --git a/src/main/kotlin/de/gmuth/log/AndroidLogAdapter.kt b/src/main/kotlin/de/gmuth/log/AndroidLogAdapter.kt deleted file mode 100644 index 030c0a85..00000000 --- a/src/main/kotlin/de/gmuth/log/AndroidLogAdapter.kt +++ /dev/null @@ -1,45 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2023 Gerhard Muth - */ - -import android.util.Log -import de.gmuth.log.Logging.LogLevel -import de.gmuth.log.Logging.LogLevel.* -import de.gmuth.log.Logging.createLogger - -// https://developer.android.com/reference/android/util/Log -class AndroidLogAdapter(name: String) : Logger(name) { - - companion object { - fun configure() { - createLogger = ::AndroidLogAdapter - } - } - - override fun isEnabled(level: LogLevel) = - level != OFF && Log.isLoggable(name, level.toInt()) - - override fun publish(logEvent: LogEvent) { - logEvent.run { - when (logLevel) { - OFF -> Unit // don't publish anything - TRACE -> Log.v(name, messageString, throwable) - DEBUG -> Log.d(name, messageString, throwable) - INFO -> Log.i(name, messageString, throwable) - WARN -> Log.w(name, messageString, throwable) - ERROR -> Log.e(name, messageString, throwable) - } - } - } - - private fun LogLevel.toInt() = when (this) { - OFF -> throw IllegalArgumentException("OFF") - TRACE -> Log.VERBOSE - DEBUG -> Log.DEBUG - INFO -> Log.INFO - WARN -> Log.WARN - ERROR -> Log.ERROR - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/ConsoleLogger.kt b/src/main/kotlin/de/gmuth/log/ConsoleLogger.kt deleted file mode 100644 index e9e3dd3d..00000000 --- a/src/main/kotlin/de/gmuth/log/ConsoleLogger.kt +++ /dev/null @@ -1,33 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2021-2023 Gerhard Muth - */ - -import java.io.PrintWriter -import java.time.LocalDateTime.now - -class ConsoleLogger(name: String) : Logger(name) { - - companion object { - var defaultLogLevel = Logging.LogLevel.INFO - // %1=timestamp, %2=loggerName, %3=level, %4=message - var format: String = "%1\$tT.%1\$tL %2\$-25s %3\$-10s %4\$s%n" - var simpleClassName = true - fun configure() { - Logging.createLogger = ::ConsoleLogger - } - } - - init { - logLevel = defaultLogLevel - } - - override fun publish(logEvent: LogEvent) { - val loggerName = if (simpleClassName) name.substringAfterLast(".") else name - logEvent.run { - print(format.format(now(), loggerName, logLevel, messageString)) - throwable?.printStackTrace(PrintWriter(System.out, true)) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt b/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt new file mode 100644 index 00000000..dd6f95dd --- /dev/null +++ b/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt @@ -0,0 +1,53 @@ +package de.gmuth.log + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import java.util.function.Supplier +import java.util.logging.Level.* +import java.util.logging.Logger + +fun Logger.finest(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(FINEST, thrown, msgSupplier) + +fun Logger.finer(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(FINER, thrown, msgSupplier) + +fun Logger.fine(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(FINE, thrown, msgSupplier) + +fun Logger.config(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(CONFIG, thrown, msgSupplier) + +fun Logger.info(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(INFO, thrown, msgSupplier) + +fun Logger.warning(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(WARNING, thrown, msgSupplier) + +fun Logger.severe(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = + log(SEVERE, thrown, msgSupplier) + +// ----- aliases ----- + +// trace --> finer +fun Logger.trace(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = + if (thrown == null) finer(msgSupplier) else finer(thrown, msgSupplier) + +// debug --> fine +fun Logger.debug(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = + if (thrown == null) fine(msgSupplier) else fine(thrown, msgSupplier) + +// warn --> warning +fun Logger.warn(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = + if (thrown == null) warning(msgSupplier) else warning(thrown, msgSupplier) + +// error --> severe +fun Logger.error(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = + if (thrown == null) severe(msgSupplier) else severe(thrown, msgSupplier) + +fun Logger.logWithCauseMessages(throwable: Throwable) { + throwable.cause?.let { logWithCauseMessages(it) } + log(level) { "${throwable.javaClass.name}: ${throwable.message}" } +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/JulAdapter.kt b/src/main/kotlin/de/gmuth/log/JulAdapter.kt deleted file mode 100644 index 9a320925..00000000 --- a/src/main/kotlin/de/gmuth/log/JulAdapter.kt +++ /dev/null @@ -1,59 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2022-2023 Gerhard Muth - */ - -import de.gmuth.log.Logging.LogLevel -import de.gmuth.log.Logging.LogLevel.* -import de.gmuth.log.Logging.createLogger -import java.util.logging.Level -import java.util.logging.LogManager - -// forward log messages to java util logging -// https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html -class JulAdapter(name: String) : Logger(name) { - - companion object { - fun configure() { - createLogger = ::JulAdapter - } - - fun configureLogManager(configResource: String = "/ippclient-logging.properties") = - LogManager.getLogManager().readConfiguration(JulAdapter::class.java.getResourceAsStream(configResource)) - } - - private val julLogger = java.util.logging.Logger.getLogger(name) - - override var logLevel: LogLevel - get() = julLogger.level.toLogLevel() - set(value) { - julLogger.level = value.toJulLevel() - } - - override fun isEnabled(level: LogLevel) = - julLogger.isLoggable(level.toJulLevel()) - - override fun publish(logEvent: LogEvent) = logEvent.run { - julLogger.log(logLevel.toJulLevel(), messageString, throwable) - } - - fun LogLevel.toJulLevel(): Level = when (this) { - OFF -> Level.OFF - TRACE -> Level.FINER - DEBUG -> Level.FINE - INFO -> Level.INFO - WARN -> Level.WARNING - ERROR -> Level.SEVERE - } -} - -fun Level.toLogLevel() = when (this) { - Level.OFF -> OFF - Level.FINER, Level.FINEST, Level.ALL -> TRACE - Level.FINE -> DEBUG - Level.INFO, Level.CONFIG -> INFO - Level.WARNING -> WARN - Level.SEVERE -> ERROR - else -> throw IllegalArgumentException("unknown Level $this") -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/JulHandler.kt b/src/main/kotlin/de/gmuth/log/JulHandler.kt deleted file mode 100644 index dc6b3e5a..00000000 --- a/src/main/kotlin/de/gmuth/log/JulHandler.kt +++ /dev/null @@ -1,28 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2022-2023 Gerhard Muth - */ - -import java.util.logging.Handler -import java.util.logging.Level -import java.util.logging.LogRecord -import java.util.logging.Logger - -// redirect java util logging messages to de.gmuth.log.Logging -object JulHandler : Handler() { - - override fun publish(logRecord: LogRecord) = logRecord.run { - Logging.getLogger(loggerName).log(level.toLogLevel(), thrown) { message } - } - - override fun flush() = Unit - override fun close() = Unit - - fun addToJulLogger(name: String = "", julLevel: Level = Level.ALL) { - Logger.getLogger(name).run { - if (!handlers.contains(JulHandler)) addHandler(JulHandler) - level = julLevel - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/LogEvent.kt b/src/main/kotlin/de/gmuth/log/LogEvent.kt deleted file mode 100644 index e1646d36..00000000 --- a/src/main/kotlin/de/gmuth/log/LogEvent.kt +++ /dev/null @@ -1,13 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2023 Gerhard Muth - */ - -class LogEvent( - val logLevel: Logging.LogLevel, - val produceMessage: MessageProducer, - val throwable: Throwable? -) { - val messageString by lazy { produceMessage()?.toString() } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/Logger.kt b/src/main/kotlin/de/gmuth/log/Logger.kt deleted file mode 100644 index 5aef66de..00000000 --- a/src/main/kotlin/de/gmuth/log/Logger.kt +++ /dev/null @@ -1,51 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2022-2023 Gerhard Muth - */ - -import de.gmuth.log.Logging.LogLevel -import de.gmuth.log.Logging.LogLevel.* - -typealias MessageProducer = () -> Any? - -abstract class Logger(val name: String) { - - open var logLevel = OFF - open fun isEnabled(level: LogLevel) = logLevel != OFF && logLevel <= level - abstract fun publish(logEvent: LogEvent) - - @JvmOverloads - fun trace(throwable: Throwable? = null, messageProducer: MessageProducer = { "" }) = - log(TRACE, throwable, messageProducer) - - @JvmOverloads - fun debug(throwable: Throwable? = null, messageProducer: MessageProducer = { "" }) = - log(DEBUG, throwable, messageProducer) - - @JvmOverloads - fun info(throwable: Throwable? = null, messageProducer: MessageProducer = { "" }) = - log(INFO, throwable, messageProducer) - - @JvmOverloads - fun warn(throwable: Throwable? = null, messageProducer: MessageProducer = { "" }) = - log(WARN, throwable, messageProducer) - - @JvmOverloads - fun error(throwable: Throwable? = null, messageProducer: MessageProducer = { "" }) = - log(ERROR, throwable, messageProducer) - - @JvmOverloads - fun log(messageLogLevel: LogLevel, throwable: Throwable? = null, produceMessage: MessageProducer) = - log(LogEvent(messageLogLevel, produceMessage, throwable)) - - fun log(logEvent: LogEvent) = logEvent.run { - if (isEnabled(logLevel)) publish(logEvent) - } - - @JvmOverloads - fun logWithCauseMessages(throwable: Throwable, logLevel: LogLevel = ERROR) { - throwable.cause?.let { logWithCauseMessages(it, logLevel) } - log(logLevel) { "${throwable.javaClass.name}: ${throwable.message}" } - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/Logging.kt b/src/main/kotlin/de/gmuth/log/Logging.kt deleted file mode 100644 index 4c548905..00000000 --- a/src/main/kotlin/de/gmuth/log/Logging.kt +++ /dev/null @@ -1,34 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2020-2023 Gerhard Muth - */ - -object Logging { - - enum class LogLevel { OFF, TRACE, DEBUG, INFO, WARN, ERROR } - - var createLogger: ((name: String) -> Logger)? = ::ConsoleLogger // default logger - - private val loggerMap: MutableMap = mutableMapOf() - - fun disable() { - createLogger = null - } - - fun getLogger(name: String) = - if (createLogger == null) - NoOperationLogger - else - loggerMap[name] ?: createLogger!!(name).apply { - loggerMap[name] = this - } - - fun getLogger(noOperation: () -> Unit) = - getLogger(noOperation.javaClass.enclosingClass.name) - - object NoOperationLogger : Logger("") { - override fun isEnabled(level: LogLevel) = false - override fun publish(logEvent: LogEvent) = Unit - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/log/Slf4jAdapter.kt b/src/main/kotlin/de/gmuth/log/Slf4jAdapter.kt deleted file mode 100644 index 32542a11..00000000 --- a/src/main/kotlin/de/gmuth/log/Slf4jAdapter.kt +++ /dev/null @@ -1,41 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2022-2023 Gerhard Muth - */ - -import de.gmuth.log.Logging.LogLevel -import de.gmuth.log.Logging.LogLevel.* -import de.gmuth.log.Logging.createLogger - -// http://www.slf4j.org -class Slf4jAdapter(name: String) : Logger(name) { - - companion object { - fun configure() { - createLogger = ::Slf4jAdapter - } - } - - private val slf4jLogger = org.slf4j.LoggerFactory.getLogger(name) - - override fun isEnabled(level: LogLevel) = when (level) { - OFF -> false - TRACE -> slf4jLogger.isTraceEnabled - DEBUG -> slf4jLogger.isDebugEnabled - INFO -> slf4jLogger.isInfoEnabled - WARN -> slf4jLogger.isWarnEnabled - ERROR -> slf4jLogger.isErrorEnabled - } - - override fun publish(logEvent: LogEvent) = with(logEvent) { - when (logLevel) { - OFF -> Unit // don't publish anything - TRACE -> slf4jLogger.trace(messageString, throwable) - DEBUG -> slf4jLogger.debug(messageString, throwable) - INFO -> slf4jLogger.info(messageString, throwable) - WARN -> slf4jLogger.warn(messageString, throwable) - ERROR -> slf4jLogger.error(messageString, throwable) - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/http/HttpClientMock.kt b/src/test/kotlin/de/gmuth/http/HttpClientMock.kt index 3c31f489..c5e29c27 100644 --- a/src/test/kotlin/de/gmuth/http/HttpClientMock.kt +++ b/src/test/kotlin/de/gmuth/http/HttpClientMock.kt @@ -7,16 +7,13 @@ package de.gmuth.http import de.gmuth.io.ByteArray import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus -import de.gmuth.log.Logging import java.io.* import java.net.URI +import java.util.logging.Logger.getLogger class HttpClientMock(config: Http.Config = Http.Config()) : Http.Client(config) { - companion object { - val log = Logging.getLogger {} - } - + val log = getLogger(javaClass.name) lateinit var rawIppRequest: ByteArray var httpStatus: Int = 200 var httpServer: String? = "HttpClientMock" diff --git a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt index 3a1eacbb..614e8f1a 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt @@ -9,18 +9,14 @@ import de.gmuth.ipp.core.IppException import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppStatus.SuccessfulOk -import de.gmuth.log.Logging import org.junit.Test import java.net.URI +import java.util.logging.Logger.getLogger import kotlin.test.assertFailsWith import kotlin.test.assertTrue class CupsClientTests { - - companion object { - val log = Logging.getLogger { } - } - + val log = getLogger(javaClass.name) val httpClient = HttpClientMock() val cupsClient = CupsClient(URI.create("ipps://cups"), httpClient = httpClient) @@ -38,7 +34,7 @@ class CupsClientTests { @Test fun getPrinters() { httpClient.mockResponse("CUPS/Cups-Get-Printers.ipp") - cupsClient.getPrinters().forEach { log.info { it } } + cupsClient.getPrinters().forEach { log.info { it.toString() } } } @Test diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt index 73f8d76b..e603d9c7 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt @@ -4,16 +4,11 @@ import de.gmuth.http.HttpClientMock import de.gmuth.ipp.core.IppOperation.GetPrinterAttributes import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus.SuccessfulOk -import de.gmuth.log.Logging import org.junit.Test import java.net.URI import kotlin.test.assertEquals class IppClientTests { - companion object { - val log = Logging.getLogger { } - } - val httpClient = HttpClientMock() val ippClient = IppClient(httpClient = httpClient) diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 795fb1e7..1739dac9 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -10,11 +10,11 @@ import de.gmuth.ipp.core.IppStatus.SuccessfulOk import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.core.toIppString -import de.gmuth.log.Logging import org.junit.Test import java.io.File import java.io.FileInputStream import java.net.URI +import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -22,10 +22,8 @@ import kotlin.test.assertTrue class IppJobTests { - companion object { - val log = Logging.getLogger {} - val blankPdf = File("tool/A4-blank.pdf") - } + val log = getLogger(javaClass.name) + val blankPdf = File("tool/A4-blank.pdf") val httpClient = HttpClientMock() val ippConfig = IppConfig() @@ -205,7 +203,6 @@ class IppJobTests { @Test fun cupsGetDocument3() { - IppDocument.log.logLevel = Logging.LogLevel.DEBUG printer.attributes.remove("cups-version") httpClient.ippResponse = cupsDocumentResponse("application/octetstream").apply { jobGroup.remove("document-name") diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt index 1f5791b0..45365807 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt @@ -20,11 +20,10 @@ import de.gmuth.ipp.client.IppTemplateAttributes.pageRanges import de.gmuth.ipp.client.IppTemplateAttributes.printerResolution import de.gmuth.ipp.client.IppWhichJobs.Completed import de.gmuth.ipp.core.IppOperation.GetPrinterAttributes -import de.gmuth.log.Logging -import de.gmuth.log.Logging.LogLevel.TRACE import java.io.File import java.io.FileInputStream import java.net.URI +import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory import kotlin.io.path.pathString import kotlin.test.Test @@ -34,16 +33,8 @@ import kotlin.test.assertTrue class IppPrinterTests { - companion object { - val log = Logging.getLogger {} - val blankPdf = File("tool/A4-blank.pdf") - } - - init { - IppClient.log.logLevel = TRACE - IppPrinter.log.logLevel = TRACE - } - + val log = getLogger(javaClass.name) + val blankPdf = File("tool/A4-blank.pdf") val httpClient = HttpClientMock() val ippConfig = IppConfig() @@ -60,7 +51,6 @@ class IppPrinterTests { @Test fun printerAttributes() { printer.apply { - log.logLevel = TRACE log.info { toString() } assertTrue(isAcceptingJobs) assertTrue(documentFormatSupported.contains("application/pdf")) @@ -83,7 +73,7 @@ class IppPrinterTests { assertEquals(80, levelPercent()) assertEquals("#000000", colorCode) assertFalse(levelIsLow()) - log.info { this } + log.info { this.toString() } } assertTrue(isIdle()) assertFalse(isProcessing()) @@ -97,7 +87,7 @@ class IppPrinterTests { communicationChannelsSupported.forEach { log.info { "${it.uri}, ${it.security}, ${it.authentication}, $it" } } - ippConfig.logDetails() + ippConfig.log(log) } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt b/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt index e3c7e0ae..5e3c071d 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt @@ -1,16 +1,16 @@ package de.gmuth.ipp.client import de.gmuth.http.Http -import de.gmuth.http.HttpURLConnectionClient -import de.gmuth.log.Logging +import de.gmuth.log.error import java.net.URI +import java.util.logging.Logger.getLogger fun main() { //Logging.defaultLogLevel = Logging.LogLevel.DEBUG - HttpURLConnectionClient.log.logLevel = Logging.LogLevel.TRACE - IppClient.log.logLevel = Logging.LogLevel.DEBUG + //HttpURLConnectionClient.log.logLevel = Logging.LogLevel.TRACE + //IppClient.log.logLevel = Logging.LogLevel.DEBUG - val log = Logging.getLogger {} + val log = getLogger("issueNo11") //val printerUri = URI.create("ipp://192.168.31.244:631/ipp/print") val printerUri = URI.create("ipp://xero.local/ipp/print") @@ -25,8 +25,8 @@ fun main() { ) ) { log.info { "successfully connected $printerUri" } - log.info { this } - markers.forEach { log.info { it } } + log.info { toString() } + markers.forEach { log.info { it.toString() } } } } catch (exception: Exception) { log.error(exception) { "failed to connect to $printerUri" } diff --git a/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt b/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt index d8d926ab..4eabfc97 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt @@ -1,18 +1,17 @@ package de.gmuth.ipp.client -import de.gmuth.http.HttpURLConnectionClient -import de.gmuth.log.ConsoleLogger -import de.gmuth.log.Logging +import de.gmuth.log.error import java.net.HttpURLConnection import java.net.URI +import java.util.logging.Logger.getLogger fun main() { val printerUri = URI.create("ipp://xero.local:631/ipp/print") - ConsoleLogger.defaultLogLevel = Logging.LogLevel.DEBUG - HttpURLConnectionClient.log.logLevel = Logging.LogLevel.TRACE - val log = Logging.getLogger {} + //ConsoleLogger.defaultLogLevel = Logging.LogLevel.DEBUG + //HttpURLConnectionClient.log.logLevel = Logging.LogLevel.TRACE + val log = getLogger("issueNo3") var ippPrinter: IppPrinter? = null // httpConnect(printerUri) @@ -20,7 +19,7 @@ fun main() { val ippConfig = IppConfig().apply { ippVersion = "1.1" - logDetails() + log(log) } try { log.info { "open ipp connection to $printerUri" } @@ -49,7 +48,7 @@ fun main() { } fun httpConnect(printerUri: URI) { - val log = Logging.getLogger {} + val log = getLogger("httpConnect") val printerUrl = URI("http://${printerUri.host}:${printerUri.port}").toURL() try { log.info { "open http connection to $printerUrl" } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt index ea1653b7..3a128d28 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt @@ -5,17 +5,14 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging import java.io.File +import java.util.logging.Logger.getLogger import kotlin.test.* class IppAttributeTests { private val ippAttribute = IppAttribute("printer-state-reasons", Keyword, "none") - - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) @Test fun constructorFailsDueToDelimiterTag() { diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index c5c22957..069d822f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -5,7 +5,6 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.Logging import java.io.File import kotlin.test.Test import kotlin.test.assertEquals @@ -38,7 +37,6 @@ class IppAttributesGroupTests { @Test fun putWithReplacementWarning() { - IppAttributesGroup.log.logLevel = Logging.LogLevel.INFO group.put(IppAttribute("number", Integer, 0)) group.put(IppAttribute("number", Integer, 1, 2), true) assertEquals(1, group.size) diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt index c51fafce..5d3f527f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt @@ -5,18 +5,21 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.client.IppExchangeException +import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals class IppExchangeExceptionTests { + val log = getLogger(javaClass.name) + @Test fun constructor() { with(IppExchangeException( IppRequest(IppOperation.GetPrinterAttributes).apply { encode() }, null, 400 )) { - logDetails() + log(log) assertEquals(11, request.code) assertEquals(400, httpStatus) assertEquals(message, "Get-Printer-Attributes failed") diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt index bd592700..0a9c1314 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt @@ -5,9 +5,9 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppResolution.Unit.DPI -import de.gmuth.log.Logging import java.io.ByteArrayInputStream import java.net.URI +import java.util.logging.Logger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -15,10 +15,6 @@ import kotlin.test.assertTrue class IppInputStreamTest { - companion object { - val log = Logging.getLogger {} - } - private val message = object : IppMessage() { override val codeDescription: String get() = "codeDescription" @@ -167,9 +163,7 @@ class IppInputStreamTest { fun readMessage() { val encoded = "02 01 00 0B 00 00 00 08 01 47 00 12 61 74 74 72 69 62 75 74 65 73 2D 63 68 61 72 73 65 74 00 05 75 74 66 2D 38 02 22 00 01 31 00 01 01 22 00 00 00 01 00 13 00 01 30 00 00 03" - IppInputStream.log.logLevel = Logging.LogLevel.TRACE encoded.toIppInputStream().readMessage(message) - IppInputStream.log.logLevel = Logging.LogLevel.TRACE with(message) { assertEquals("2.1", version) assertEquals(IppOperation.GetPrinterAttributes.code, code) diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt index d0798a9f..c8e29e28 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt @@ -5,7 +5,6 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppResolution.Unit.DPI -import de.gmuth.log.Logging import java.io.ByteArrayOutputStream import java.net.URI import kotlin.test.Test @@ -14,10 +13,6 @@ import kotlin.test.assertFailsWith class IppOutputStreamTest { - companion object { - val log = Logging.getLogger {} - } - private val byteArrayOutputStream = ByteArrayOutputStream() private val ippOutputStream = IppOutputStream(byteArrayOutputStream).apply { attributesCharset = Charsets.US_ASCII } @@ -205,10 +200,7 @@ class IppOutputStreamTest { attribute("0", IppTag.NoValue) // cover OutOfBand } } - IppOutputStream.log.logLevel = Logging.LogLevel.TRACE ippOutputStream.writeMessage(message) - IppOutputStream.log.logLevel = Logging.LogLevel.WARN - assertEquals( "02 01 00 0B 00 00 00 08 01 47 00 12 61 74 74 72 69 62 75 74 65 73 2D 63 68 61 72 73 65 74 00 05 75 74 66 2D 38 02 22 00 01 31 00 01 01 22 00 00 00 01 00 13 00 01 30 00 00 03", byteArrayOutputStream.toHex() diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt index b968ea3a..6b3de901 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt @@ -4,8 +4,8 @@ package de.gmuth.ipp.core * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.Logging import java.net.URI +import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -13,9 +13,7 @@ import kotlin.test.assertNotNull class IppRequestTests { - companion object { - val log = Logging.getLogger {} - } + val log = getLogger(javaClass.name) @Test fun requestConstructor1() { @@ -32,7 +30,6 @@ class IppRequestTests { @Test fun requestConstructor2() { - IppMessage.log.logLevel = Logging.LogLevel.DEBUG val request = IppRequest(IppOperation.StartupPrinter, URI.create("ipp://foo")) assertEquals(1, request.requestId) assertEquals("1.1", request.version) @@ -43,15 +40,10 @@ class IppRequestTests { assertEquals("Startup-Printer", request.codeDescription) val requestEncoded = request.encode() assertEquals(97, requestEncoded.size) - IppMessage.log.logLevel = Logging.LogLevel.INFO } @Test fun printJobRequest() { - IppInputStream.log.logLevel = Logging.LogLevel.INFO - IppOutputStream.log.logLevel = Logging.LogLevel.INFO - IppMessage.log.logLevel = Logging.LogLevel.INFO - val request = IppRequest( IppOperation.PrintJob, URI.create("ipp://printer"), 0, listOf("one", "two"), "user" diff --git a/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt b/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt index b0e01bd2..0a149fa9 100644 --- a/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt +++ b/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt @@ -4,10 +4,11 @@ package de.gmuth.ipp.iana * Copyright (c) 2020 Gerhard Muth */ -import de.gmuth.log.Logging +import de.gmuth.log.debug import java.io.InputStream import java.io.OutputStream import java.io.PrintWriter +import java.util.logging.Logger.getLogger import kotlin.math.log10 // https://tools.ietf.org/html/rfc4180 @@ -18,6 +19,8 @@ class CSVReader(private val rowMapper: RowMapper) { fun mapRow(columns: List, rowNum: Int): T } + val log = getLogger(javaClass.name) + fun readResource(resource: String, skipHeader: Boolean = true): List { return read(javaClass.getResourceAsStream(resource), skipHeader) } @@ -78,8 +81,6 @@ class CSVReader(private val rowMapper: RowMapper) { companion object { - val log = Logging.getLogger {} - fun prettyPrintResource(resource: String) { val rows = readRowsFromResource(resource) prettyPrint(rows) diff --git a/src/test/kotlin/de/gmuth/log/ConsoleHandler.kt b/src/test/kotlin/de/gmuth/log/ConsoleHandler.kt new file mode 100644 index 00000000..dd06df43 --- /dev/null +++ b/src/test/kotlin/de/gmuth/log/ConsoleHandler.kt @@ -0,0 +1,16 @@ +package de.gmuth.log + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import java.io.OutputStream +import java.util.logging.ConsoleHandler +import java.util.logging.Level + +class ConsoleHandler(outputStream: OutputStream = System.out) : ConsoleHandler() { + init { + super.setOutputStream(outputStream) + level = Level.ALL + } +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/log/Logging.kt b/src/test/kotlin/de/gmuth/log/Logging.kt new file mode 100644 index 00000000..c89c6d5f --- /dev/null +++ b/src/test/kotlin/de/gmuth/log/Logging.kt @@ -0,0 +1,31 @@ +package de.gmuth.log + +import java.io.FileInputStream +import java.util.logging.LogManager +import java.util.logging.Logger + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +fun LogManager.readConfigurationFile(filename: String = "logging.properties") = + readConfiguration(FileInputStream(filename)) + .also { println("configured logging by file: $filename") } + +fun LogManager.readConfigurationResource(resource: String = "/logging.properties") = + readConfiguration(Logging::class.java.getResourceAsStream(resource)) + .also { println("configured logging by resource: $resource") } + +object Logging { + + init { + LogManager.getLogManager().readConfigurationResource() + } + + fun getLogger(name: String) = + Logger.getLogger(name) + + fun getLogger(noOperation: () -> Unit) = + Logger.getLogger(noOperation.javaClass.enclosingClass.name) + +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/log/SimpleClassNameFormatter.kt b/src/test/kotlin/de/gmuth/log/SimpleClassNameFormatter.kt new file mode 100644 index 00000000..58c57343 --- /dev/null +++ b/src/test/kotlin/de/gmuth/log/SimpleClassNameFormatter.kt @@ -0,0 +1,26 @@ +package de.gmuth.log + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import java.util.logging.LogManager +import java.util.logging.LogRecord +import java.util.logging.SimpleFormatter + +class SimpleClassNameFormatter( + val simpleClassName: Boolean = getProperty("simpleClassName")?.toBooleanStrict() ?: true +) : SimpleFormatter() { + + companion object { + private val logManager by lazy { LogManager.getLogManager() } + private fun getProperty(name: String): String? = + logManager.getProperty("${SimpleClassNameFormatter::class.java.canonicalName}.$name") + } + + override fun format(logRecord: LogRecord?) = super.format( + logRecord.apply { + if (this != null && simpleClassName) loggerName = loggerName.substringAfterLast(".") + } + ) +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/log/StdoutHandler.kt b/src/test/kotlin/de/gmuth/log/StdoutHandler.kt new file mode 100644 index 00000000..cd3c6143 --- /dev/null +++ b/src/test/kotlin/de/gmuth/log/StdoutHandler.kt @@ -0,0 +1,21 @@ +package de.gmuth.log + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import java.util.logging.Level +import java.util.logging.LogRecord +import java.util.logging.StreamHandler + +class StdoutHandler() : StreamHandler(System.out, SimpleClassNameFormatter()) { + + init { + level = Level.ALL + } + + override fun publish(record: LogRecord?) { + super.publish(record) + flush() + } +} \ No newline at end of file From 563218543f0587d93e88584908f623bf9cf1943e Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 9 Sep 2023 13:41:08 +0300 Subject: [PATCH 02/47] pipeline --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8120654e..7683b1fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,11 +5,11 @@ name: build on: - workflow_dispatch: - push: - branches: - - master - - develop + workflow_dispatch + push +# branches: +# - master +# - develop pull_request: branches: [ master ] From f51d81f3b049768ee880967d5639b7d6b7e716f5 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 9 Sep 2023 13:42:08 +0300 Subject: [PATCH 03/47] pipeline --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7683b1fa..6df70f13 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,8 +5,8 @@ name: build on: - workflow_dispatch - push + workflow_dispatch: + push: # branches: # - master # - develop From 816f9a66c1ae6ab02bbbf73fc878219a17faee1d Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 9 Sep 2023 13:48:49 +0300 Subject: [PATCH 04/47] fix sonar issues --- src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt | 3 --- src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt | 7 +------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index 7ca5421e..4eb1c2e2 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -6,7 +6,6 @@ package de.gmuth.ipp.client import java.nio.charset.Charset import java.util.logging.Logger -import java.util.logging.Logger.getLogger class IppConfig( var userName: String? = System.getProperty("user.name"), @@ -14,8 +13,6 @@ class IppConfig( var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", ) { - //val log = getLogger(javaClass.name) - fun log(log: Logger) = log.run { info { "userName: $userName" } info { "ippVersion: $ippVersion" } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 50e2135d..a7afa4d9 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -12,13 +12,10 @@ import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2 import de.gmuth.log.* -//import de.gmuth.log.Logger.Level.* -import java.util.logging.Logger.getLogger -//import de.gmuth.log.JavaUtilLogging.getLogger import java.io.* import java.net.URI import java.time.Duration -import java.util.logging.Logger +import java.util.logging.Logger.getLogger @SuppressWarnings("kotlin:S1192") class IppPrinter( @@ -31,11 +28,9 @@ class IppPrinter( requestedAttributesOnInit: List? = null ) { var workDirectory: File = File("work") - //val log = getLogger(javaClass.canonicalName) val log = getLogger(javaClass.name) companion object { - //val log = JUL2.getLogger {} val printerStateAttributes = listOf( "printer-is-accepting-jobs", "printer-state", "printer-state-reasons" From 5a7930070450f3f7bd312c45374e204389c68471 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 9 Sep 2023 14:12:57 +0300 Subject: [PATCH 05/47] fix sonar issues --- src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt | 2 +- src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt | 3 ++- src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index 4eb1c2e2..7e45c1a7 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -13,7 +13,7 @@ class IppConfig( var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", ) { - fun log(log: Logger) = log.run { + fun log(logger: Logger) = logger.run { info { "userName: $userName" } info { "ippVersion: $ippVersion" } info { "charset: ${charset.name().lowercase()}" } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index a7afa4d9..500c98c3 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -15,6 +15,7 @@ import de.gmuth.log.* import java.io.* import java.net.URI import java.time.Duration +import java.util.logging.Level.SEVERE import java.util.logging.Logger.getLogger @SuppressWarnings("kotlin:S1192") @@ -70,7 +71,7 @@ class IppPrinter( if (ippExchangeException.statusIs(ClientErrorNotFound)) log.error { ippExchangeException.message } else { - //log.logWithCauseMessages(ippExchangeException, ERROR) + log.logWithCauseMessages(ippExchangeException, SEVERE) log.error { "failed to get printer attributes on init" } ippExchangeException.response?.let { if (it.containsGroup(Printer)) log.info { "${it.printerGroup.size} attributes parsed" } diff --git a/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt b/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt index dd6f95dd..ceb805dc 100644 --- a/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt +++ b/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt @@ -5,6 +5,7 @@ package de.gmuth.log */ import java.util.function.Supplier +import java.util.logging.Level import java.util.logging.Level.* import java.util.logging.Logger @@ -47,7 +48,7 @@ fun Logger.warn(thrown: Throwable? = null, msgSupplier: Supplier = Supp fun Logger.error(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = if (thrown == null) severe(msgSupplier) else severe(thrown, msgSupplier) -fun Logger.logWithCauseMessages(throwable: Throwable) { - throwable.cause?.let { logWithCauseMessages(it) } +fun Logger.logWithCauseMessages(throwable: Throwable, level: Level) { + throwable.cause?.let { logWithCauseMessages(it, level) } log(level) { "${throwable.javaClass.name}: ${throwable.message}" } } \ No newline at end of file From 8e5ee9cba8df7ad552f86e3a4ab8b77cb0e1feab Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 9 Sep 2023 14:24:04 +0300 Subject: [PATCH 06/47] fix warning --- src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt index fe03926d..5e9cbaad 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt @@ -32,10 +32,10 @@ class CupsMarker( YELLOW("#FFFF00"), MAGENTA("#FF00FF"), TRI_COLOR("#00FFFF#FF00FF#FFFF00"), // Cyan, Magenta, Yellow - UNKNOWN("?"); + UNKNOWN("#?"); companion object { - val log = getLogger(javaClass.name) + val log = getLogger(Color::class.java.name) fun fromString(code: String) = values().find { it.code == code } ?: UNKNOWN.apply { log.warn { "unknown color code: $code" } } } From 5d15e97db053a318f93b0b1322860b36fb822608 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Wed, 13 Sep 2023 21:56:36 +0300 Subject: [PATCH 07/47] refactored all logging calls to go to java util logging --- .../de/gmuth/http/HttpURLConnectionClient.kt | 12 +--- .../kotlin/de/gmuth/http/JavaHttpClient.kt | 8 +-- .../kotlin/de/gmuth/ipp/client/CupsClient.kt | 8 +-- .../kotlin/de/gmuth/ipp/client/CupsMarker.kt | 3 +- .../kotlin/de/gmuth/ipp/client/IppClient.kt | 35 +++++----- .../de/gmuth/ipp/client/IppColorMode.kt | 24 ++----- .../kotlin/de/gmuth/ipp/client/IppConfig.kt | 12 ++-- .../kotlin/de/gmuth/ipp/client/IppDocument.kt | 8 +-- .../gmuth/ipp/client/IppEventNotification.kt | 5 +- .../gmuth/ipp/client/IppExchangeException.kt | 13 ++-- src/main/kotlin/de/gmuth/ipp/client/IppJob.kt | 18 ++--- .../kotlin/de/gmuth/ipp/client/IppMedia.kt | 8 +-- .../kotlin/de/gmuth/ipp/client/IppPrinter.kt | 39 +++++------ .../de/gmuth/ipp/client/IppSubscription.kt | 9 ++- .../kotlin/de/gmuth/ipp/core/IppAttribute.kt | 7 +- .../de/gmuth/ipp/core/IppAttributesGroup.kt | 15 +++-- .../de/gmuth/ipp/core/IppInputStream.kt | 25 ++++--- .../kotlin/de/gmuth/ipp/core/IppMessage.kt | 35 +++++----- .../de/gmuth/ipp/core/IppOutputStream.kt | 13 ++-- .../ipp/iana/IppRegistrationsSection2.kt | 66 ++++++++++--------- .../de/gmuth/log/JavaUtilLoggingExtensions.kt | 54 --------------- .../kotlin/de/gmuth/ipp/client/IppJobTests.kt | 6 +- .../de/gmuth/ipp/client/IppPrinterTests.kt | 2 +- .../kotlin/de/gmuth/ipp/client/issueNo11.kt | 4 +- .../kotlin/de/gmuth/ipp/client/issueNo3.kt | 10 +-- .../gmuth/ipp/core/IppAttributesGroupTests.kt | 4 +- .../de/gmuth/ipp/core/IppMessageTests.kt | 7 +- .../de/gmuth/ipp/core/IppRequestTests.kt | 4 +- .../de/gmuth/ipp/core/IppResponseTests.kt | 8 ++- .../kotlin/de/gmuth/ipp/iana/CSVReader.kt | 5 +- src/test/kotlin/de/gmuth/ipp/tool/IppTool.kt | 4 +- src/test/kotlin/de/gmuth/log/Logging.kt | 26 ++------ 32 files changed, 211 insertions(+), 286 deletions(-) delete mode 100644 src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt diff --git a/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt b/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt index 46bc8973..c5bd1372 100644 --- a/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt +++ b/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt @@ -4,8 +4,6 @@ package de.gmuth.http * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.debug -import de.gmuth.log.error import java.io.OutputStream import java.net.HttpURLConnection import java.net.URI @@ -19,13 +17,7 @@ class HttpURLConnectionClient(config: Http.Config = Http.Config()) : Http.Client val log = getLogger(javaClass.name) init { - log.debug { "HttpURLConnectionClient created" } - /** if (config.debugLogging && createLogger != ::JulAdapter) { - // The JulHandler forwards ALL jul message to Logging - JulRedirectHandler.addToJulLogger("sun.net.www.protocol.http") - // The JulHandler does NOT use the jul output config - Logging.getLogger("sun.net.www.protocol.http.HttpURLConnection").logLevel = TRACE - } **/ + log.fine { "HttpURLConnectionClient created" } if (config.debugLogging) { getLogger("sun.net.www.protocol.http.HttpURLConnection").level = Level.FINER } @@ -62,7 +54,7 @@ class HttpURLConnectionClient(config: Http.Config = Http.Config()) : Http.Client val responseStream = try { inputStream } catch (exception: Exception) { - log.error { "http exception: $responseCode $responseMessage" } + log.severe { "http exception: $responseCode $responseMessage" } errorStream } return Http.Response( diff --git a/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt b/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt index 57483b50..09d5b912 100644 --- a/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt +++ b/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt @@ -4,7 +4,6 @@ package de.gmuth.http * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.debug import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.OutputStream @@ -15,6 +14,7 @@ import java.net.http.HttpRequest import java.net.http.HttpRequest.BodyPublishers import java.net.http.HttpResponse.BodyHandlers import java.time.Duration +import java.util.logging.Level import java.util.logging.Logger.getLogger // requires Java >=11 @@ -26,15 +26,15 @@ class JavaHttpClient(config: Http.Config = Http.Config()) : Http.Client(config) HttpClient.newHttpClient() true } catch (exception: ClassNotFoundException) { - log.debug(exception) { "HttpClient not found" } + log.log(Level.FINER, exception, { "HttpClient not found" }) false }.apply { - log.debug { "Java HttpClient supported: $this" } + log.fine { "Java HttpClient supported: $this" } } } init { - log.debug { "JavaHttpClient created" } + log.fine { "JavaHttpClient created" } if (!config.verifySSLHostname) setProperty("jdk.internal.httpclient.disableHostnameVerification", true.toString()) } diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index edc3166d..499def9c 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -11,8 +11,6 @@ import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.debug -import de.gmuth.log.warn import java.io.File import java.io.InputStream import java.net.URI @@ -61,7 +59,7 @@ open class CupsClient( } } catch (clientErrorNotFoundException: ClientErrorNotFoundException) { with(getPrinters()) { - if (isNotEmpty()) log.warn { "Available CUPS printers: ${map { it.name }}" } + if (isNotEmpty()) log.warning { "Available CUPS printers: ${map { it.name }}" } } throw clientErrorNotFoundException } @@ -78,7 +76,7 @@ open class CupsClient( val optionalPort = if (port > 0) ":$port" else "" URI("$scheme://$host$optionalPort/printers/$printerName") }.apply { - log.debug { "cupsPrinterUri($printerName) -> $this" } + log.fine { "cupsPrinterUri($printerName) -> $this" } } // https://www.cups.org/doc/spec-ipp.html#CUPS_ADD_MODIFY_PRINTER @@ -332,7 +330,7 @@ open class CupsClient( val jobOwnersIterator = jobOwners.iterator() while (jobOwnersIterator.hasNext() && ippExchangeException != null) { ippConfig.userName = jobOwnersIterator.next() - log.debug { "set userName '${ippConfig.userName}'" } + log.fine { "set userName '${ippConfig.userName}'" } tryToGetDocuments() } ippConfig.userName = configuredUserName diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt index 5e9cbaad..c7e21559 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt @@ -4,7 +4,6 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.warn import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html @@ -37,7 +36,7 @@ class CupsMarker( companion object { val log = getLogger(Color::class.java.name) fun fromString(code: String) = values().find { it.code == code } - ?: UNKNOWN.apply { log.warn { "unknown color code: $code" } } + ?: UNKNOWN.apply { log.warning { "unknown color code: $code" } } } } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt index 22b34aed..a19d7c94 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt @@ -14,12 +14,11 @@ import de.gmuth.ipp.core.IppStatus.ClientErrorBadRequest import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.Unsupported import de.gmuth.ipp.iana.IppRegistrationsSection2 -import de.gmuth.log.debug -import de.gmuth.log.trace -import de.gmuth.log.warn import java.io.File import java.net.URI import java.util.concurrent.atomic.AtomicInteger +import java.util.logging.Level.FINE +import java.util.logging.Level.WARNING import java.util.logging.Logger.getLogger typealias IppResponseInterceptor = (request: IppRequest, response: IppResponse) -> Unit @@ -41,8 +40,8 @@ open class IppClient( companion object { const val APPLICATION_IPP = "application/ipp" - const val version = "2.5-SNAPSHOT" - const val build = "2023" + const val version = "3.0-SNAPSHOT" + const val build = "09-2023" init { println("IPP-Client: Version: $version, Build: $build, MIT License, (c) 2020-2023 Gerhard Muth") @@ -89,11 +88,11 @@ open class IppClient( open fun exchange(request: IppRequest, throwWhenNotSuccessful: Boolean = true): IppResponse { val ippUri: URI = request.printerUri val httpUri = toHttpUri(ippUri) - log.trace { "send '${request.operation}' request to $ippUri" } + log.finer { "send '${request.operation}' request to $ippUri" } val httpResponse = httpPostRequest(httpUri, request) val response = decodeIppResponse(request, httpResponse) - log.debug { "$ippUri: $request => $response" } + log.fine { "$ippUri: $request => $response" } httpServer = httpResponse.server if (saveMessages) { @@ -130,7 +129,7 @@ open class IppClient( ).apply { var exceptionMessage: String? = null if (contentType == null) { - log.debug { "missing content-type in http response (should be '$APPLICATION_IPP')" } + log.fine { "missing content-type in http response (should be '$APPLICATION_IPP')" } } else { if (!contentType.startsWith(APPLICATION_IPP)) { exceptionMessage = "invalid content-type: $contentType (expecting '$APPLICATION_IPP')" @@ -141,12 +140,12 @@ open class IppClient( "user '$requestingUserName' is unauthorized for operation '$operation' (status=$status)" } exceptionMessage?.run { - config.log(log) - request.logDetails("IPP REQUEST: ") - log.warn { "http response status: $status" } - server?.let { log.warn { "ipp-server: $it" } } - contentType?.let { log.warn { "content-type: $it" } } - contentStream?.let { log.warn { "content:\n" + it.bufferedReader().use { it.readText() } } } + config.log(log, WARNING) + request.log(log, WARNING, prefix = "IPP REQUEST: ") + log.warning { "http response status: $status" } + server?.let { log.warning { "ipp-server: $it" } } + contentType?.let { log.warning { "content-type: $it" } } + contentStream?.let { log.warning { "content:\n" + it.bufferedReader().use { it.readText() } } } throw IppExchangeException(request, null, status, message = exceptionMessage) } } @@ -161,11 +160,11 @@ open class IppClient( saveMessages("decoding_ipp_response_${request.requestId}_failed") } } - if (status == ClientErrorBadRequest) request.logDetails("BAD-REQUEST: ") - if (!status.isSuccessful()) log.debug { "status: $status" } - if (hasStatusMessage()) log.debug { "status-message: $statusMessage" } + if (status == ClientErrorBadRequest) request.log(log, FINE, prefix="BAD-REQUEST: ") + if (!status.isSuccessful()) log.fine { "status: $status" } + if (hasStatusMessage()) log.fine { "status-message: $statusMessage" } if (containsGroup(Unsupported)) unsupportedGroup.values.forEach { - log.warn { "unsupported: $it" } + log.warning { "unsupported: $it" } } } } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt b/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt index 98bd0779..1c5994fe 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt @@ -8,8 +8,6 @@ import de.gmuth.ipp.core.IppAttribute import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppTag.Keyword -import de.gmuth.log.warn -import java.util.logging.Logger.getLogger enum class IppColorMode(private val keyword: String) : IppAttributeBuilder { @@ -17,20 +15,12 @@ enum class IppColorMode(private val keyword: String) : IppAttributeBuilder { Color("color"), Monochrome("monochrome"); - val log = getLogger(javaClass.name) - - override fun buildIppAttribute(printerAttributes: IppAttributesGroup): IppAttribute { - // use job-creation-attributes-supported? // 5100.11 - val modeAttributeName = when { - printerAttributes.containsKey("print-color-mode-supported") -> "print-color-mode" // 5100.14 ipp everywhere + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = IppAttribute( + when { // use job-creation-attributes-supported? // 5100.11 printerAttributes.containsKey("output-mode-supported") -> "output-mode" // cups extension - else -> { - if (printerAttributes.isEmpty()) log.warn { "no printer attributes" } - else log.warn { "printer does not support 'output-mode' or 'print-color-mode'" } - "print-color-mode" - } - } - return IppAttribute(modeAttributeName, Keyword, keyword) - } - + else -> "print-color-mode" // 5100.14 ipp everywhere + }, + Keyword, + keyword + ) } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index 7e45c1a7..de856b0d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -5,6 +5,8 @@ package de.gmuth.ipp.client */ import java.nio.charset.Charset +import java.util.logging.Level +import java.util.logging.Level.INFO import java.util.logging.Logger class IppConfig( @@ -13,10 +15,10 @@ class IppConfig( var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", ) { - fun log(logger: Logger) = logger.run { - info { "userName: $userName" } - info { "ippVersion: $ippVersion" } - info { "charset: ${charset.name().lowercase()}" } - info { "naturalLanguage: $naturalLanguage" } + fun log(logger: Logger, level: Level = INFO) = logger.run { + log(level) { "userName: $userName" } + log(level) { "ippVersion: $ippVersion" } + log(level) { "charset: ${charset.name().lowercase()}" } + log(level) { "naturalLanguage: $naturalLanguage" } } } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index 14d70c16..a5f361c4 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -6,10 +6,10 @@ package de.gmuth.ipp.client import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppString -import de.gmuth.log.debug import java.io.File import java.io.IOException import java.io.InputStream +import java.util.logging.Logger import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory @@ -32,7 +32,7 @@ class IppDocument( var file: File? = null fun readBytes() = inputStream.readBytes().also { - log.debug { "read ${it.size} bytes of $this" } + log.fine { "read ${it.size} bytes of $this" } } fun filenameSuffix() = when (format) { @@ -82,7 +82,7 @@ class IppDocument( toString() } - fun logDetails() = - attributes.logDetails(title = "DOCUMENT-$number") + fun log(logger: Logger) = + attributes.log(logger, title = "DOCUMENT-$number") } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt index f4eaf917..75d81101 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt @@ -6,6 +6,7 @@ package de.gmuth.ipp.client import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppString +import java.util.logging.Logger import java.util.logging.Logger.getLogger class IppEventNotification( @@ -72,7 +73,7 @@ class IppEventNotification( toString() } - fun logDetails() = - attributes.logDetails(title = "event notification #$sequenceNumber $subscribedEvent") + fun log(logger: Logger) = + attributes.log(logger, title = "event notification #$sequenceNumber $subscribedEvent") } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt index 8036a8d1..5b2ecd8b 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt @@ -10,6 +10,8 @@ import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import java.io.File +import java.util.logging.Level +import java.util.logging.Level.INFO import java.util.logging.Logger import java.util.logging.Logger.getLogger import kotlin.io.path.createTempDirectory @@ -27,7 +29,8 @@ open class IppExchangeException( class ClientErrorNotFoundException(request: IppRequest, response: IppResponse) : IppExchangeException(request, response) { init { - require(response.status == ClientErrorNotFound) { "ipp response status is not ClientErrorNotFound: ${response.status}" } + require(response.status == ClientErrorNotFound) + { "ipp response status is not ClientErrorNotFound: ${response.status}" } } } @@ -49,10 +52,10 @@ open class IppExchangeException( fun statusIs(status: IppStatus) = response?.status == status - fun log(logger: Logger) = logger.run { - if (httpStatus != null) info { "HTTP-STATUS: $httpStatus" } - request.logDetails(" REQUEST: ") - response?.logDetails("RESPONSE: ") + fun log(logger: Logger, level: Level = INFO) = logger.run { + if (httpStatus != null) log(level) { "HTTP-STATUS: $httpStatus" } + request.log(log, level, prefix = " REQUEST: ") + response?.log(log, level, prefix = "RESPONSE: ") } fun saveMessages( diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index 8494f4ae..923114ce 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -8,22 +8,21 @@ import de.gmuth.ipp.client.IppJobState.* import de.gmuth.ipp.core.* import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.debug -import de.gmuth.log.warn import java.io.File import java.io.FileInputStream import java.io.InputStream import java.net.URI +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger class IppJob( val printer: IppPrinter, var attributes: IppAttributesGroup, subscriptionAttributes: IppAttributesGroup? = null - ) { companion object { - var defaultDelayMillis: Long = 3000 var useJobOwnerAsUserName: Boolean = false } @@ -165,8 +164,8 @@ class IppJob( fun restart() = exchange(ippRequest(RestartJob)).also { updateAttributes() } fun cancel(messageForOperator: String? = null): IppResponse { // RFC 8011 4.3.3 - if (isCanceled()) log.warn { "job #$id is already 'canceled'" } - if (isProcessingToStopPoint()) log.warn { "job #$id is already 'processing-to-stop-point'" } + if (isCanceled()) log.warning { "job #$id is already 'canceled'" } + if (isProcessingToStopPoint()) log.warning { "job #$id is already 'processing-to-stop-point'" } val request = ippRequest(CancelJob).apply { messageForOperator?.let { operationGroup.attribute("message", TextWithoutLanguage, it.toIppString()) } } @@ -242,7 +241,7 @@ class IppJob( return IppSubscription(printer, subscriptionAttributes).apply { subscription = this if (notifyEvents != null && !events.containsAll(notifyEvents)) { - log.warn { "server ignored some notifyEvents $notifyEvents, subscribed events: $events" } + log.warning { "server ignored some notifyEvents $notifyEvents, subscribed events: $events" } } } } @@ -268,7 +267,7 @@ class IppJob( //------------------------------------------------------------------------------------- fun cupsGetDocument(documentNumber: Int = 1): IppDocument { - log.debug { "cupsGetDocument #$documentNumber for job #$id" } + log.fine { "cupsGetDocument #$documentNumber for job #$id" } val response = exchange(ippRequest(CupsGetDocument).apply { operationGroup.attribute("document-number", Integer, documentNumber) }) @@ -329,5 +328,6 @@ class IppJob( } } - fun logDetails() = attributes.logDetails(title = "JOB-$id") + fun log(logger: Logger, level: Level = INFO) = + attributes.log(logger, level, title = "JOB-$id") } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt b/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt index 725d7712..9a7d9394 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt @@ -9,8 +9,6 @@ import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppCollection import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.debug -import de.gmuth.log.warn import java.util.logging.Logger.getLogger object IppMedia { @@ -64,11 +62,11 @@ object IppMedia { private fun checkIfSourceIsSupported(printerAttributes: IppAttributesGroup) { val mediaSourceSupported = printerAttributes["media-source-supported"] if (mediaSourceSupported == null) { - log.debug { "printer does not provide attribute 'media-source-supported'" } + log.fine { "printer does not provide attribute 'media-source-supported'" } } else { if (!mediaSourceSupported.values.contains(source)) { - log.warn { "media-source '$source' not supported by printer" } - log.warn { mediaSourceSupported.toString() } + log.warning { "media-source '$source' not supported by printer" } + log.warning { mediaSourceSupported.toString() } } } } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 500c98c3..0fa7f5ff 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -15,7 +15,9 @@ import de.gmuth.log.* import java.io.* import java.net.URI import java.time.Duration -import java.util.logging.Level.SEVERE +import java.util.logging.Level +import java.util.logging.Level.* +import java.util.logging.Logger import java.util.logging.Logger.getLogger @SuppressWarnings("kotlin:S1192") @@ -55,10 +57,10 @@ class IppPrinter( } init { - log.debug { "create IppPrinter for $printerUri" } + log.fine { "create IppPrinter for $printerUri" } if (printerUri.scheme == "ipps") httpConfig.trustAnyCertificateAndSSLHostname() if (!getPrinterAttributesOnInit) { - log.debug { "getPrinterAttributesOnInit disabled => no printer attributes available" } + log.fine { "getPrinterAttributesOnInit disabled => no printer attributes available" } } else if (attributes.isEmpty()) { try { updateAttributes(requestedAttributesOnInit) @@ -69,18 +71,17 @@ class IppPrinter( } } catch (ippExchangeException: IppExchangeException) { if (ippExchangeException.statusIs(ClientErrorNotFound)) - log.error { ippExchangeException.message } + log.severe { ippExchangeException.message } else { - log.logWithCauseMessages(ippExchangeException, SEVERE) - log.error { "failed to get printer attributes on init" } + log.log(SEVERE, ippExchangeException, { "failed to get printer attributes on init" }) ippExchangeException.response?.let { if (it.containsGroup(Printer)) log.info { "${it.printerGroup.size} attributes parsed" } - else log.warn { it.toString() } + else log.warning { it.toString() } } try { fetchRawPrinterAttributes("getPrinterAttributesFailed.bin") } catch (exception: Exception) { - log.error(exception) { "failed to fetch raw printer attributes" } + log.log(SEVERE, exception, { "failed to fetch raw printer attributes" }) } } throw ippExchangeException @@ -275,10 +276,10 @@ class IppPrinter( getPrinterAttributes(requestedAttributes.toList()) fun updateAttributes(requestedAttributes: List? = null) { - log.debug { "update attributes: $requestedAttributes" } + log.fine { "update attributes: $requestedAttributes" } getPrinterAttributes(requestedAttributes).run { if (containsGroup(Printer)) attributes.put(printerGroup) - else log.warn { "no printerGroup in response for requested attributes: $requestedAttributes" } + else log.warning { "no printerGroup in response for requested attributes: $requestedAttributes" } } } @@ -372,7 +373,7 @@ class IppPrinter( // put attribute in operation or job group? val groupTag = IppRegistrationsSection2.selectGroupForAttribute(attribute.name) ?: Job if (!containsGroup(groupTag)) createAttributesGroup(groupTag) - log.trace { "$groupTag put $attribute" } + log.finer { "$groupTag put $attribute" } getSingleAttributesGroup(groupTag).put(attribute) } } @@ -494,7 +495,7 @@ class IppPrinter( protected fun exchangeForIppJob(request: IppRequest): IppJob { val response = exchange(request) if (request.containsGroup(Subscription) && !response.containsGroup(Subscription)) { - request.logDetails("REQUEST: ") + request.log(log, WARNING, prefix = "REQUEST: ") val events: List = request.getSingleAttributesGroup(Subscription).getValues("notify-events") throw IppException("printer/server did not create subscription for events: ${events.joinToString(",")}") } @@ -517,8 +518,8 @@ class IppPrinter( toString() } - fun logDetails() = - attributes.logDetails(title = "PRINTER-$name ($makeAndModel), $state $stateReasons") + fun log(logger: Logger, level: Level = INFO) = + attributes.log(logger, level, title = "PRINTER-$name ($makeAndModel), $state $stateReasons") // ------------------------------------------------------ // attribute value checking based on printer capabilities @@ -549,7 +550,7 @@ class IppPrinter( IppTag.Enum, Charset, NaturalLanguage, MimeMediaType, Keyword, Resolution -> when (supportedAttributeName) { "media-col-supported" -> with(value as IppCollection) { members.filter { !supportedAttribute.values.contains(it.name) } - .forEach { log.warn { "member unsupported: $it" } } + .forEach { log.warning { "member unsupported: $it" } } // all member names must be supported supportedAttribute.values.containsAll(members.map { it.name }) } @@ -569,11 +570,11 @@ class IppPrinter( else -> null } when (attributeValueIsSupported) { - null -> log.warn { "unable to check if value '$value' is supported by $supportedAttribute" } - true -> log.debug { "$supportedAttributeName: $value" } + null -> log.warning { "unable to check if value '$value' is supported by $supportedAttribute" } + true -> log.fine { "$supportedAttributeName: $value" } false -> { - log.warn { "according to printer attributes value '${supportedAttribute.enumNameOrValue(value)}' is not supported." } - log.warn { "$supportedAttribute" } + log.warning { "according to printer attributes value '${supportedAttribute.enumNameOrValue(value)}' is not supported." } + log.warning { "$supportedAttribute" } } } return attributeValueIsSupported diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt index 94f2c759..9396113c 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt @@ -11,11 +11,13 @@ import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppString import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.warn import java.time.Duration import java.time.Duration.ofSeconds import java.time.LocalDateTime import java.time.LocalDateTime.now +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger class IppSubscription( @@ -135,7 +137,7 @@ class IppSubscription( try { pollHandlesNotifications = true do { - if (expired()) log.warn { "subscription #$id has expired" } + if (expired()) log.warning { "subscription #$id has expired" } getNotifications().forEach { handleNotification(it) } if (expiresAfterDelay() && autoRenewSubscription) renew(leaseDuration) Thread.sleep(pollEvery.toMillis()) @@ -159,6 +161,7 @@ class IppSubscription( toString() } - fun logDetails() = attributes.logDetails(title = "subscription #$id") + fun log(logger: Logger, level: Level = INFO) = + attributes.log(logger, level, title = "subscription #$id") } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt index 415da351..f2bf6c54 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt @@ -1,13 +1,12 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020-2022 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2.attributeIs1setOf import de.gmuth.ipp.iana.IppRegistrationsSection6.getEnumName -import de.gmuth.log.warn import java.nio.charset.Charset import java.util.logging.Logger.getLogger @@ -30,14 +29,14 @@ data class IppAttribute constructor(val name: String, val tag: IppTag) : IppA val value: T get() = values.single().apply { - if (attributeIs1setOf(name) == true) log.warn { "'$name' is registered as '1setOf', use 'values' instead" } + if (attributeIs1setOf(name) == true) log.warning { "'$name' is registered as '1setOf', use 'values' instead" } } @Suppress("UNCHECKED_CAST") fun additionalValue(attribute: IppAttribute<*>): Any = when { attribute.name.isNotEmpty() -> throw IppException("for additional '$name' values attribute name must be empty") attribute.values.size != 1 -> throw IppException("expected 1 additional value, not ${attribute.values.size}") - attribute.tag != tag -> log.warn { "$name: ignore additional value \"$attribute\" - tag is not '$tag'" } + attribute.tag != tag -> log.warning { "$name: ignore additional value \"$attribute\" - tag is not '$tag'" } else -> { validateValueClass(attribute.value as Any) values.add(attribute.value as T) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index 73e0fd17..a37e3ac1 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -4,9 +4,10 @@ package de.gmuth.ipp.core * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.log.debug -import de.gmuth.log.warn import java.io.File +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap>() { @@ -19,7 +20,7 @@ open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap, onReplaceWarn: Boolean = false) = put(attribute.name, attribute).also { - if (it != null && onReplaceWarn) log.warn { "replaced '$it' with '${attribute.values.joinToString(",")}' in group $tag" } + if (it != null && onReplaceWarn) log.warning { "replaced '$it' with '${attribute.values.joinToString(",")}' in group $tag" } } fun attribute(name: String, tag: IppTag, vararg values: Any) = @@ -48,16 +49,16 @@ open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap(name).text fun put(attributesGroup: IppAttributesGroup) { - log.debug { "put ${attributesGroup.size} attributes" } + log.fine { "put ${attributesGroup.size} attributes" } attributesGroup.values.forEach { put(it, false) } } override fun toString() = "'$tag' $size attributes" @JvmOverloads - fun logDetails(prefix: String = "", title: String = "$tag") { - log.info { "${prefix}$title" } - keys.forEach { log.info { "$prefix ${get(it)}" } } + fun log(logger: Logger, level: Level = INFO, prefix: String = "", title: String = "$tag") { + logger.log(level) { "${prefix}$title" } + keys.forEach { logger.log(level) { "$prefix ${get(it)}" } } } fun saveText(file: File) = file.run { diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index 24d3bad5..f9932e2d 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -6,9 +6,6 @@ package de.gmuth.ipp.core import de.gmuth.io.hexdump import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.debug -import de.gmuth.log.trace -import de.gmuth.log.warn import java.io.BufferedInputStream import java.io.DataInputStream import java.io.EOFException @@ -27,13 +24,13 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt fun readMessage(message: IppMessage) { with(message) { version = "${readUnsignedByte()}.${readUnsignedByte()}" - log.debug { "version = $version" } + log.fine { "version = $version" } code = readShort() - log.debug { "code = $code ($codeDescription)" } + log.fine { "code = $code ($codeDescription)" } requestId = readInt() - log.debug { "requestId = $requestId" } + log.fine { "requestId = $requestId" } } lateinit var currentGroup: IppAttributesGroup @@ -48,11 +45,11 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt tag.isValueTag() -> { val attribute = readAttribute(tag) if (attribute.name.isNotEmpty()) { - log.debug { attribute.toString() } + log.fine { attribute.toString() } currentGroup.put(attribute, onReplaceWarn = true) currentAttribute = attribute } else { // name.isEmpty() -> 1setOf - log.debug { IppAttribute(currentAttribute.name, attribute.tag, attribute.value).toString() } + log.fine { IppAttribute(currentAttribute.name, attribute.tag, attribute.value).toString() } currentAttribute.additionalValue(attribute) } } @@ -61,8 +58,8 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt } catch (exception: Exception) { readBytes().apply { if (isNotEmpty()) { - log.warn { "skipped $size unparsed bytes" } - hexdump { log.warn { it } } + log.warning { "skipped $size unparsed bytes" } + hexdump { log.warning { it } } } } throw exception @@ -71,7 +68,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt internal fun readTag() = IppTag.fromByte(readByte()).apply { - if (isDelimiterTag()) log.debug { "--- $this ---" } + if (isDelimiterTag()) log.fine { "--- $this ---" } } internal fun readAttribute(tag: IppTag): IppAttribute { @@ -164,7 +161,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt readCollection() } else { // Xerox B210: workaround for invalid 'media-col' without members - log.warn { "invalid value length for IppCollection, trying to recover" } + log.warning { "invalid value length for IppCollection, trying to recover" } IppCollection() } } @@ -210,7 +207,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt // avoid Java-11-readNBytes(length) for compatibility with older jvms internal fun readBytes(length: Int) = ByteArray(length).apply { - log.trace { "read $length bytes" } + log.finer { "read $length bytes" } readFully(this) } @@ -221,7 +218,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt if (!this) { // unexpected value length reset() // revert 'readShort()' with("expected value length of $expected bytes but found $length") { - if (throwException) throw IppException(this) else log.warn { this } + if (throwException) throw IppException(this) else log.warning { this } } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index 89be7df7..02af9d37 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -7,9 +7,10 @@ package de.gmuth.ipp.core import de.gmuth.io.ByteArraySavingInputStream import de.gmuth.io.ByteArraySavingOutputStream import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.debug -import de.gmuth.log.warn import java.io.* +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger abstract class IppMessage() { @@ -71,10 +72,10 @@ abstract class IppMessage() { IppOutputStream(byteArraySavingOutputStream).writeMessage(this) } finally { rawBytes = byteArraySavingOutputStream.toByteArray() - log.debug { "wrote raw ipp message: ${rawBytes!!.size} bytes" } + log.fine { "wrote raw ipp message: ${rawBytes!!.size} bytes" } byteArraySavingOutputStream.saveBytes = false // stop saving document bytes } - if(hasDocument()) copyDocumentStream(byteArraySavingOutputStream) + if (hasDocument()) copyDocumentStream(byteArraySavingOutputStream) } fun write(file: File) = @@ -83,7 +84,7 @@ abstract class IppMessage() { fun encode(): ByteArray = with(ByteArrayOutputStream()) { write(this) - log.debug { "ByteArrayOutputStream size: ${this.size()} bytes" } + log.fine { "ByteArrayOutputStream size: ${this.size()} bytes" } toByteArray() } @@ -102,17 +103,17 @@ abstract class IppMessage() { documentInputStream = bufferedInputStream } finally { rawBytes = byteArraySavingInputStream.toByteArray() - log.debug { "read ${rawBytes!!.size} raw bytes" } + log.fine { "read ${rawBytes!!.size} raw bytes" } } } fun read(file: File) { - log.debug { "read file ${file.absolutePath}: ${file.length()} bytes" } + log.fine { "read file ${file.absolutePath}: ${file.length()} bytes" } read(FileInputStream(file)) } fun decode(byteArray: ByteArray) { - log.debug { "decode ${byteArray.size} bytes" } + log.fine { "decode ${byteArray.size} bytes" } read(ByteArrayInputStream(byteArray)) } @@ -121,9 +122,9 @@ abstract class IppMessage() { // ------------------------ protected fun copyDocumentStream(outputStream: OutputStream): Long { - if (documentInputStreamIsConsumed) log.warn { "documentInputStream is consumed" } + if (documentInputStreamIsConsumed) log.warning { "documentInputStream is consumed" } return documentInputStream!!.copyTo(outputStream).apply { - log.debug { "consumed documentInputStream: $this bytes" } + log.fine { "consumed documentInputStream: $this bytes" } documentInputStreamIsConsumed = true } } @@ -135,7 +136,7 @@ abstract class IppMessage() { fun saveRawBytes(file: File) = if (rawBytes == null) { - log.warn { "no raw bytes to save. you must call read/decode or write/encode before." } + log.warning { "no raw bytes to save. you must call read/decode or write/encode before." } } else { file.writeBytes(rawBytes!!) log.info { "saved ${file.path} (${file.length()} bytes)" } @@ -151,13 +152,13 @@ abstract class IppMessage() { if (rawBytes == null) "" else " (${rawBytes!!.size} bytes)" ) - fun logDetails(prefix: String = "") { - if (rawBytes != null) log.info { "${prefix}${rawBytes!!.size} raw ipp bytes" } - log.info { "${prefix}version = $version" } - log.info { "${prefix}$codeDescription" } - log.info { "${prefix}request-id = $requestId" } + fun log(logger: Logger, level: Level = INFO, prefix: String = "") { + if (rawBytes != null) logger.log(level) { "${prefix}${rawBytes!!.size} raw ipp bytes" } + logger.log(level) { "${prefix}version = $version" } + logger.log(level) { "${prefix}$codeDescription" } + logger.log(level) { "${prefix}request-id = $requestId" } for (group in attributesGroups) { - group.logDetails(prefix) + group.log(logger, level, prefix = prefix) } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt index 1a9a8f2e..5e9f51a7 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt @@ -1,11 +1,10 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020-2021 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.debug import java.io.DataOutputStream import java.io.OutputStream import java.net.URI @@ -23,13 +22,13 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea attributesCharset = operationGroup.getValue("attributes-charset") writeVersion(version ?: throw IppException("missing version")) - log.debug { "version = $version" } + log.fine { "version = $version" } writeShort(code?.toInt() ?: throw IppException("missing operation or status code")) - log.debug { "code = $code ($codeDescription)" } + log.fine { "code = $code ($codeDescription)" } writeInt(requestId ?: throw IppException("missing requestId")) - log.debug { "requestId = $requestId" } + log.fine { "requestId = $requestId" } for (group in attributesGroups) { writeTag(group.tag) @@ -52,7 +51,7 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea } internal fun writeTag(tag: IppTag) { - if (tag.isDelimiterTag()) log.debug { "--- $tag ---" } + if (tag.isDelimiterTag()) log.fine { "--- $tag ---" } writeByte(tag.code.toInt()) } @@ -64,7 +63,7 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea } internal fun writeAttribute(attribute: IppAttribute<*>) { - log.debug { "$attribute" } + log.fine { "$attribute" } with(attribute) { if (values.isEmpty() || tag.isOutOfBandTag()) { writeTag(tag) diff --git a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt index 2262fa58..b3570661 100644 --- a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt +++ b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.iana /** - * Copyright (c) 2020-2021 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.csv.CSVTable @@ -10,8 +10,6 @@ import de.gmuth.ipp.core.IppCollection import de.gmuth.ipp.core.IppMessage import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* -import de.gmuth.log.trace -import de.gmuth.log.warn import java.util.logging.Logger.getLogger /** @@ -22,20 +20,20 @@ object IppRegistrationsSection2 { val log = getLogger(javaClass.name) data class Attribute( - val collection: String, - val name: String, - val memberAttribute: String, - val subMemberAttribute: String, - val syntax: String, - val reference: String + val collection: String, + val name: String, + val memberAttribute: String, + val subMemberAttribute: String, + val syntax: String, + val reference: String ) { constructor(columns: List) : this( - collection = columns[0], - name = columns[1], - memberAttribute = columns[2], - subMemberAttribute = columns[3], - syntax = columns[4], - reference = columns[5] + collection = columns[0], + name = columns[1], + memberAttribute = columns[2], + subMemberAttribute = columns[3], + syntax = columns[4], + reference = columns[5] ) override fun toString() = String.format("%-30s%-70s%s", collection, key(), syntax) @@ -86,33 +84,39 @@ object IppRegistrationsSection2 { // alias example: Printer Description,media-col-default,"" val aliasMap = mutableMapOf().apply { allAttributes - .filter { it.memberAttribute.lowercase().contains("same as") } - .forEach { put(it.name, it.memberAttribute.replace("^.*\"(.+)\".*$".toRegex(), "$1")) } + .filter { it.memberAttribute.lowercase().contains("same as") } + .forEach { put(it.name, it.memberAttribute.replace("^.*\"(.+)\".*$".toRegex(), "$1")) } // apple cups extension 'output-mode' was standardized to 'print-color-mode' put("output-mode-default", "print-color-mode-default") put("output-mode-supported", "print-color-mode-supported") // 'media-col-default' resolves to 'media-col' and 'media-source-feed-...' values are registered for 'media-col-ready' - put("media-col/media-source-properties/media-source-feed-direction", "media-col-ready/media-source-properties/media-source-feed-direction") - put("media-col/media-source-properties/media-source-feed-orientation", "media-col-ready/media-source-properties/media-source-feed-orientation") + put( + "media-col/media-source-properties/media-source-feed-direction", + "media-col-ready/media-source-properties/media-source-feed-direction" + ) + put( + "media-col/media-source-properties/media-source-feed-orientation", + "media-col-ready/media-source-properties/media-source-feed-orientation" + ) } fun resolveAlias(name: String) = (aliasMap[name] ?: name).also { - if (aliasMap.containsKey(name)) log.trace { "'$name' resolves to '$it'" } + if (aliasMap.containsKey(name)) log.finer { "'$name' resolves to '$it'" } } fun getAttribute(name: String, resolveAlias: Boolean = true) = - attributesMap[if (resolveAlias) resolveAlias(name) else name] + attributesMap[if (resolveAlias) resolveAlias(name) else name] fun syntaxForAttribute(name: String, resolveAlias: Boolean) = - getAttribute(name, resolveAlias)?.syntax + getAttribute(name, resolveAlias)?.syntax - fun tagForAttribute(name: String) = getAttribute(name)?.tag() + fun tagForAttribute(name: String) = getAttribute(name)?.tag() fun attributeIs1setOf(name: String) = - getAttribute(name, false)?.is1setOf() + getAttribute(name, false)?.is1setOf() fun selectGroupForAttribute(name: String) = - getAttribute(name, false)?.collectionGroupTag() + getAttribute(name, false)?.collectionGroupTag() val unknownAttributes = mutableSetOf() @@ -120,10 +124,10 @@ object IppRegistrationsSection2 { if (tag.isOutOfBandTag()) return val syntax = syntaxForAttribute(name, true) if (syntax == null) { - log.trace { "no syntax found for '$name'" } + log.finer { "no syntax found for '$name'" } unknownAttributes.add(name) } else if (!syntax.contains(tag.registeredSyntax())) { - log.warn { "$name ($tag) does not match syntax '$syntax'" } + log.warning { "$name ($tag) does not match syntax '$syntax'" } } } @@ -139,16 +143,16 @@ object IppRegistrationsSection2 { fun validate(ippAttribute: IppAttribute<*>) = with(ippAttribute) { checkSyntaxOfAttribute(name, tag) if (isCollection()) validate(name, values as List) - if (!tag.isOutOfBandTag() && values.isEmpty()) log.warn { "'$name' ($tag) has no values" } - if (values.size > 1 && attributeIs1setOf(name) == false) log.warn { "'$name' is not registered as '1setOf'" } + if (!tag.isOutOfBandTag() && values.isEmpty()) log.warning { "'$name' ($tag) has no values" } + if (values.size > 1 && attributeIs1setOf(name) == false) log.warning { "'$name' is not registered as '1setOf'" } } @Suppress("UNCHECKED_CAST") fun validate(name: String, ippCollections: List) { - log.trace { "validate collection '$name'" } + log.finer { "validate collection '$name'" } val resolvedName = resolveAlias(name) for (ippCollection in ippCollections) { - log.trace { " ${ippCollection.members.size} members" } + log.finer { " ${ippCollection.members.size} members" } for (member in ippCollection.members) { if (member.isCollection()) { validate("$resolvedName/${member.name}", member.values as List) diff --git a/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt b/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt deleted file mode 100644 index ceb805dc..00000000 --- a/src/main/kotlin/de/gmuth/log/JavaUtilLoggingExtensions.kt +++ /dev/null @@ -1,54 +0,0 @@ -package de.gmuth.log - -/** - * Copyright (c) 2023 Gerhard Muth - */ - -import java.util.function.Supplier -import java.util.logging.Level -import java.util.logging.Level.* -import java.util.logging.Logger - -fun Logger.finest(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(FINEST, thrown, msgSupplier) - -fun Logger.finer(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(FINER, thrown, msgSupplier) - -fun Logger.fine(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(FINE, thrown, msgSupplier) - -fun Logger.config(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(CONFIG, thrown, msgSupplier) - -fun Logger.info(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(INFO, thrown, msgSupplier) - -fun Logger.warning(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(WARNING, thrown, msgSupplier) - -fun Logger.severe(thrown: Throwable, msgSupplier: Supplier = Supplier { "" }) = - log(SEVERE, thrown, msgSupplier) - -// ----- aliases ----- - -// trace --> finer -fun Logger.trace(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = - if (thrown == null) finer(msgSupplier) else finer(thrown, msgSupplier) - -// debug --> fine -fun Logger.debug(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = - if (thrown == null) fine(msgSupplier) else fine(thrown, msgSupplier) - -// warn --> warning -fun Logger.warn(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = - if (thrown == null) warning(msgSupplier) else warning(thrown, msgSupplier) - -// error --> severe -fun Logger.error(thrown: Throwable? = null, msgSupplier: Supplier = Supplier { "" }) = - if (thrown == null) severe(msgSupplier) else severe(thrown, msgSupplier) - -fun Logger.logWithCauseMessages(throwable: Throwable, level: Level) { - throwable.cause?.let { logWithCauseMessages(it, level) } - log(level) { "${throwable.javaClass.name}: ${throwable.message}" } -} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 1739dac9..4f05c915 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -53,7 +53,7 @@ class IppJobTests { fun jobAttributes() { job.apply { log.info { toString() } - logDetails() + log(log) assertEquals("ipp://localhost:631/jobs/2366", uri.toString()) assertEquals(0, mediaSheetsCompleted) assertEquals(2, kOctets) @@ -78,7 +78,7 @@ class IppJobTests { httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.apply { updateAttributes() - logDetails() + log(log) assertEquals(31, attributes.size) } } @@ -190,7 +190,7 @@ class IppJobTests { httpClient.ippResponse = cupsDocumentResponse("application/pdf") job.cupsGetDocument().apply { log.info { toString() } - logDetails() + log(log) save().delete() } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt index 45365807..afe75bc1 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt @@ -102,7 +102,7 @@ class IppPrinterTests { httpClient.mockResponse("Simulated_Laser_Printer/Get-Printer-Attributes.ipp") printer.apply { updateAttributes() - logDetails() + log(log) assertEquals(122, attributes.size) } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt b/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt index 5e3c071d..47cbe8fb 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt @@ -1,8 +1,8 @@ package de.gmuth.ipp.client import de.gmuth.http.Http -import de.gmuth.log.error import java.net.URI +import java.util.logging.Level.SEVERE import java.util.logging.Logger.getLogger fun main() { @@ -29,6 +29,6 @@ fun main() { markers.forEach { log.info { it.toString() } } } } catch (exception: Exception) { - log.error(exception) { "failed to connect to $printerUri" } + log.log(SEVERE, exception, { "failed to connect to $printerUri" }) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt b/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt index 4eabfc97..043f1d5e 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/issueNo3.kt @@ -1,8 +1,8 @@ package de.gmuth.ipp.client -import de.gmuth.log.error import java.net.HttpURLConnection import java.net.URI +import java.util.logging.Level.SEVERE import java.util.logging.Logger.getLogger fun main() { @@ -26,7 +26,7 @@ fun main() { ippPrinter = IppPrinter(printerUri, ippConfig = ippConfig, getPrinterAttributesOnInit = true) log.info { "successfully connected $printerUri" } } catch (exception: Exception) { - log.error(exception) { "failed to connect to $printerUri" } + log.log(SEVERE, exception, { "failed to connect to $printerUri" }) } if (ippPrinter != null) ippPrinter.run { @@ -35,14 +35,14 @@ fun main() { savePrinterAttributes() log.info { "saved printer attributes" } } catch (exception: Exception) { - log.error(exception) { "failed to save printer attributes" } + log.log(SEVERE, exception, { "failed to save printer attributes" }) } } try { log.info { "documentFormatSupported:" } documentFormatSupported.forEach { log.info { " $it" } } } catch (exception: Exception) { - log.error(exception) { "failed to read documentFormatSupported" } + log.log(SEVERE, exception, { "failed to read documentFormatSupported" }) } } } @@ -63,6 +63,6 @@ fun httpConnect(printerUri: URI) { log.info { "content: ${contentBytes.size} bytes of type '$contentType'" } } } catch (exception: Exception) { - log.error(exception) { "http connection failed to $printerUrl" } + log.log(SEVERE, exception, { "http connection failed to $printerUrl" }) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index 069d822f..1d520adc 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -6,6 +6,7 @@ package de.gmuth.ipp.core import de.gmuth.ipp.core.IppTag.* import java.io.File +import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -13,6 +14,7 @@ import kotlin.test.assertTrue class IppAttributesGroupTests { + val log = getLogger(javaClass.name) private val group = IppAttributesGroup(Operation) @Test @@ -103,7 +105,7 @@ class IppAttributesGroupTests { @Test fun logDetails() { group.attribute("Commodore PET", Integer, 2001) - group.logDetails("|", "title") + group.log(log, prefix = "|", title = "title") } @Test diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt index a245e463..8148e126 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt @@ -7,10 +7,13 @@ package de.gmuth.ipp.core import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File.createTempFile +import java.util.logging.Logger.getLogger import kotlin.test.* class IppMessageTests { + val log = getLogger(javaClass.name) + private val message = object : IppMessage() { override val codeDescription: String get() = "codeDescription" @@ -54,7 +57,7 @@ class IppMessageTests { assertEquals(38, rawBytes!!.size) write(ByteArrayOutputStream()) // cover warning toString() // cover toString - logDetails() // cover logDetails + log(log) // cover logDetails } } @@ -86,7 +89,7 @@ class IppMessageTests { @Test fun withoutRawBytes() { assertEquals("codeDescription []", message.toString()) - message.logDetails() + message.log(log) // missing raw bytes with(createTempFile("test", null)) { try { diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt index 6b3de901..d442597c 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt @@ -20,7 +20,7 @@ class IppRequestTests { IppRequest().run { code = 5 log.info { toString() } - logDetails() + log(log) assertEquals(null, version) assertEquals(IppOperation.CreateJob, operation) createAttributesGroup(IppTag.Operation) @@ -50,7 +50,7 @@ class IppRequestTests { ) request.documentInputStream = "pdl-content".byteInputStream() log.info { request.toString() } - request.logDetails() + request.log(log) val requestEncoded = request.encode() log.info { "encoded ${requestEncoded.size} bytes" } val requestDecoded = IppRequest() diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt index 35de9bdf..6b09b666 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt @@ -1,17 +1,19 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import java.io.File import java.net.URI +import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class IppResponseTests { + val log = getLogger(javaClass.name) private val ippResponse = IppResponse() @Test @@ -37,7 +39,7 @@ class IppResponseTests { @Test fun invalidXeroxMediaColResponse() { ippResponse.read(File("src/test/resources/invalidXeroxMediaCol.response")) - ippResponse.logDetails() + ippResponse.log(log) with(ippResponse.jobGroup) { assertEquals(598, getValue("job-id")) assertEquals(4, getValue("job-state")) // pending-held @@ -51,7 +53,7 @@ class IppResponseTests { // IppInputStream solution: first mark(2) then NameWithLanguage -> readShort().let { if (markSupported() && it < 6) reset() } // requestNaturalLanguage = "de" // triggers HP name with language bug ippResponse.read(File("src/test/resources/invalidHpNameWithLanguage.response")) - ippResponse.logDetails() + ippResponse.log(log) with(ippResponse.jobGroup) { assertEquals(IppString("A4-blank.pdf", "de"), getValue("job-name")) assertEquals(993, getValue("job-id")) diff --git a/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt b/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt index 0a149fa9..fb377a85 100644 --- a/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt +++ b/src/test/kotlin/de/gmuth/ipp/iana/CSVReader.kt @@ -1,10 +1,9 @@ package de.gmuth.ipp.iana /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2023 Gerhard Muth */ -import de.gmuth.log.debug import java.io.InputStream import java.io.OutputStream import java.io.PrintWriter @@ -34,7 +33,7 @@ class CSVReader(private val rowMapper: RowMapper) { val row = rowMapper.mapRow(columns, ++rowNum) mappedRows.add(row) } - log.debug { "rows read: ${mappedRows.size}" } + log.fine { "rows read: ${mappedRows.size}" } return mappedRows } diff --git a/src/test/kotlin/de/gmuth/ipp/tool/IppTool.kt b/src/test/kotlin/de/gmuth/ipp/tool/IppTool.kt index c430f058..6629305f 100644 --- a/src/test/kotlin/de/gmuth/ipp/tool/IppTool.kt +++ b/src/test/kotlin/de/gmuth/ipp/tool/IppTool.kt @@ -8,8 +8,10 @@ import java.io.InputStream import java.io.Reader import java.net.URI import java.nio.charset.Charset +import java.util.logging.Logger.getLogger class IppTool { + val log = getLogger(javaClass.name) var verbose: Boolean = false var uri: URI? = null var filename: String? = null @@ -33,7 +35,7 @@ class IppTool { val lineItems = line.trim().split("\\s+".toRegex()) if (lineItems.size > 1) interpretLine(lineItems) } - executeIppRequest().logDetails() + executeIppRequest().log(log) } private fun interpretLine(lineItems: List) { diff --git a/src/test/kotlin/de/gmuth/log/Logging.kt b/src/test/kotlin/de/gmuth/log/Logging.kt index c89c6d5f..1189526c 100644 --- a/src/test/kotlin/de/gmuth/log/Logging.kt +++ b/src/test/kotlin/de/gmuth/log/Logging.kt @@ -1,31 +1,15 @@ package de.gmuth.log -import java.io.FileInputStream -import java.util.logging.LogManager -import java.util.logging.Logger - /** * Copyright (c) 2023 Gerhard Muth */ -fun LogManager.readConfigurationFile(filename: String = "logging.properties") = - readConfiguration(FileInputStream(filename)) - .also { println("configured logging by file: $filename") } - -fun LogManager.readConfigurationResource(resource: String = "/logging.properties") = - readConfiguration(Logging::class.java.getResourceAsStream(resource)) - .also { println("configured logging by resource: $resource") } +import java.util.logging.LogManager object Logging { - - init { - LogManager.getLogManager().readConfigurationResource() + fun configure() { + LogManager + .getLogManager() + .readConfiguration(Logging::class.java.getResourceAsStream("/logging.properties")) } - - fun getLogger(name: String) = - Logger.getLogger(name) - - fun getLogger(noOperation: () -> Unit) = - Logger.getLogger(noOperation.javaClass.enclosingClass.name) - } \ No newline at end of file From 5024f6292a0b0cdce0f0d8729244dd337d1fb546 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Wed, 13 Sep 2023 22:03:58 +0300 Subject: [PATCH 08/47] fixed compile issue --- src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 0fa7f5ff..931296a8 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -11,7 +11,6 @@ import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2 -import de.gmuth.log.* import java.io.* import java.net.URI import java.time.Duration From 7c54e336a33b295374b75236dbdfff588bc14449 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Wed, 13 Sep 2023 22:41:33 +0300 Subject: [PATCH 09/47] updated README section about logging and added logging.propertie --- README.md | 28 ++----------------- src/test/resources/logging.properties | 39 +++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 src/test/resources/logging.properties diff --git a/README.md b/README.md index 070a47cc..76067811 100644 --- a/README.md +++ b/README.md @@ -159,32 +159,8 @@ printer.printJob( ## Logging -Log levels can be changed globally or individually. -The `defaultLogLevel` must be changed before any constructor of a logger is called. - -```kotlin -Logging.defaultLogLevel = Logging.LogLevel.ERROR -IppInputStream.log.logLevel = Logging.LogLevel.DEBUG -IppOutputStream.log.logLevel = Logging.LogLevel.TRACE -``` - -A simple stdout console writer is enabled by default and can be disabled. - -```kotlin -Logging.disable() -``` - -You can configure the library to use -[Java Util Logging](https://docs.oracle.com/en/java/javase/11/core/java-logging-overview.html) -or [Slf4j](http://www.slf4j.org). -Then the log levels must be configured according to the underlaying implementation -(e.g. [logback](http://logback.qos.ch/manual/configuration.html) -or [Slf4j-Android](http://www.slf4j.org/android/)). - -```kotlin -JulAdapter.configure() -Slf4jAdapter.configure() -``` +From version 3.0 onwards the library uses [java.util.logging](https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html) - configure as you like. +Tests can use Logging.configure() to load logging.properties from test/resources. ## Build diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties new file mode 100644 index 00000000..f587ff64 --- /dev/null +++ b/src/test/resources/logging.properties @@ -0,0 +1,39 @@ +# ------ levels ------ + +.level=INFO +de.gmuth.level=ALL +#de.gmuth.ipp.core.level=INFO +#de.gmuth.ipp.client.level=INFO +sun.net.www.protocol.level=FINEST + +# ------- formatters ------- + +# https://docs.oracle.com/javase/7/docs/api/java/util/logging/SimpleFormatter.html +# https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax +# %1 date - a Date object representing event time of the log record. +# %2 source - a string representing the caller, if available; otherwise, the logger's name. +# %3 logger - the logger's name. +# %4 level - the log level. +# %5 message - the formatted log message +# %6 thrown + +java.util.logging.SimpleFormatter.format=%1$tT.%1$tL %3$-25s%4$-9s%5$s%6$s%n +java.util.logging.FileHandler.formatter=de.gmuth.log.SimpleClassNameFormatter +de.gmuth.log.ConsoleHandler.formatter=de.gmuth.log.SimpleClassNameFormatter +de.gmuth.log.StdoutHandler.formatter=de.gmuth.log.SimpleClassNameFormatter +de.gmuth.log.SimpleClassNameFormatter.simpleClassName=true + +# ------ handlers ------ + +handlers=de.gmuth.log.StdoutHandler,java.util.logging.FileHandler +#java.util.logging.FileHandler +#java.util.logging.ConsoleHandler +#de.gmuth.log.StdoutHandler +#de.gmuth.log.ConsoleHandler + +de.gmuth.log.StdoutHandler.level=INFO +#java.util.logging.ConsoleHandler.level=ALL + +java.util.logging.FileHandler.level=FINE +java.util.logging.FileHandler.append=false +java.util.logging.FileHandler.pattern=ipp-client.log \ No newline at end of file From 222f655c687c47399da185d970cfc16527fd877c Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Wed, 13 Sep 2023 23:23:03 +0300 Subject: [PATCH 10/47] hint to logging-kotlin --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 76067811..2ce15e43 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,12 @@ printer.printJob( ## Logging -From version 3.0 onwards the library uses [java.util.logging](https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html) - configure as you like. +From version 3.0 onwards the library uses [Java Logging](https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html) - configure as you like. Tests can use Logging.configure() to load logging.properties from test/resources. +The behaviour of the my previously used [ConsoleLogger](https://github.com/gmuth/logging-kotlin/blob/main/src/main/kotlin/de/gmuth/log/ConsoleLogger.kt) is now implemented by StdoutHandler and SimpleClassNameFormatter. +I moved all of my custom logging code to it's own repository [logging-kotlin](https://github.com/gmuth/logging-kotlin/tree/main/src/main/kotlin/de/gmuth/log). + ## Build To build the jar make sure you have JDK 11 installed. From dd9588d0daa9dac69fda3870d141a287b50033e1 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 14 Sep 2023 00:01:00 +0300 Subject: [PATCH 11/47] refactored log details --- README.md | 3 +-- .../de/gmuth/ipp/client/CupsPrinterType.kt | 9 ++++--- .../kotlin/de/gmuth/ipp/core/IppAttribute.kt | 13 +++++---- .../kotlin/de/gmuth/ipp/core/IppCollection.kt | 9 ++++--- .../resources/ippclient-logging.properties | 27 ------------------- .../de/gmuth/ipp/client/IppPrinterTests.kt | 16 +++++------ .../de/gmuth/ipp/core/IppAttributeTests.kt | 6 ++--- .../gmuth/ipp/core/IppAttributesGroupTests.kt | 2 +- .../de/gmuth/ipp/core/IppCollectionTests.kt | 10 ++++--- .../de/gmuth/ipp/core/IppMessageTests.kt | 2 +- 10 files changed, 40 insertions(+), 57 deletions(-) delete mode 100644 src/main/resources/ippclient-logging.properties diff --git a/README.md b/README.md index 2ce15e43..d2813e7b 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Repository [ipp-samples](https://github.com/gmuth/ipp-samples) contains examples ```kotlin // initialize printer connection and show printer attributes val ippPrinter = IppPrinter(URI.create("ipp://colorjet.local/ipp/printer")) -ippPrinter.attributes.logDetails() +ippPrinter.attributes.log(logger) // marker levels ippPrinter.markers.forEach { println(it) } @@ -47,7 +47,6 @@ val job = ippPrinter.printJob( mediaColSource("tray-1"), notifyEvents = listOf("job-state-changed", "job-stopped", "job-completed") // CUPS ) -job.logDetails() job.subscription?.processEvents { println(it) } // print remote file, make printer pull document from remote server diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt index 4ea4a2e5..b8f27dcd 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt @@ -4,6 +4,9 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html @@ -51,10 +54,10 @@ class CupsPrinterType(val value: Int) { override fun toString() = "$value (${toSet().joinToString(",")})" - fun logDetails() { - log.info { "PRINTER-TYPE 0x%08X capabilities:".format(value) } + fun log(logger: Logger, level: Level = INFO) = logger.run { + log(level) { "PRINTER-TYPE 0x%08X capabilities:".format(value) } for (capability in toSet()) { - log.info { "* ${capability.description}" } + log(level) { "* ${capability.description}" } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt index f2bf6c54..9a21c9c5 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt @@ -8,6 +8,9 @@ import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2.attributeIs1setOf import de.gmuth.ipp.iana.IppRegistrationsSection6.getEnumName import java.nio.charset.Charset +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger data class IppAttribute constructor(val name: String, val tag: IppTag) : IppAttributeBuilder { @@ -69,17 +72,17 @@ data class IppAttribute constructor(val name: String, val tag: IppTag) : IppA fun enumNameOrValue(value: Any) = if (tag == IppTag.Enum) getEnumName(name, value) else value - fun logDetails(prefix: String = "") { + fun log(logger: Logger, level: Level = INFO, prefix: String = "") = logger.run { val string = toString() if (string.length < 160) { - log.info { "$prefix$string" } + log(level) { "$prefix$string" } } else { - log.info { "$prefix$name ($tag) =" } + log(level) { "$prefix$name ($tag) =" } for (value in values) { if (value is IppCollection) { - value.logDetails("$prefix ") + (value as IppCollection).log(logger, level, "$prefix ") } else { - log.info { "$prefix ${enumNameOrValue(value as Any)}" } + log(level) { "$prefix ${enumNameOrValue(value as Any)}" } } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt index b663a864..b6b1f191 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt @@ -1,5 +1,8 @@ package de.gmuth.ipp.core +import java.util.logging.Level +import java.util.logging.Level.INFO +import java.util.logging.Logger import java.util.logging.Logger.getLogger /** @@ -30,10 +33,10 @@ data class IppCollection(val members: MutableCollection> = mutab "${it.name}=${it.values.joinToString(",")}" } - fun logDetails(prefix: String = "") { + fun log(logger: Logger, level: Level = INFO, prefix: String = "") { val string = toString() - if (string.length < 160) log.info { "$prefix$string" } - else members.forEach { member -> member.logDetails(prefix) } + if (string.length < 160) logger.log(level) { "$prefix$string" } + else members.forEach { member -> member.log(logger, level, prefix) } } } \ No newline at end of file diff --git a/src/main/resources/ippclient-logging.properties b/src/main/resources/ippclient-logging.properties deleted file mode 100644 index aab57712..00000000 --- a/src/main/resources/ippclient-logging.properties +++ /dev/null @@ -1,27 +0,0 @@ -# activate: JulAdapter.configure("/ipp-client-logging.conf") -# https://docs.oracle.com/javase/7/docs/api/java/util/logging/SimpleFormatter.html -# https://docs.oracle.com/javase/7/docs/api/java/util/Formatter.html#syntax -# %1 format - the java.util.Formatter format string specified in the java.util.logging.SimpleFormatter.format property or the default format. -# %2 date - a Date object representing event time of the log record. -# %3 source - a string representing the caller, if available; otherwise, the logger's name. -# %3 logger - the logger's name. -# %4 level - the log level. -# %5 message - the formatted log message -# %6 thrown - -handlers=java.util.logging.ConsoleHandler -java.util.logging.ConsoleHandler.level=ALL -java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter -java.util.logging.SimpleFormatter.format=%1$tT.%1$tL %3$-50s%4$-15s%5$s%6$s%n - -sun.net.www.protocol.http.level=FINE - -# verbose -#de.gmuth.ipp.client.IppClient.level=FINE -#de.gmuth.ipp.core.IppOutputStream.level=FINE -#de.gmuth.ipp.core.IppInputStream.level=FINE -#sun.net.www.protocol.level=FINEST - -# very verbose -#.level=ALL -#de.gmuth.level=ALL diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt index afe75bc1..a435cad0 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt @@ -33,7 +33,7 @@ import kotlin.test.assertTrue class IppPrinterTests { - val log = getLogger(javaClass.name) + val tlog = getLogger(javaClass.name) val blankPdf = File("tool/A4-blank.pdf") val httpClient = HttpClientMock() val ippConfig = IppConfig() @@ -50,8 +50,8 @@ class IppPrinterTests { @Test fun printerAttributes() { - printer.apply { - log.info { toString() } + printer.run { + tlog.info { toString() } assertTrue(isAcceptingJobs) assertTrue(documentFormatSupported.contains("application/pdf")) assertTrue(supportsOperations(GetPrinterAttributes)) @@ -81,13 +81,13 @@ class IppPrinterTests { assertFalse(isMediaNeeded()) assertFalse(isCups()) printerType.apply { - log.info { toString() } - logDetails() + tlog.info { toString() } + log(tlog) } communicationChannelsSupported.forEach { - log.info { "${it.uri}, ${it.security}, ${it.authentication}, $it" } + tlog.info { "${it.uri}, ${it.security}, ${it.authentication}, $it" } } - ippConfig.log(log) + ippConfig.log(tlog) } } @@ -102,7 +102,7 @@ class IppPrinterTests { httpClient.mockResponse("Simulated_Laser_Printer/Get-Printer-Attributes.ipp") printer.apply { updateAttributes() - log(log) + log(tlog) assertEquals(122, attributes.size) } } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt index 3a128d28..d8cae28a 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt @@ -12,7 +12,7 @@ import kotlin.test.* class IppAttributeTests { private val ippAttribute = IppAttribute("printer-state-reasons", Keyword, "none") - val log = getLogger(javaClass.name) + val tlog = getLogger(javaClass.name) @Test fun constructorFailsDueToDelimiterTag() { @@ -103,9 +103,9 @@ class IppAttributeTests { } @Test - fun logDetails() { + fun log() { // cover an output with more than 160 characters and a collection value - IppAttribute("media-col".padEnd(160, '-'), BegCollection, IppCollection()).logDetails() + IppAttribute("media-col".padEnd(160, '-'), BegCollection, IppCollection()).log(tlog) } @Test diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index 1d520adc..ca74eda1 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -103,7 +103,7 @@ class IppAttributesGroupTests { } @Test - fun logDetails() { + fun log() { group.attribute("Commodore PET", Integer, 2001) group.log(log, prefix = "|", title = "title") } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt index 75e55e4e..8887410e 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt @@ -5,12 +5,14 @@ package de.gmuth.ipp.core */ import java.util.NoSuchElementException +import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith class IppCollectionTests { + val tlog = getLogger(javaClass.name) private val collection = IppCollection(IppAttribute("foo", IppTag.Keyword, "a", "b")) @Test @@ -38,14 +40,14 @@ class IppCollectionTests { } @Test - fun logDetailsNarrow() { - collection.logDetails() + fun logNarrow() { + collection.log(tlog) } @Test - fun logDetailsWide() { + fun logWide() { collection.addAll(listOf(IppAttribute("bar", IppTag.Keyword, "c".repeat(160)))) - collection.logDetails() + collection.log(tlog) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt index 8148e126..d9e7077f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt @@ -57,7 +57,7 @@ class IppMessageTests { assertEquals(38, rawBytes!!.size) write(ByteArrayOutputStream()) // cover warning toString() // cover toString - log(log) // cover logDetails + log(log) // cover log } } From 6abb19a3d2d0c3e3843279ce914ac4e8c4ee67fa Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 14 Sep 2023 00:18:42 +0300 Subject: [PATCH 12/47] run sonar only in pipeline --- build.gradle.kts | 62 ++++++++++++++++++++++++++--------------------- gradle.properties | 9 +++---- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 245ccf3d..307eb880 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,5 @@ import org.jetbrains.dokka.gradle.DokkaTask +import java.lang.System // how to build? run ./gradlew // where is the jar? build/lib/ipp-client-kotlin...jar @@ -166,38 +167,45 @@ repo?.run { // tasks for publishing on maven repo // ====== analyse code with SonarQube ====== -// required for sonarqube code coverage -// https://docs.sonarqube.org/latest/analysis/test-coverage/java-test-coverage -tasks.jacocoTestReport { - dependsOn(tasks.test) - // https://stackoverflow.com/questions/67725347/jacoco-fails-on-gradle-7-0-2-and-kotlin-1-5-10 - //version = "0.8.7" - reports { - xml.required.set(true) - csv.required.set(false) - html.required.set(false) +val isRunningOnGithub = System.getProperty("CI")?.toBoolean() ?: false +println("isRunningOnGithub=$isRunningOnGithub") +if(isRunningOnGithub) { + + // required for sonarqube code coverage + // https://docs.sonarqube.org/latest/analysis/test-coverage/java-test-coverage + tasks.jacocoTestReport { + dependsOn(tasks.test) + // https://stackoverflow.com/questions/67725347/jacoco-fails-on-gradle-7-0-2-and-kotlin-1-5-10 + //version = "0.8.7" + reports { + xml.required.set(true) + csv.required.set(false) + html.required.set(false) + } + } + + // gradle test jacocoTestReport sonar + // https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-gradle/ + // configure token with 'publish analysis' permission in file ~/.gradle/gradle.properties: + // systemProp.sonar.login= + sonar { + properties { + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.projectKey", "gmuth_ipp-client-kotlin") + property("sonar.organization", "gmuth") + //property("sonar.verbose", "true") + //property("sonar.junit.reportPaths", "build/test-results/test") + //property("sonar.jacoco.reportPaths", "build/jacoco/test.exec") + //property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/") + } } -} -// gradle test jacocoTestReport sonar -// https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-gradle/ -// configure token with 'publish analysis' permission in file ~/.gradle/gradle.properties: -// systemProp.sonar.login= -sonar { - properties { - property("sonar.host.url", "https://sonarcloud.io") - property("sonar.projectKey", "gmuth_ipp-client-kotlin") - property("sonar.organization", "gmuth") - //property("sonar.verbose", "true") - //property("sonar.junit.reportPaths", "build/test-results/test") - //property("sonar.jacoco.reportPaths", "build/jacoco/test.exec") - //property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/") + tasks.sonar { + dependsOn(tasks.jacocoTestReport) // for coverage } } -tasks.sonar { - dependsOn(tasks.jacocoTestReport) // for coverage -} +// ====== fat jar ====== tasks.register("fatJar", Jar::class) { description = "Generates a fat jar for this project." diff --git a/gradle.properties b/gradle.properties index 76ee9d93..7505c484 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,7 @@ # solution for "sonarqube jvm out of memory" -org.gradle.jvmargs=-Xmx3g +#org.gradle.jvmargs=-Xmx3g -# java -XX:+PrintFlagsFinal -version# | grep HeapSize +# java -XX:+PrintFlagsFinal -version|grep MaxHeapSize # Oracle-arm 11.0.5: MaxHeapSize = 2147483648 -#org.gradle.daemon=false - -slf4jVersion=1.7.36 -androidVersion=4.1.1.4 +#org.gradle.daemon=false \ No newline at end of file From b83dcc50564f2ce6c4338b2f0ff9d9de392c83ca Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 14 Sep 2023 00:24:16 +0300 Subject: [PATCH 13/47] CI env --- build.gradle.kts | 61 +++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 307eb880..29eca917 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,4 @@ import org.jetbrains.dokka.gradle.DokkaTask -import java.lang.System // how to build? run ./gradlew // where is the jar? build/lib/ipp-client-kotlin...jar @@ -167,44 +166,42 @@ repo?.run { // tasks for publishing on maven repo // ====== analyse code with SonarQube ====== -val isRunningOnGithub = System.getProperty("CI")?.toBoolean() ?: false +val isRunningOnGithub = System.getenv("CI")?.toBoolean() ?: false println("isRunningOnGithub=$isRunningOnGithub") -if(isRunningOnGithub) { - - // required for sonarqube code coverage - // https://docs.sonarqube.org/latest/analysis/test-coverage/java-test-coverage - tasks.jacocoTestReport { - dependsOn(tasks.test) - // https://stackoverflow.com/questions/67725347/jacoco-fails-on-gradle-7-0-2-and-kotlin-1-5-10 - //version = "0.8.7" - reports { - xml.required.set(true) - csv.required.set(false) - html.required.set(false) - } - } - // gradle test jacocoTestReport sonar - // https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-gradle/ - // configure token with 'publish analysis' permission in file ~/.gradle/gradle.properties: - // systemProp.sonar.login= - sonar { - properties { - property("sonar.host.url", "https://sonarcloud.io") - property("sonar.projectKey", "gmuth_ipp-client-kotlin") - property("sonar.organization", "gmuth") - //property("sonar.verbose", "true") - //property("sonar.junit.reportPaths", "build/test-results/test") - //property("sonar.jacoco.reportPaths", "build/jacoco/test.exec") - //property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/") - } +// required for sonarqube code coverage +// https://docs.sonarqube.org/latest/analysis/test-coverage/java-test-coverage +tasks.jacocoTestReport { + dependsOn(tasks.test) + // https://stackoverflow.com/questions/67725347/jacoco-fails-on-gradle-7-0-2-and-kotlin-1-5-10 + //version = "0.8.7" + reports { + xml.required.set(true) + csv.required.set(false) + html.required.set(false) } +} - tasks.sonar { - dependsOn(tasks.jacocoTestReport) // for coverage +// gradle test jacocoTestReport sonar +// https://docs.sonarqube.org/latest/analysis/scan/sonarscanner-for-gradle/ +// configure token with 'publish analysis' permission in file ~/.gradle/gradle.properties: +// systemProp.sonar.login= +sonar { + properties { + property("sonar.host.url", "https://sonarcloud.io") + property("sonar.projectKey", "gmuth_ipp-client-kotlin") + property("sonar.organization", "gmuth") + //property("sonar.verbose", "true") + //property("sonar.junit.reportPaths", "build/test-results/test") + //property("sonar.jacoco.reportPaths", "build/jacoco/test.exec") + //property("sonar.coverage.jacoco.xmlReportPaths", "build/reports/jacoco/test/") } } +tasks.sonar { + dependsOn(tasks.jacocoTestReport) // for coverage +} + // ====== fat jar ====== tasks.register("fatJar", Jar::class) { From a5fcf5e9842e3323df20ff71fa858a0740740a91 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 14 Sep 2023 00:31:01 +0300 Subject: [PATCH 14/47] Xmx for sonar --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7505c484..554f1f9e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # solution for "sonarqube jvm out of memory" -#org.gradle.jvmargs=-Xmx3g +org.gradle.jvmargs=-Xmx3g # java -XX:+PrintFlagsFinal -version|grep MaxHeapSize # Oracle-arm 11.0.5: MaxHeapSize = 2147483648 From 898462322e2da82274516360ebb32a3d850b903b Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 16 Sep 2023 20:18:44 +0200 Subject: [PATCH 15/47] eliminated own http layer/package in favour of standard HttpURLConnection --- build.gradle.kts | 3 +- src/main/kotlin/de/gmuth/http/Http.kt | 66 -------- .../de/gmuth/http/HttpURLConnectionClient.kt | 65 -------- .../kotlin/de/gmuth/http/JavaHttpClient.kt | 77 --------- .../kotlin/de/gmuth/ipp/client/CupsClient.kt | 21 +-- .../kotlin/de/gmuth/ipp/client/IppClient.kt | 148 +++++++++--------- .../kotlin/de/gmuth/ipp/client/IppConfig.kt | 27 ++++ .../gmuth/ipp/client/IppExchangeException.kt | 18 ++- .../kotlin/de/gmuth/ipp/client/IppPrinter.kt | 30 +--- .../gmuth/{http => ipp/client}/SSLHelper.kt | 2 +- .../kotlin/de/gmuth/ipp/core/IppException.kt | 2 +- .../kotlin/de/gmuth/ipp/core/IppResponse.kt | 14 +- .../kotlin/de/gmuth/http/HttpClientMock.kt | 39 ----- .../de/gmuth/ipp/client/CupsClientTests.kt | 41 ++--- .../de/gmuth/ipp/client/IppClientMock.kt | 51 ++++++ .../de/gmuth/ipp/client/IppClientTests.kt | 18 +-- .../kotlin/de/gmuth/ipp/client/IppJobTests.kt | 56 +++---- .../de/gmuth/ipp/client/IppPrinterTests.kt | 59 ++++--- .../kotlin/de/gmuth/ipp/client/issueNo11.kt | 3 +- 19 files changed, 265 insertions(+), 475 deletions(-) delete mode 100644 src/main/kotlin/de/gmuth/http/Http.kt delete mode 100644 src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt delete mode 100644 src/main/kotlin/de/gmuth/http/JavaHttpClient.kt rename src/main/kotlin/de/gmuth/{http => ipp/client}/SSLHelper.kt (98%) delete mode 100644 src/test/kotlin/de/gmuth/http/HttpClientMock.kt create mode 100644 src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt diff --git a/build.gradle.kts b/build.gradle.kts index 29eca917..addf4dae 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,9 +52,10 @@ tasks.compileKotlin { // avoid warnings "jvm target compatibility should be set to the same Java version." tasks.compileTestKotlin { kotlinOptions { - jvmTarget = "11" + jvmTarget = "1.8" } } + tasks.compileJava { sourceCompatibility = tasks.compileKotlin.get().kotlinOptions.jvmTarget targetCompatibility = tasks.compileKotlin.get().kotlinOptions.jvmTarget diff --git a/src/main/kotlin/de/gmuth/http/Http.kt b/src/main/kotlin/de/gmuth/http/Http.kt deleted file mode 100644 index 32978bf8..00000000 --- a/src/main/kotlin/de/gmuth/http/Http.kt +++ /dev/null @@ -1,66 +0,0 @@ -package de.gmuth.http - -/** - * Copyright (c) 2020-2023 Gerhard Muth - */ - -import de.gmuth.http.Http.Implementation.JavaHttpURLConnection -import java.io.InputStream -import java.io.OutputStream -import java.net.URI -import java.nio.charset.Charset -import java.util.* -import javax.net.ssl.SSLContext - -interface Http { - - data class Config( - var timeout: Int = 30000, // milli seconds - var userAgent: String? = null, - var basicAuth: BasicAuth? = null, - var sslContext: SSLContext? = null, - // trust any certificate: sslContextForAnyCertificate() - // use individual certificate: sslContext(loadCertificate(FileInputStream("printer.pem"))) - // use truststore: sslContext(loadKeyStore(FileInputStream("printer.jks"), "changeit")) - var verifySSLHostname: Boolean = true, - var accept: String? = "application/ipp", // avoid 'text/html' with sun.net.www.protocol.http.HttpURLConnection - var acceptEncoding: String? = "identity", // avoid 'gzip' with Androids OkHttp - var debugLogging: Boolean = false - ) { - fun trustAnyCertificateAndSSLHostname() { - sslContext = SSLHelper.sslContextForAnyCertificate() - verifySSLHostname = false - } - } - - // https://stackoverflow.com/questions/7242316/what-encoding-should-i-use-for-http-basic-authentication - data class BasicAuth(val user: String, val password: String, val charset: Charset = Charsets.UTF_8) { - - fun encodeBase64(): String = Base64.getEncoder() - .encodeToString("$user:$password".toByteArray(charset)) - - fun authorization() = - "Basic " + encodeBase64() - } - - data class Response( - val status: Int, val server: String?, val contentType: String?, val contentStream: InputStream? - ) - - abstract class Client(val config: Config) { - abstract fun post( - uri: URI, contentType: String, writeContent: (OutputStream) -> Unit, chunked: Boolean = false - ): Response - } - - // standard jvm implementations - enum class Implementation(val createClient: (config: Config) -> Client) { - JavaHttpURLConnection({ HttpURLConnectionClient(it) }), - Java11HttpClient({ JavaHttpClient(it) }) - } - - companion object { - var defaultImplementation: Implementation = JavaHttpURLConnection - } - -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt b/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt deleted file mode 100644 index c5bd1372..00000000 --- a/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt +++ /dev/null @@ -1,65 +0,0 @@ -package de.gmuth.http - -/** - * Copyright (c) 2020-2023 Gerhard Muth - */ - -import java.io.OutputStream -import java.net.HttpURLConnection -import java.net.URI -import java.util.logging.Level -import java.util.logging.Logger.getLogger -import javax.net.ssl.HostnameVerifier -import javax.net.ssl.HttpsURLConnection - -class HttpURLConnectionClient(config: Http.Config = Http.Config()) : Http.Client(config) { - - val log = getLogger(javaClass.name) - - init { - log.fine { "HttpURLConnectionClient created" } - if (config.debugLogging) { - getLogger("sun.net.www.protocol.http.HttpURLConnection").level = Level.FINER - } - } - - override fun post( - uri: URI, contentType: String, writeContent: (OutputStream) -> Unit, chunked: Boolean - ): Http.Response { - with(uri.toURL().openConnection() as HttpURLConnection) { - if (this is HttpsURLConnection && config.sslContext != null) { - sslSocketFactory = config.sslContext!!.socketFactory - if (!config.verifySSLHostname) hostnameVerifier = HostnameVerifier { _, _ -> true } - } - doOutput = true // trigger POST method - config.run { - connectTimeout = timeout - readTimeout = timeout - accept?.let { setRequestProperty("Accept", it) } - acceptEncoding?.let { setRequestProperty("Accept-Encoding", it) } - basicAuth?.let { setRequestProperty("Authorization", it.authorization()) } - userAgent?.let { setRequestProperty("User-Agent", it) } - } - setRequestProperty("Content-Type", contentType) - if (chunked) setChunkedStreamingMode(0) - writeContent(outputStream) - for ((key, values) in headerFields) { - val logLevel = when { - responseCode < 300 -> Level.FINE - responseCode in 400..499 -> Level.INFO - else -> Level.WARNING - } - log.log(logLevel) { "$key = $values" } - } - val responseStream = try { - inputStream - } catch (exception: Exception) { - log.severe { "http exception: $responseCode $responseMessage" } - errorStream - } - return Http.Response( - responseCode, getHeaderField("Server"), getHeaderField("Content-Type"), responseStream - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt b/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt deleted file mode 100644 index 09d5b912..00000000 --- a/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt +++ /dev/null @@ -1,77 +0,0 @@ -package de.gmuth.http - -/** - * Copyright (c) 2020-2023 Gerhard Muth - */ - -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.OutputStream -import java.lang.System.setProperty -import java.net.URI -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpRequest.BodyPublishers -import java.net.http.HttpResponse.BodyHandlers -import java.time.Duration -import java.util.logging.Level -import java.util.logging.Logger.getLogger - -// requires Java >=11 -class JavaHttpClient(config: Http.Config = Http.Config()) : Http.Client(config) { - - companion object { - val log = getLogger(JavaHttpClient::javaClass.name) - fun isSupported() = try { - HttpClient.newHttpClient() - true - } catch (exception: ClassNotFoundException) { - log.log(Level.FINER, exception, { "HttpClient not found" }) - false - }.apply { - log.fine { "Java HttpClient supported: $this" } - } - } - - init { - log.fine { "JavaHttpClient created" } - if (!config.verifySSLHostname) - setProperty("jdk.internal.httpclient.disableHostnameVerification", true.toString()) - } - - val httpClient by lazy { - HttpClient.newBuilder().run { - config.sslContext?.let { sslContext(it) } - build() - } - } - - override fun post( - uri: URI, - contentType: String, - writeContent: (OutputStream) -> Unit, - chunked: Boolean - ): Http.Response { - val content = ByteArrayOutputStream().also { writeContent(it) }.toByteArray() - val request = HttpRequest.newBuilder().run { - with(config) { - timeout(Duration.ofMillis(timeout.toLong())) - userAgent?.let { header("User-Agent", it) } - acceptEncoding?.let { header("Accept-Encoding", it) } - basicAuth?.let { header("Authorization", it.authorization()) } - } - header("Content-Type", contentType) - POST(BodyPublishers.ofInputStream { ByteArrayInputStream(content) }) - uri(uri) - build() - } - httpClient.send(request, BodyHandlers.ofInputStream()).run { - return Http.Response( - statusCode(), - headers().firstValue("server").run { if (isPresent) get() else null }, - headers().firstValue("content-type").get(), - body() - ) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index 499def9c..775a7b51 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -4,7 +4,6 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.http.Http import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException import de.gmuth.ipp.core.IppOperation import de.gmuth.ipp.core.IppOperation.* @@ -21,23 +20,19 @@ import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html open class CupsClient( val cupsUri: URI = URI.create("ipp://localhost"), - val ippConfig: IppConfig = IppConfig(), - httpClient: Http.Client = Http.defaultImplementation.createClient(Http.Config()) + val ippClient: IppClient = IppClient() ) { constructor(host: String = "localhost") : this(URI.create("ipp://$host")) val log = getLogger(javaClass.name) - var userName: String? by ippConfig::userName - val httpConfig: Http.Config by httpClient::config + val config: IppConfig by ippClient::config + var userName: String? by config::userName var cupsClientWorkDirectory = File("cups-${cupsUri.host}") - val ippClient = IppClient(ippConfig, httpClient = httpClient) init { - if (cupsUri.scheme == "ipps") httpConfig.trustAnyCertificateAndSSLHostname() + if (cupsUri.scheme == "ipps") config.trustAnyCertificateAndSSLHostname() } - fun getIppServer() = ippClient.getHttpServer() - fun getPrinters() = try { exchange(ippRequest(CupsGetPrinters)) .getAttributesGroups(Printer) @@ -326,14 +321,14 @@ open class CupsClient( } tryToGetDocuments() if (ippExchangeException != null && ippExchangeException!!.httpStatus == 401) { - val configuredUserName = ippConfig.userName + val configuredUserName = config.userName val jobOwnersIterator = jobOwners.iterator() while (jobOwnersIterator.hasNext() && ippExchangeException != null) { - ippConfig.userName = jobOwnersIterator.next() - log.fine { "set userName '${ippConfig.userName}'" } + config.userName = jobOwnersIterator.next() + log.fine { "set userName '${config.userName}'" } tryToGetDocuments() } - ippConfig.userName = configuredUserName + config.userName = configuredUserName } documents.onEach { document -> document.save(job.printerDirectory(), overwrite = true) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt index a19d7c94..db66adbf 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt @@ -4,7 +4,6 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.http.Http import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException import de.gmuth.ipp.core.IppException import de.gmuth.ipp.core.IppOperation @@ -15,48 +14,32 @@ import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.Unsupported import de.gmuth.ipp.iana.IppRegistrationsSection2 import java.io.File +import java.net.HttpURLConnection import java.net.URI import java.util.concurrent.atomic.AtomicInteger -import java.util.logging.Level.FINE -import java.util.logging.Level.WARNING +import java.util.logging.Level.SEVERE import java.util.logging.Logger.getLogger +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.HttpsURLConnection typealias IppResponseInterceptor = (request: IppRequest, response: IppResponse) -> Unit -open class IppClient( - val config: IppConfig = IppConfig(), - val httpConfig: Http.Config = Http.Config(), - val httpClient: Http.Client = Http.defaultImplementation.createClient(httpConfig) -) { +open class IppClient(val config: IppConfig = IppConfig()) { val log = getLogger(javaClass.name) var saveMessages: Boolean = false var saveMessagesDirectory = File("ipp-messages") var responseInterceptor: IppResponseInterceptor? = null + //var server: String? = null fun basicAuth(user: String, password: String) { - httpConfig.basicAuth = Http.BasicAuth(user, password) config.userName = user + config.password = password } companion object { const val APPLICATION_IPP = "application/ipp" - const val version = "3.0-SNAPSHOT" - const val build = "09-2023" - - init { - println("IPP-Client: Version: $version, Build: $build, MIT License, (c) 2020-2023 Gerhard Muth") - } - } - - init { - with(httpConfig) { if (userAgent == null) userAgent = "ipp-client/$version" } } - private var httpServer: String? = null - - @SuppressWarnings("kotlin:S6512") // read only - fun getHttpServer() = httpServer - //----------------- // build IppRequest //----------------- @@ -87,13 +70,10 @@ open class IppClient( open fun exchange(request: IppRequest, throwWhenNotSuccessful: Boolean = true): IppResponse { val ippUri: URI = request.printerUri - val httpUri = toHttpUri(ippUri) log.finer { "send '${request.operation}' request to $ippUri" } - val httpResponse = httpPostRequest(httpUri, request) - val response = decodeIppResponse(request, httpResponse) + val response = postRequest(toHttpUri(ippUri), request) log.fine { "$ippUri: $request => $response" } - httpServer = httpResponse.server if (saveMessages) { val messageSubDirectory = File(saveMessagesDirectory, ippUri.host).apply { @@ -107,11 +87,15 @@ open class IppClient( responseInterceptor?.invoke(request, response) - if (!response.isSuccessful()) { - IppRegistrationsSection2.validate(request) - if (throwWhenNotSuccessful) - throw if (response.status == ClientErrorNotFound) ClientErrorNotFoundException(request, response) - else IppExchangeException(request, response) + with(response) { + if (status == ClientErrorBadRequest) request.log(log, SEVERE, prefix = "BAD-REQUEST: ") + if (containsGroup(Unsupported)) unsupportedGroup.values.forEach { log.warning() { "unsupported: $it" } } + if (!isSuccessful()) { + IppRegistrationsSection2.validate(request) + if (throwWhenNotSuccessful) + throw if (status == ClientErrorNotFound) ClientErrorNotFoundException(request, response) + else IppExchangeException(request, response) + } } return response } @@ -122,49 +106,65 @@ open class IppClient( URI.create("$scheme://$host:$port$rawPath") } - fun httpPostRequest(httpUri: URI, request: IppRequest) = httpClient.post( - httpUri, APPLICATION_IPP, - { httpPostStream -> request.write(httpPostStream) }, - chunked = request.hasDocument() - ).apply { - var exceptionMessage: String? = null - if (contentType == null) { - log.fine { "missing content-type in http response (should be '$APPLICATION_IPP')" } - } else { - if (!contentType.startsWith(APPLICATION_IPP)) { - exceptionMessage = "invalid content-type: $contentType (expecting '$APPLICATION_IPP')" + open fun postRequest(httpUri: URI, request: IppRequest): IppResponse { + with(httpUri.toURL().openConnection() as HttpURLConnection) { + if (this is HttpsURLConnection && config.sslContext != null) { + sslSocketFactory = config.sslContext!!.socketFactory + if (!config.verifySSLHostname) hostnameVerifier = HostnameVerifier { _, _ -> true } + } + config.run { + connectTimeout = timeout.toMillis().toInt() + readTimeout = timeout.toMillis().toInt() + userAgent?.let { setRequestProperty("User-Agent", it) } + if (password != null) setRequestProperty("Authorization", authorization()) + } + doOutput = true // POST + setRequestProperty("Content-Type", APPLICATION_IPP) + setRequestProperty("Accept", APPLICATION_IPP) + setRequestProperty("Accept-Encoding", "identity") // avoid 'gzip' with Androids OkHttp + if (request.hasDocument()) setChunkedStreamingMode(0) // send document in chunks + request.write(outputStream) + val responseContentStream = try { + inputStream + } catch (throwable: Throwable) { + errorStream } - } - if (status != 200) exceptionMessage = "http request to $httpUri failed: status=$status" - if (status == 401) exceptionMessage = with(request) { - "user '$requestingUserName' is unauthorized for operation '$operation' (status=$status)" - } - exceptionMessage?.run { - config.log(log, WARNING) - request.log(log, WARNING, prefix = "IPP REQUEST: ") - log.warning { "http response status: $status" } - server?.let { log.warning { "ipp-server: $it" } } - contentType?.let { log.warning { "content-type: $it" } } - contentStream?.let { log.warning { "content:\n" + it.bufferedReader().use { it.readText() } } } - throw IppExchangeException(request, null, status, message = exceptionMessage) - } - } - fun decodeIppResponse(request: IppRequest, httpResponse: Http.Response) = IppResponse().apply { - try { - read(httpResponse.contentStream!!) - } catch (exception: Exception) { - throw IppExchangeException( - request, this, httpResponse.status, "failed to decode ipp response", exception - ).apply { - saveMessages("decoding_ipp_response_${request.requestId}_failed") + // error handling + when { + responseCode == 401 -> with(request) { + "User '$requestingUserName' is unauthorized for operation '$operation'" + } + responseCode != 200 -> { + "HTTP request to $httpUri failed: $responseCode, $responseMessage" + } + contentType != null && !contentType.startsWith(APPLICATION_IPP) -> { + "Invalid Content-Type: $contentType" + } + else -> null + }?.let { + throw IppExchangeException( + request, + response = null, + responseCode, + httpHeaderFields = headerFields, + httpStream = responseContentStream, + message = it + ) + } + + // decode ipp message + return IppResponse().apply { + try { + read(responseContentStream) + } catch (throwable: Throwable) { + throw IppExchangeException( + request, this, responseCode, message = "failed to decode ipp response", cause = throwable + ).apply { + saveMessages("decoding_ipp_response_${request.requestId}_failed") + } + } } - } - if (status == ClientErrorBadRequest) request.log(log, FINE, prefix="BAD-REQUEST: ") - if (!status.isSuccessful()) log.fine { "status: $status" } - if (hasStatusMessage()) log.fine { "status-message: $statusMessage" } - if (containsGroup(Unsupported)) unsupportedGroup.values.forEach { - log.warning { "unsupported: $it" } } } -} +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index de856b0d..4e0ee798 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -5,20 +5,47 @@ package de.gmuth.ipp.client */ import java.nio.charset.Charset +import java.time.Duration +import java.util.Base64.getEncoder import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger +import javax.net.ssl.SSLContext class IppConfig( + + // core IPP config options var userName: String? = System.getProperty("user.name"), var ippVersion: String = "1.1", var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", + + // HTTP config options + var timeout: Duration = Duration.ofSeconds(30), + var userAgent: String? = "ipp-client/3.0", + var password: String? = null, + var sslContext: SSLContext? = null, + // trust any certificate: sslContextForAnyCertificate() + // use individual certificate: sslContext(loadCertificate(FileInputStream("printer.pem"))) + // use truststore: sslContext(loadKeyStore(FileInputStream("printer.jks"), "changeit")) + var verifySSLHostname: Boolean = true + ) { + fun authorization() = + "Basic " + getEncoder().encodeToString("$userName:$password".toByteArray(Charsets.UTF_8)) + + fun trustAnyCertificateAndSSLHostname() { + sslContext = SSLHelper.sslContextForAnyCertificate() + verifySSLHostname = false + } + fun log(logger: Logger, level: Level = INFO) = logger.run { log(level) { "userName: $userName" } log(level) { "ippVersion: $ippVersion" } log(level) { "charset: ${charset.name().lowercase()}" } log(level) { "naturalLanguage: $naturalLanguage" } + log(level) { "timeout: $timeout" } + log(level) { "userAgent: $userAgent" } + log(level) { "verifySSLHostname: $verifySSLHostname" } } } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt index 5b2ecd8b..ccfa6f05 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt @@ -10,6 +10,7 @@ import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import java.io.File +import java.io.InputStream import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger @@ -21,9 +22,10 @@ open class IppExchangeException( val request: IppRequest, val response: IppResponse? = null, val httpStatus: Int? = null, + val httpHeaderFields: Map>? = null, + val httpStream: InputStream? = null, message: String = defaultMessage(request, response), - cause: Exception? = null - + cause: Throwable? = null, ) : IppException(message, cause) { class ClientErrorNotFoundException(request: IppRequest, response: IppResponse) : @@ -37,13 +39,14 @@ open class IppExchangeException( val log = getLogger(javaClass.name) companion object { - fun defaultMessage(request: IppRequest, response: IppResponse?) = StringBuilder().apply { + fun defaultMessage(request: IppRequest, response: IppResponse?) = StringBuilder().run { append("${request.operation} failed") response?.run { append(": '$status'") if (hasStatusMessage()) append(", $statusMessage") } - }.toString() + toString() + } } init { @@ -53,9 +56,12 @@ open class IppExchangeException( fun statusIs(status: IppStatus) = response?.status == status fun log(logger: Logger, level: Level = INFO) = logger.run { - if (httpStatus != null) log(level) { "HTTP-STATUS: $httpStatus" } - request.log(log, level, prefix = " REQUEST: ") + log(level) { message } + request.log(log, level, prefix = "REQUEST: ") response?.log(log, level, prefix = "RESPONSE: ") + httpStatus?.let { log(level) { "HTTP-Status: $it" } } + httpHeaderFields?.let { for ((key: String?, value) in it) log(level) { "HTTP: $key = $value" } } + httpStream?.let { log.log(level) { "HTTP-Content:\n" + it.bufferedReader().use { it.readText() } } } } fun saveMessages( diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 931296a8..95704575 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -4,7 +4,6 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.http.Http import de.gmuth.ipp.client.IppPrinterState.* import de.gmuth.ipp.core.* import de.gmuth.ipp.core.IppOperation.* @@ -18,19 +17,19 @@ import java.util.logging.Level import java.util.logging.Level.* import java.util.logging.Logger import java.util.logging.Logger.getLogger +import kotlin.io.path.createTempDirectory @SuppressWarnings("kotlin:S1192") -class IppPrinter( +open class IppPrinter( val printerUri: URI, var attributes: IppAttributesGroup = IppAttributesGroup(Printer), - httpConfig: Http.Config = Http.Config(), ippConfig: IppConfig = IppConfig(), - val ippClient: IppClient = IppClient(ippConfig, httpConfig), + val ippClient: IppClient = IppClient(ippConfig), getPrinterAttributesOnInit: Boolean = true, requestedAttributesOnInit: List? = null ) { - var workDirectory: File = File("work") val log = getLogger(javaClass.name) + var workDirectory: File = createTempDirectory().toFile() companion object { @@ -41,9 +40,12 @@ class IppPrinter( val printerClassAttributes = listOf( "printer-name", "printer-make-and-model", + "printer-info", + "printer-location", "printer-is-accepting-jobs", "printer-state", "printer-state-reasons", + "printer-state-message", "document-format-supported", "operations-supported", "color-supported", @@ -57,7 +59,7 @@ class IppPrinter( init { log.fine { "create IppPrinter for $printerUri" } - if (printerUri.scheme == "ipps") httpConfig.trustAnyCertificateAndSSLHostname() + if (printerUri.scheme == "ipps") ippConfig.trustAnyCertificateAndSSLHostname() if (!getPrinterAttributesOnInit) { log.fine { "getPrinterAttributesOnInit disabled => no printer attributes available" } } else if (attributes.isEmpty()) { @@ -77,11 +79,6 @@ class IppPrinter( if (it.containsGroup(Printer)) log.info { "${it.printerGroup.size} attributes parsed" } else log.warning { it.toString() } } - try { - fetchRawPrinterAttributes("getPrinterAttributesFailed.bin") - } catch (exception: Exception) { - log.log(SEVERE, exception, { "failed to fetch raw printer attributes" }) - } } throw ippExchangeException } @@ -591,17 +588,6 @@ class IppPrinter( } } - fun fetchRawPrinterAttributes(filename: String = "printer-attributes.bin") { - ippClient.run { - val httpResponse = httpPostRequest(toHttpUri(printerUri), ippRequest(GetPrinterAttributes)) - log.info { "http status: ${httpResponse.status}, content-type: ${httpResponse.contentType}" } - File(filename).apply { - httpResponse.contentStream!!.copyTo(outputStream()) - log.info { "saved ${length()} bytes: $path" } - } - } - } - fun printerDirectory(printerName: String = name.text.replace("\\s+".toRegex(), "_")) = File(workDirectory, printerName).apply { if (!mkdirs() && !isDirectory) throw IOException("failed to create printer directory: $path") diff --git a/src/main/kotlin/de/gmuth/http/SSLHelper.kt b/src/main/kotlin/de/gmuth/ipp/client/SSLHelper.kt similarity index 98% rename from src/main/kotlin/de/gmuth/http/SSLHelper.kt rename to src/main/kotlin/de/gmuth/ipp/client/SSLHelper.kt index 11d86379..df816dfe 100644 --- a/src/main/kotlin/de/gmuth/http/SSLHelper.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/SSLHelper.kt @@ -1,4 +1,4 @@ -package de.gmuth.http +package de.gmuth.ipp.client /** * Copyright (c) 2020-2023 Gerhard Muth diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppException.kt b/src/main/kotlin/de/gmuth/ipp/core/IppException.kt index 38fc80b8..78c3377f 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppException.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppException.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020-2022 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ open class IppException(message: String, cause: Throwable? = null) : RuntimeException(message, cause) \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt index 174b7f52..645286b2 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020-2021 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppTag.Printer @@ -24,7 +24,7 @@ class IppResponse : IppMessage { get() = operationGroup.getValue("status-message") fun hasStatusMessage() = - operationGroup.containsKey("status-message") + operationGroup.containsKey("status-message") val printerGroup: IppAttributesGroup get() = getSingleAttributesGroup(Printer) @@ -37,11 +37,11 @@ class IppResponse : IppMessage { constructor() : super() constructor( - status: IppStatus, - version: String = "1.1", - requestId: Int = 1, - charset: Charset = Charsets.UTF_8, - naturalLanguage: String = "en" + status: IppStatus, + version: String = "1.1", + requestId: Int = 1, + charset: Charset = Charsets.UTF_8, + naturalLanguage: String = "en" ) : super(version, requestId, charset, naturalLanguage) { code = status.code } diff --git a/src/test/kotlin/de/gmuth/http/HttpClientMock.kt b/src/test/kotlin/de/gmuth/http/HttpClientMock.kt deleted file mode 100644 index c5e29c27..00000000 --- a/src/test/kotlin/de/gmuth/http/HttpClientMock.kt +++ /dev/null @@ -1,39 +0,0 @@ -package de.gmuth.http - -/** - * Copyright (c) 2021 Gerhard Muth - */ - -import de.gmuth.io.ByteArray -import de.gmuth.ipp.core.IppResponse -import de.gmuth.ipp.core.IppStatus -import java.io.* -import java.net.URI -import java.util.logging.Logger.getLogger - -class HttpClientMock(config: Http.Config = Http.Config()) : Http.Client(config) { - - val log = getLogger(javaClass.name) - lateinit var rawIppRequest: ByteArray - var httpStatus: Int = 200 - var httpServer: String? = "HttpClientMock" - var httpContentType: String? = "application/ipp" - var ippResponse: IppResponse? = IppResponse(IppStatus.SuccessfulOk) - var httpContentFile: File? = null - - fun mockResponse(file: File) { - httpContentFile = file - ippResponse = null - } - - fun mockResponse(fileName: String, directory: String = "printers") = - mockResponse(File(directory, fileName)) - - override fun post(uri: URI, contentType: String, writeContent: (OutputStream) -> Unit, chunked: Boolean) = - Http.Response( - httpStatus, httpServer, httpContentType, ippResponse?.encodeAsInputStream() ?: httpContentFile?.inputStream() - ).apply { - rawIppRequest = ByteArray(writeContent) - log.info { "post ${rawIppRequest.size} bytes ipp request to $uri -> response '$server', $status, ${this.contentType}" } - } -} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt index 614e8f1a..8e397e36 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt @@ -1,46 +1,49 @@ package de.gmuth.ipp.client /** - * Copyright (c) 2020-2021 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.http.HttpClientMock import de.gmuth.ipp.core.IppException -import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound -import de.gmuth.ipp.core.IppStatus.SuccessfulOk import org.junit.Test import java.net.URI import java.util.logging.Logger.getLogger +import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertTrue class CupsClientTests { val log = getLogger(javaClass.name) - val httpClient = HttpClientMock() - val cupsClient = CupsClient(URI.create("ipps://cups"), httpClient = httpClient) - - init { - httpClient.ippResponse = IppResponse(SuccessfulOk) // default mocked response - } + val ippClientMock = IppClientMock("printers/CUPS") + val cupsClient = CupsClient(URI.create("ipps://cups"), ippClient = ippClientMock) @Test fun constructors() { CupsClient() CupsClient("host") - CupsClient(ippConfig = IppConfig()) } @Test fun getPrinters() { - httpClient.mockResponse("CUPS/Cups-Get-Printers.ipp") - cupsClient.getPrinters().forEach { log.info { it.toString() } } + ippClientMock.mockResponse("Cups-Get-Printers.ipp") + cupsClient.getPrinters().run { + forEach { log.info { it.toString() } } + assertEquals(12, size) + } } @Test fun getPrinter() { - httpClient.mockResponse("CUPS_HP_LaserJet_100_color_MFP_M175/Get-Printer-Attributes.ipp") - cupsClient.getPrinter("ColorJet_HP") + ippClientMock.mockResponse("Get-Printer-Attributes.ipp", "printers/CUPS_HP_LaserJet_100_color_MFP_M175") + cupsClient.getPrinter("ColorJet_HP").run { + log(log) + assertEquals("HP LaserJet 100 color MFP M175", makeAndModel.text) + assertEquals(IppPrinterState.Idle, state) + assertEquals(5, markers.size) + assertTrue(isAcceptingJobs) + assertTrue(isCups()) + } } @Test @@ -52,14 +55,16 @@ class CupsClientTests { @Test fun getDefault() { - httpClient.mockResponse("CUPS/Cups-Get-Default.ipp") - cupsClient.getDefault() + ippClientMock.mockResponse("Cups-Get-Default.ipp") + cupsClient.getDefault().run { + assertEquals("ColorJet_HP", name.text) + } } @Test fun getDefaultFails() { val exception = assertFailsWith { - httpClient.mockResponse("CUPS/Cups-Get-Default-Error.ipp") + ippClientMock.mockResponse("Cups-Get-Default-Error.ipp") cupsClient.getDefault() } assertTrue(exception.statusIs(ClientErrorNotFound)) diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt b/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt new file mode 100644 index 00000000..15ac8b68 --- /dev/null +++ b/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt @@ -0,0 +1,51 @@ +package de.gmuth.ipp.client + +/** + * Copyright (c) 2023 Gerhard Muth + */ + +import de.gmuth.io.ByteArray +import de.gmuth.ipp.core.IppRequest +import de.gmuth.ipp.core.IppResponse +import de.gmuth.ipp.core.IppStatus +import de.gmuth.log.Logging +import java.io.File +import java.net.URI + +class IppClientMock( + var directory: String = "printers" +) : IppClient() { + + init { + Logging.configure() + mockResponse(IppResponse(IppStatus.SuccessfulOk)) + } + + lateinit var rawResponse: ByteArray + + fun mockResponse(response: IppResponse) { + rawResponse = response.encode() + } + + fun mockResponse(file: File) { + rawResponse = file.readBytes() + } + + fun mockResponse(fileName: String, directory: String = this.directory) { + mockResponse(File(directory, fileName)) + } + + // when used with real http, responses are frequently created and garbage collected + // however references to attribute groups are kept in IPP objects + // changes to an attribute group would affect other tests as well + // therefor it's important to produce a fresh response for each call + + override fun postRequest(httpUri: URI, request: IppRequest): IppResponse { + ByteArray { request.write(it) }.run { + log.info { "mocked post $size IPP bytes to $httpUri" } + } + return IppResponse().apply { + decode(rawResponse) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt index e603d9c7..ce3f2edb 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt @@ -1,26 +1,18 @@ package de.gmuth.ipp.client -import de.gmuth.http.HttpClientMock import de.gmuth.ipp.core.IppOperation.GetPrinterAttributes -import de.gmuth.ipp.core.IppResponse -import de.gmuth.ipp.core.IppStatus.SuccessfulOk import org.junit.Test import java.net.URI import kotlin.test.assertEquals class IppClientTests { - val httpClient = HttpClientMock() - val ippClient = IppClient(httpClient = httpClient) - - init { - httpClient.ippResponse = IppResponse(SuccessfulOk) - } + val ippClient = IppClientMock() @Test fun sendRequestToURIWithEncodedWhitespaces() { - val request = ippClient.ippRequest(GetPrinterAttributes, URI.create("ipp://0/PDF%20Printer")) - ippClient.exchange(request) - assertEquals("/PDF%20Printer", request.printerUri.rawPath) - assertEquals("/PDF Printer", request.printerUri.path) + ippClient.ippRequest(GetPrinterAttributes, URI.create("ipp://0/PDF%20Printer")).run { + assertEquals("/PDF%20Printer", printerUri.rawPath) + assertEquals("/PDF Printer", printerUri.path) + } } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 4f05c915..38ad3029 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -4,7 +4,6 @@ package de.gmuth.ipp.client * Copyright (c) 2021-2023 Gerhard Muth */ -import de.gmuth.http.HttpClientMock import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus.SuccessfulOk import de.gmuth.ipp.core.IppTag @@ -15,7 +14,6 @@ import java.io.File import java.io.FileInputStream import java.net.URI import java.util.logging.Logger.getLogger -import kotlin.io.path.createTempDirectory import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -25,30 +23,20 @@ class IppJobTests { val log = getLogger(javaClass.name) val blankPdf = File("tool/A4-blank.pdf") - val httpClient = HttpClientMock() - val ippConfig = IppConfig() - val printer: IppPrinter - val job: IppJob - - init { - // mock ipp printer - printer = IppPrinter( - URI.create("ipp://printer"), - ippClient = IppClient(ippConfig, httpClient = httpClient), - getPrinterAttributesOnInit = false - ).apply { - attributes = ippResponse("Get-Printer-Attributes.ipp").printerGroup - workDirectory = createTempDirectory().toFile() + val ippClientMock = IppClientMock("printers/CUPS_HP_LaserJet_100_color_MFP_M175") + + val printer = IppPrinter( + URI.create("ipp://printer-for-job-tests"), + ippClient = ippClientMock.apply { mockResponse("Get-Printer-Attributes.ipp") } + ) + + val job: IppJob = printer.run { + ippClientMock.mockResponse("Get-Job-Attributes.ipp") + getJob(2366).apply { + attributes.attribute("document-name-supplied", NameWithLanguage, "blank.pdf".toIppString()) } - // mock ipp job - job = IppJob(printer, ippResponse("Get-Job-Attributes.ipp").jobGroup.apply { - attribute("document-name-supplied", NameWithLanguage, "blank.pdf".toIppString()) - }) } - fun ippResponse(fileName: String, directory: String = "printers/CUPS_HP_LaserJet_100_color_MFP_M175") = - IppResponse().apply { read(File(directory, fileName)) } - @Test fun jobAttributes() { job.apply { @@ -69,13 +57,11 @@ class IppJobTests { @Test fun getAttributes() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.getJobAttributes() } @Test fun updateAttributes() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.apply { updateAttributes() log(log) @@ -85,31 +71,26 @@ class IppJobTests { @Test fun hold() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.hold() } @Test fun release() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.release() } @Test fun restart() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.restart() } @Test fun cancel() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.cancel() } @Test fun cancelWithMessage() { - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") job.cancel("message") } @@ -137,20 +118,19 @@ class IppJobTests { assertFalse(isProcessingToStopPoint()) attributes.attribute("job-state-reasons", Keyword, "processing-to-stop-point") assertTrue(isProcessingToStopPoint()) - httpClient.ippResponse = ippResponse("Get-Job-Attributes.ipp") cancel() } } @Test fun sendDocument() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") + ippClientMock.mockResponse("Print-Job.ipp") job.sendDocument(FileInputStream(blankPdf)) } @Test fun sendUri() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") + ippClientMock.mockResponse("Print-Job.ipp") job.sendUri(URI.create("ftp://no.doc")) } @@ -187,7 +167,7 @@ class IppJobTests { @Test fun cupsGetDocument1() { - httpClient.ippResponse = cupsDocumentResponse("application/pdf") + ippClientMock.mockResponse(cupsDocumentResponse("application/pdf")) job.cupsGetDocument().apply { log.info { toString() } log(log) @@ -197,16 +177,16 @@ class IppJobTests { @Test fun cupsGetDocument2() { - httpClient.ippResponse = cupsDocumentResponse("application/postscript") + ippClientMock.mockResponse(cupsDocumentResponse("application/postscript")) job.cupsGetDocument().filename() } @Test fun cupsGetDocument3() { printer.attributes.remove("cups-version") - httpClient.ippResponse = cupsDocumentResponse("application/octetstream").apply { + ippClientMock.mockResponse(cupsDocumentResponse("application/octetstream").apply { jobGroup.remove("document-name") - } + }) job.cupsGetDocument(2).apply { log.info { toString() } log.info { "${filename()} (${readBytes().size} bytes)" } @@ -217,7 +197,7 @@ class IppJobTests { @Test fun cupsGetAndSaveDocuments() { - httpClient.ippResponse = cupsDocumentResponse("application/postscript") + ippClientMock.mockResponse(cupsDocumentResponse("application/postscript")) job.cupsGetDocuments(save = true) } diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt index a435cad0..b2f056dc 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt @@ -1,11 +1,9 @@ package de.gmuth.ipp.client /** - * Copyright (c) 2021-2022 Gerhard Muth + * Copyright (c) 2021-2023 Gerhard Muth */ -import de.gmuth.http.HttpClientMock -import de.gmuth.io.toIppResponse import de.gmuth.ipp.client.CupsPrinterType.Capability.CanPunchOutput import de.gmuth.ipp.client.IppFinishing.Punch import de.gmuth.ipp.client.IppFinishing.Staple @@ -35,18 +33,11 @@ class IppPrinterTests { val tlog = getLogger(javaClass.name) val blankPdf = File("tool/A4-blank.pdf") - val httpClient = HttpClientMock() - val ippConfig = IppConfig() - + val ippClientMock = IppClientMock("printers/Simulated_Laser_Printer") val printer = IppPrinter( - URI.create("ipp://printer"), - ippClient = IppClient(ippConfig, httpClient = httpClient), - getPrinterAttributesOnInit = false - ).apply { - attributes = File("printers/Simulated_Laser_Printer/Get-Printer-Attributes.ipp") - .toIppResponse() - .printerGroup - } + URI.create("ipp://printer-for-printer-tests"), + ippClient = ippClientMock.apply { mockResponse("Get-Printer-Attributes.ipp") } + ) @Test fun printerAttributes() { @@ -93,14 +84,14 @@ class IppPrinterTests { @Test fun savePrinterAttributes() { - httpClient.mockResponse("Simulated_Laser_Printer/Get-Printer-Attributes.ipp") + ippClientMock.mockResponse("Get-Printer-Attributes.ipp") printer.savePrinterAttributes(createTempDirectory().pathString) } @Test fun updateAttributes() { - httpClient.mockResponse("Simulated_Laser_Printer/Get-Printer-Attributes.ipp") - printer.apply { + ippClientMock.mockResponse("Get-Printer-Attributes.ipp") + printer.run { updateAttributes() log(tlog) assertEquals(122, attributes.size) @@ -117,7 +108,7 @@ class IppPrinterTests { @Test fun printJobFile() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") + ippClientMock.mockResponse("Print-Job.ipp") printer.printJob( File("tool/A4-blank.pdf"), jobName("A4.pdf"), @@ -149,53 +140,57 @@ class IppPrinterTests { @Test fun printJobInputStream() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") - printer.printJob(FileInputStream(blankPdf)) + ippClientMock.mockResponse("Print-Job.ipp") + printer.printJob(FileInputStream(blankPdf)).run { + log(tlog) + assertEquals(461881017, id) + assertEquals(IppJobState.Pending, state) + assertEquals(listOf("none"), stateReasons) + assertEquals("ipp://SpaceBook-2.local.:8632/jobs/461881017", uri.toString()) + } } @Test fun printJobByteArray() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") + ippClientMock.mockResponse("Print-Job.ipp") printer.printJob(blankPdf.readBytes()) } @Test fun printUri() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") + ippClientMock.mockResponse("Print-Job.ipp") printer.printUri(URI.create("http://server/document.pdf")) } @Test fun createJob() { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") - printer.createJob().apply { - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") + ippClientMock.mockResponse("Print-Job.ipp") + printer.createJob().run { sendDocument(FileInputStream(blankPdf), true, "blank.pdf", "en") - httpClient.mockResponse("Simulated_Laser_Printer/Print-Job.ipp") sendUri(URI.create("http://server/document.pdf"), true, "black.pdf", "en") } } @Test fun getJob() { - httpClient.mockResponse("Simulated_Laser_Printer/Get-Job-Attributes.ipp") - printer.getJob(11).apply { + ippClientMock.mockResponse("Get-Job-Attributes.ipp") + printer.getJob(11).run { assertEquals(21, attributes.size) } } @Test fun getJobsWithDefaultParameters() { - httpClient.mockResponse("Simulated_Laser_Printer/Get-Jobs.ipp") - printer.getJobs().apply { + ippClientMock.mockResponse("Get-Jobs.ipp") + printer.getJobs().run { assertEquals(1, size) } } @Test fun getJobsWithParameters() { - httpClient.mockResponse("Simulated_Laser_Printer/Get-Jobs.ipp") - printer.getJobs(Completed, myJobs = true, limit = 10).apply { + ippClientMock.mockResponse("Get-Jobs.ipp") + printer.getJobs(Completed, myJobs = true, limit = 10).run { assertEquals(1, size) } } diff --git a/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt b/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt index 47cbe8fb..bf2001f3 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/issueNo11.kt @@ -1,6 +1,5 @@ package de.gmuth.ipp.client -import de.gmuth.http.Http import java.net.URI import java.util.logging.Level.SEVERE import java.util.logging.Logger.getLogger @@ -20,7 +19,7 @@ fun main() { with( IppPrinter( printerUri, - httpConfig = Http.Config(debugLogging = true), + //httpConfig = HttpClient.Config(debugLogging = true), //getPrinterAttributesOnInit = false ) ) { From b1fdce269995ca3ac70ded72fb9a65a53d32dfa5 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 16 Sep 2023 21:36:37 +0200 Subject: [PATCH 16/47] fix sonar issues --- .../kotlin/de/gmuth/ipp/client/IppClient.kt | 151 ++++++++++-------- 1 file changed, 82 insertions(+), 69 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt index db66adbf..414f6088 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt @@ -14,6 +14,7 @@ import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import de.gmuth.ipp.core.IppTag.Unsupported import de.gmuth.ipp.iana.IppRegistrationsSection2 import java.io.File +import java.io.InputStream import java.net.HttpURLConnection import java.net.URI import java.util.concurrent.atomic.AtomicInteger @@ -22,14 +23,10 @@ import java.util.logging.Logger.getLogger import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection -typealias IppResponseInterceptor = (request: IppRequest, response: IppResponse) -> Unit - open class IppClient(val config: IppConfig = IppConfig()) { val log = getLogger(javaClass.name) var saveMessages: Boolean = false var saveMessagesDirectory = File("ipp-messages") - var responseInterceptor: IppResponseInterceptor? = null - //var server: String? = null fun basicAuth(user: String, password: String) { config.userName = user @@ -68,25 +65,48 @@ open class IppClient(val config: IppConfig = IppConfig()) { // exchange IppRequest for IppResponse //------------------------------------ - open fun exchange(request: IppRequest, throwWhenNotSuccessful: Boolean = true): IppResponse { + fun exchange(request: IppRequest): IppResponse { val ippUri: URI = request.printerUri log.finer { "send '${request.operation}' request to $ippUri" } - val response = postRequest(toHttpUri(ippUri), request) log.fine { "$ippUri: $request => $response" } + if (saveMessages) saveMessages(ippUri, request, response) + validateResponse(request, response, throwWhenNotSuccessful = true) + return response + } - if (saveMessages) { - val messageSubDirectory = File(saveMessagesDirectory, ippUri.host).apply { - if (!mkdirs() && !isDirectory) throw IppException("failed to create directory: $path") + //---------------------------------------------- + // http post IPP request and decode IPP response + //---------------------------------------------- + open fun postRequest(httpUri: URI, request: IppRequest): IppResponse { + with(httpUri.toURL().openConnection() as HttpURLConnection) { + if (this is HttpsURLConnection && config.sslContext != null) { + sslSocketFactory = config.sslContext!!.socketFactory + if (!config.verifySSLHostname) hostnameVerifier = HostnameVerifier { _, _ -> true } } + configure(chunked = request.hasDocument()) + request.write(outputStream) + val responseContentStream = try { + inputStream + } catch (throwable: Throwable) { + errorStream + } + validateResponse(request, responseContentStream) + return decodeContentStream(request, responseCode, responseContentStream) + } + } - fun file(suffix: String) = File(messageSubDirectory, "${request.requestId}-${request.operation}.$suffix") - request.saveRawBytes(file("request")) - response.saveRawBytes(file("response")) + private fun saveMessages(ippUri: URI, request: IppRequest, response: IppResponse) { + val messageSubDirectory = File(saveMessagesDirectory, ippUri.host).apply { + if (!mkdirs() && !isDirectory) throw IppException("failed to create directory: $path") } - responseInterceptor?.invoke(request, response) + fun file(suffix: String) = File(messageSubDirectory, "${request.requestId}-${request.operation}.$suffix") + request.saveRawBytes(file("request")) + response.saveRawBytes(file("response")) + } + private fun validateResponse(request: IppRequest, response: IppResponse, throwWhenNotSuccessful: Boolean = true) { with(response) { if (status == ClientErrorBadRequest) request.log(log, SEVERE, prefix = "BAD-REQUEST: ") if (containsGroup(Unsupported)) unsupportedGroup.values.forEach { log.warning() { "unsupported: $it" } } @@ -97,74 +117,67 @@ open class IppClient(val config: IppConfig = IppConfig()) { else IppExchangeException(request, response) } } - return response } - fun toHttpUri(ippUri: URI): URI = with(ippUri) { + private fun toHttpUri(ippUri: URI): URI = with(ippUri) { val scheme = scheme.replace("ipp", "http") val port = if (port == -1) 631 else port URI.create("$scheme://$host:$port$rawPath") } - open fun postRequest(httpUri: URI, request: IppRequest): IppResponse { - with(httpUri.toURL().openConnection() as HttpURLConnection) { - if (this is HttpsURLConnection && config.sslContext != null) { - sslSocketFactory = config.sslContext!!.socketFactory - if (!config.verifySSLHostname) hostnameVerifier = HostnameVerifier { _, _ -> true } - } - config.run { - connectTimeout = timeout.toMillis().toInt() - readTimeout = timeout.toMillis().toInt() - userAgent?.let { setRequestProperty("User-Agent", it) } - if (password != null) setRequestProperty("Authorization", authorization()) + private fun HttpURLConnection.configure(chunked: Boolean) { + config.run { + connectTimeout = timeout.toMillis().toInt() + readTimeout = timeout.toMillis().toInt() + userAgent?.let { setRequestProperty("User-Agent", it) } + if (password != null) setRequestProperty("Authorization", authorization()) + } + doOutput = true // POST + if (chunked) setChunkedStreamingMode(0) + setRequestProperty("Content-Type", APPLICATION_IPP) + setRequestProperty("Accept", APPLICATION_IPP) + setRequestProperty("Accept-Encoding", "identity") // avoid 'gzip' with Androids OkHttp + } + + private fun HttpURLConnection.validateResponse(request: IppRequest, contentStream: InputStream) = + when { + responseCode == 401 -> with(request) { + "User '$requestingUserName' is unauthorized for operation '$operation'" } - doOutput = true // POST - setRequestProperty("Content-Type", APPLICATION_IPP) - setRequestProperty("Accept", APPLICATION_IPP) - setRequestProperty("Accept-Encoding", "identity") // avoid 'gzip' with Androids OkHttp - if (request.hasDocument()) setChunkedStreamingMode(0) // send document in chunks - request.write(outputStream) - val responseContentStream = try { - inputStream - } catch (throwable: Throwable) { - errorStream + responseCode != 200 -> { + "HTTP request failed: $responseCode, $responseMessage" } - - // error handling - when { - responseCode == 401 -> with(request) { - "User '$requestingUserName' is unauthorized for operation '$operation'" - } - responseCode != 200 -> { - "HTTP request to $httpUri failed: $responseCode, $responseMessage" - } - contentType != null && !contentType.startsWith(APPLICATION_IPP) -> { - "Invalid Content-Type: $contentType" - } - else -> null - }?.let { - throw IppExchangeException( - request, - response = null, - responseCode, - httpHeaderFields = headerFields, - httpStream = responseContentStream, - message = it - ) + contentType != null && !contentType.startsWith(APPLICATION_IPP) -> { + "Invalid Content-Type: $contentType" } + else -> null + }?.let { + throw IppExchangeException( + request, + response = null, + responseCode, // HTTP + httpHeaderFields = headerFields, + httpStream = contentStream, + message = it + ) + } - // decode ipp message - return IppResponse().apply { - try { - read(responseContentStream) - } catch (throwable: Throwable) { - throw IppExchangeException( - request, this, responseCode, message = "failed to decode ipp response", cause = throwable - ).apply { - saveMessages("decoding_ipp_response_${request.requestId}_failed") - } - } + private fun decodeContentStream( + request: IppRequest, + httpStatus: Int, + contentStream: InputStream, + onExceptionSaveMessages: Boolean = true + ) = IppResponse().apply { + try { + read(contentStream) + } catch (throwable: Throwable) { + throw IppExchangeException( + request, this, httpStatus, message = "failed to decode ipp response", cause = throwable + ).apply { + if (onExceptionSaveMessages) + saveMessages("decoding_ipp_response_${request.requestId}_failed") } } } + } \ No newline at end of file From 4ab90a1ff50312e56df0dbf15cb50ceab81dd5b8 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 16 Sep 2023 21:59:22 +0200 Subject: [PATCH 17/47] updated docs about http transportation --- README.md | 81 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index d2813e7b..364325f9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ipp-client +# ipp-client A client implementation of the ipp protocol for java and kotlin. RFCs [8010](https://tools.ietf.org/html/rfc8010), @@ -15,10 +15,12 @@ RFCs [8010](https://tools.ietf.org/html/rfc8010), ## Usage You may use ```ippfind``` or other ZeroConf tools for printer discovery. -The [CupsClient](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt) supports printer lookup by queue name. +The [CupsClient](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt) +supports printer lookup by queue name. Repository [ipp-samples](https://github.com/gmuth/ipp-samples) contains examples how to use jmDNS. ### [IppPrinter](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt) and [IppJob](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt) + ```kotlin // initialize printer connection and show printer attributes val ippPrinter = IppPrinter(URI.create("ipp://colorjet.local/ipp/printer")) @@ -75,11 +77,13 @@ ippPrinter.sound() // identify printer // subscribe and log events (e.g. from CUPS) for 1 minute ippPrinter.createPrinterSubscription(60) - .processEvents() + .processEvents() ``` + ### Printer Capabilities `IppPrinter` checks, if attribute values are supported by looking into `'...-supported'` printer attributes. + ``` documentFormat("application/pdf") @@ -95,9 +99,9 @@ val file = File("A4-blank.pdf") val ippClient = IppClient() val request = IppRequest(IppOperation.PrintJob, uri).apply { - // constructor adds 'attributes-charset', 'attributes-natural-language' and 'printer-uri' - operationGroup.attribute("document-format", IppTag.MimeMediaType, "application/pdf") - documentInputStream = FileInputStream(file) + // constructor adds 'attributes-charset', 'attributes-natural-language' and 'printer-uri' + operationGroup.attribute("document-format", IppTag.MimeMediaType, "application/pdf") + documentInputStream = FileInputStream(file) } val response = ippClient.exchange(request) println(response.jobGroup["job-id"]) @@ -116,20 +120,20 @@ val cupsClient = CupsClient() cupsClient.basicAuth("admin", "secret") // list all queues -cupsClient.getPrinters().forEach { +cupsClient.getPrinters().forEach { println("${it.name} -> ${it.printerUri}") } // list all completed jobs for queue cupsClient.getPrinter("ColorJet_HP") - .getJobs(IppWhichJobs.Completed) - .forEach { println(it) } + .getJobs(IppWhichJobs.Completed) + .forEach { println(it) } // default printer val defaultPrinter = cupsClient.getDefault() // check capability -if(defaultPrinter.hasCapability(Capability.CanPrintInColor)) { +if (defaultPrinter.hasCapability(Capability.CanPrintInColor)) { println("${defaultPrinter.name} can print in color") } @@ -146,7 +150,7 @@ val width = 2540 * 2 // hundreds of mm val jpegFile = File("label.jpg") val image = javax.imageio.ImageIO.read(jpegFile) - + printer.printJob( jpegFile, documentFormat("image/jpeg"), IppMedia.Collection( @@ -158,16 +162,21 @@ printer.printJob( ## Logging -From version 3.0 onwards the library uses [Java Logging](https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html) - configure as you like. +From version 3.0 onwards the library +uses [Java Logging](https://docs.oracle.com/javase/8/docs/technotes/guides/logging/overview.html) - configure as you +like. Tests can use Logging.configure() to load logging.properties from test/resources. -The behaviour of the my previously used [ConsoleLogger](https://github.com/gmuth/logging-kotlin/blob/main/src/main/kotlin/de/gmuth/log/ConsoleLogger.kt) is now implemented by StdoutHandler and SimpleClassNameFormatter. -I moved all of my custom logging code to it's own repository [logging-kotlin](https://github.com/gmuth/logging-kotlin/tree/main/src/main/kotlin/de/gmuth/log). +The behaviour of my previously +used [ConsoleLogger](https://github.com/gmuth/logging-kotlin/blob/main/src/main/kotlin/de/gmuth/log/ConsoleLogger.kt) is +now implemented by StdoutHandler and SimpleClassNameFormatter. +I moved all of my custom logging code to it's own +repository [logging-kotlin](https://github.com/gmuth/logging-kotlin/tree/main/src/main/kotlin/de/gmuth/log). ## Build To build the jar make sure you have JDK 11 installed. -The default tasks build the jar in `build/libs`. +The default tasks build the jar in `build/libs`. ./gradlew @@ -189,29 +198,14 @@ The build produces the jar, sources and javadoc artifacts. They are available at - group: gmuth.de - artifact: ipp-client -- version: 2.4 +- version: 3.0 Add dependency: ``` - implementation("de.gmuth:ipp-client:2.4") + implementation("de.gmuth:ipp-client:3.0") ``` -## No Multiplatform support yet - -IPP is based on the exchange of binary messages via HTTP. -For reading and writing binary data -[DataInputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataInputStream.html) -and [DataOutputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataOutputStream.html) are used. - -For the transport layer I've created a -[HTTP interface](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/http/Http.kt). -By default implementation [HttpURLConnectionClient](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt) -is used which in turn uses javas [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html). - -Only java runtimes (including Android) provide implementations of these classes. -The java standard libraries also provide [support for SSL/TLS](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLContext.html). - ## Source packages Package @@ -226,8 +220,21 @@ Package [`de.gmuth.ipp.client`](https://github.com/gmuth/ipp-client-kotlin/tree/master/src/main/kotlin/de/gmuth/ipp/client) contains the [IppClient](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt) -which requires a -[Http.Client](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/http/Http.kt) -that implements HTTP: -e.g. [HttpURLConnectionClient](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/http/HttpURLConnectionClient.kt) -or [JavaHttpClient](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/http/JavaHttpClient.kt) +and implementations of higher level IPP objects like +[IppPrinter]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt)), +[IppJob]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt)), +[IppSubscription]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt)) and +[IppEventNotification]((https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt)) + +## No Multiplatform support + +IPP is based on the exchange of binary messages via HTTP. +For reading and writing binary data +[DataInputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataInputStream.html) +and [DataOutputStream](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/DataOutputStream.html) are +used. For message transportation IppClient uses [HttpURLConnection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpURLConnection.html). + +Only Java runtimes (including Android) provide implementations of these classes. +The Java standard libraries also +provide [support for SSL/TLS](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLContext.html) +. \ No newline at end of file From e1c268f761a69584625c149f3f16dd4a71cd6a50 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Sat, 16 Sep 2023 23:37:36 +0200 Subject: [PATCH 18/47] converage tests --- .../de/gmuth/ipp/core/IppInputStream.kt | 11 +++++----- .../kotlin/de/gmuth/ipp/core/IppMessage.kt | 3 --- src/main/kotlin/de/gmuth/ipp/core/IppTag.kt | 2 +- .../gmuth/ipp/core/IppAttributesGroupTests.kt | 6 +++++ .../de/gmuth/ipp/core/IppInputStreamTests.kt | 13 +++++++++++ .../de/gmuth/ipp/core/IppRequestTests.kt | 22 ++++++++++++++++++- .../de/gmuth/ipp/core/IppResponseTests.kt | 7 ++++++ 7 files changed, 53 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index f9932e2d..031ea2c5 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -55,14 +55,14 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt } } } while (tag != End) - } catch (exception: Exception) { - readBytes().apply { + } catch (throwable: Throwable) { + if (throwable !is EOFException) readBytes().apply { if (isNotEmpty()) { log.warning { "skipped $size unparsed bytes" } hexdump { log.warning { it } } } } - throw exception + throw throwable } } @@ -75,9 +75,8 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt val name = readString() val value = try { readAttributeValue(tag) - } catch (exception: Exception) { - if (exception !is EOFException) readBytes().hexdump { log.info { it } } - throw IppException("failed to read attribute value of '$name' ($tag)", exception) + } catch (throwable: Throwable) { + throw IppException("failed to read attribute value of '$name' ($tag)", throwable) } // remember attributes-charset for name and text value decoding if (name == "attributes-charset") attributesCharset = value as Charset diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index 02af9d37..4963983b 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -88,9 +88,6 @@ abstract class IppMessage() { toByteArray() } - fun encodeAsInputStream() = - ByteArrayInputStream(encode()) - // -------- // DECODING // -------- diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt index 148da212..5a44eaf5 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt @@ -4,7 +4,7 @@ package de.gmuth.ipp.core * Copyright (c) 2020 Gerhard Muth */ -// [RFC 8010] and [RFC 3380] +// [RFC 8010] and [RFC 3380] enum class IppTag( val code: Byte, val registeredName: String, diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index ca74eda1..813aa0e7 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -72,6 +72,12 @@ class IppAttributesGroupTests { assertEquals("bar", group.getValue("foo") as String) } + @Test + fun getTextValue() { + group.attribute("foo", TextWithoutLanguage, "bar".toIppString()) + assertEquals("bar", group.getTextValue("foo")) + } + @Test fun getValueOrNull() { group.attribute("foo0", Keyword, "bar0") diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt index 0a9c1314..d43334d5 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt @@ -5,6 +5,7 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppResolution.Unit.DPI +import de.gmuth.log.Logging import java.io.ByteArrayInputStream import java.net.URI import java.util.logging.Logger @@ -15,6 +16,10 @@ import kotlin.test.assertTrue class IppInputStreamTest { + init { + Logging.configure() + } + private val message = object : IppMessage() { override val codeDescription: String get() = "codeDescription" @@ -181,6 +186,14 @@ class IppInputStreamTest { } } + @Test + fun readMessageFails() { + val encoded = "01 01 00 0B 00 00 00 08 01 47 00 01 61 00 01 66 0A 0B 0C 0D" + assertFailsWith { + encoded.toIppInputStream().readMessage(message) + } + } + @Test fun readMessageReadAttributeFails() { val encoded = "00 03 66 6F 6F 00 03 62 61 72" diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt index d442597c..cbefb69f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt @@ -4,7 +4,9 @@ package de.gmuth.ipp.core * Copyright (c) 2020-2023 Gerhard Muth */ +import de.gmuth.ipp.core.IppOperation.CreateJobSubscriptions import java.net.URI +import java.time.Duration import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals @@ -65,9 +67,27 @@ class IppRequestTests { assertEquals(URI.create("ipp://printer"), getValue("printer-uri")) assertEquals(0, getValue("job-id")) assertEquals(listOf("one", "two"), getValues("requested-attributes")) - assertEquals("user".toIppString(), getValue("requesting-user-name")) + //assertEquals("user".toIppString(), getValue("requesting-user-name")) } + assertEquals("user", requestDecoded.requestingUserName) assertEquals("pdl-content", String(requestDecoded.documentInputStream!!.readBytes())) } + @Test + fun createSubscriptionAttributesGroup() { + IppRequest(CreateJobSubscriptions, URI.create("ipp://foo")) + .createSubscriptionAttributesGroup( + listOf("all"), + Duration.ofHours(1), + Duration.ofMinutes(1), + 999 + ) + } + + @Test + fun createSubscriptionAttributesGroupWithNullDefaults() { + IppRequest(CreateJobSubscriptions, URI.create("ipp://null")) + .createSubscriptionAttributesGroup() + } + } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt index 6b09b666..ea0e79e4 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt @@ -63,4 +63,11 @@ class IppResponseTests { } } + @Test + fun createReponse() { + IppResponse(IppStatus.SuccessfulOk).run { + assertTrue(isSuccessful()) + } + } + } \ No newline at end of file From 02ed09332a3aa92fc68a25dd6dc4ed60e3a5e8b1 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 18 Sep 2023 22:49:22 +0200 Subject: [PATCH 19/47] fix compile issue --- src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index 673776d4..34a7a4ed 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -259,7 +259,6 @@ open class CupsClient( // job-originating-user-name is missing when document-count or job-originating-host-name ist requested // once hidden in response, wait for one minute and user-name should show up again ) - .onEach { log.info { it } } // job overview .onEach { job -> // update attributes and lookup job owners if (updateJobAttributes) { // update could remove job-origination-user-name // important: no requested-attributes is different to group "all" From 10d31f799b524b264c67275b3a8956c30370961f Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 18 Sep 2023 23:03:05 +0200 Subject: [PATCH 20/47] no JavaClient from Java 11 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 364325f9..1b694df0 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,6 @@ This software has **no dependencies** to [CUPS](https://www.cups.org) or [ipptool](https://www.cups.org/doc/man-ipptool.html). Operation has mostly been tested for target `jvm`. Android is supported since v1.6. -A Java Version 11 Runtime is only required if you want to use the Java 11 HttpClient. ## Artifact coordinates From 32f7f76558887c9cf3a10f9d450297f8543f7fd0 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 21 Sep 2023 22:41:10 +0200 Subject: [PATCH 21/47] set language to English --- src/test/kotlin/de/gmuth/log/Logging.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/de/gmuth/log/Logging.kt b/src/test/kotlin/de/gmuth/log/Logging.kt index 1189526c..ec2479de 100644 --- a/src/test/kotlin/de/gmuth/log/Logging.kt +++ b/src/test/kotlin/de/gmuth/log/Logging.kt @@ -4,12 +4,13 @@ package de.gmuth.log * Copyright (c) 2023 Gerhard Muth */ +import java.util.* import java.util.logging.LogManager object Logging { fun configure() { - LogManager - .getLogManager() + Locale.setDefault(Locale.ENGLISH) // -Duser.language=en + LogManager.getLogManager() .readConfiguration(Logging::class.java.getResourceAsStream("/logging.properties")) } } \ No newline at end of file From 4f916f8f794391a455340d9497934168d43fe807 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 21 Sep 2023 22:42:00 +0200 Subject: [PATCH 22/47] better exception message --- src/main/kotlin/de/gmuth/ipp/client/IppJob.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index 923114ce..98edf6db 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -73,7 +73,7 @@ class IppJob( val numberOfDocuments: Int get() = attributes.getValueOrNull("number-of-documents") ?: attributes.getValueOrNull("document-count") // CUPS 1.x - ?: throw IppException("number-of-documents not supported") + ?: throw IppException("number-of-documents or document-count not found") val documentNameSupplied: IppString get() = attributes.getValue("document-name-supplied") From cc3572e6efd433286b21e60514a4aee812b80872 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 21 Sep 2023 22:47:42 +0200 Subject: [PATCH 23/47] Cups version --- .../kotlin/de/gmuth/ipp/client/CupsClient.kt | 40 +++++++++++++------ .../kotlin/de/gmuth/ipp/client/IppPrinter.kt | 4 ++ .../de/gmuth/ipp/client/CupsClientTests.kt | 7 ++++ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index 34a7a4ed..ee6f2f8c 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -5,6 +5,7 @@ package de.gmuth.ipp.client */ import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException +import de.gmuth.ipp.client.IppWhichJobs.All import de.gmuth.ipp.core.IppOperation import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppRequest @@ -67,6 +68,13 @@ open class CupsClient( cupsPrinterRequest(CupsSetDefault, printerName) ) + val version: String by lazy { + getPrinters().run { + if (isNotEmpty()) last() + else IppPrinter(getJobs(All).last().printerUri) + }.cupsVersion + } + fun cupsPrinterUri(printerName: String) = with(cupsUri) { val optionalPort = if (port > 0) ":$port" else "" URI("$scheme://$host$optionalPort/printers/$printerName") @@ -164,7 +172,11 @@ open class CupsClient( .apply { workDirectory = cupsClientWorkDirectory } } - fun getJobs(whichJobs: IppWhichJobs? = null, limit: Int? = null, requestedAttributes: List? = null) = + fun getJobs( + whichJobs: IppWhichJobs? = null, + limit: Int? = null, + requestedAttributes: List? = ippPrinter.getJobsRequestedAttributes + ) = ippPrinter.getJobs(whichJobs = whichJobs, limit = limit, requestedAttributes = requestedAttributes) //---------------------------- @@ -243,7 +255,7 @@ open class CupsClient( private val jobOwners = mutableSetOf() fun getJobsAndSaveDocuments( - whichJobs: IppWhichJobs = IppWhichJobs.All, + whichJobs: IppWhichJobs = All, updateJobAttributes: Boolean = false, commandToHandleSavedFile: String? = null ): Collection { @@ -253,7 +265,8 @@ open class CupsClient( whichJobs, requestedAttributes = listOf( "job-id", "job-uri", "job-printer-uri", "job-originating-user-name", - "job-name", "job-state", "job-state-reasons", "number-of-documents" + "job-name", "job-state", "job-state-reasons", + if(version < "1.6.0") "document-count" else "number-of-documents" ) // wired: do not modify above set // job-originating-user-name is missing when document-count or job-originating-host-name ist requested @@ -308,12 +321,12 @@ open class CupsClient( } // ------------------------------ - // get and save documents for job + // Get and save documents for job // ------------------------------ private fun getAndSaveDocuments( job: IppJob, - onSuccessUpdateJobAttributes: Boolean = true, + onSuccessUpdateJobAttributes: Boolean = false, optionalCommandToHandleFile: String? = null ): Collection { var documents: Collection = emptyList() @@ -326,15 +339,16 @@ open class CupsClient( ippExchangeException.httpStatus!! != 401 } - val configuredUserName = config.userName - if (configuredUserName != null) getDocuments() - val jobOwnersIterator = jobOwners.iterator() - while (jobOwnersIterator.hasNext()) { - config.userName = jobOwnersIterator.next() - log.fine { "set userName '${config.userName}'" } - if (getDocuments()) break + if(!getDocuments()) { + val configuredUserName = config.userName + jobOwners.forEach { + config.userName = it + log.fine { "set userName '${config.userName}'" } + if (getDocuments()) return@forEach + } + config.userName = configuredUserName } - config.userName = configuredUserName + documents.onEach { document -> document.save(job.printerDirectory(), overwrite = true) optionalCommandToHandleFile?.let { document.runCommand(it) } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 95704575..2e569012 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -217,6 +217,9 @@ open class IppPrinter( fun marker(color: CupsMarker.Color) = markers.single { it.color == color } + val cupsVersion: String + get() = attributes.getTextValue("cups-version") + //----------------- fun isIdle() = state == Idle @@ -394,6 +397,7 @@ open class IppPrinter( limit: Int? = null, requestedAttributes: List? = getJobsRequestedAttributes ): Collection { + log.fine { "getJobs(whichJobs=$whichJobs, requestedAttributes=$requestedAttributes)"} val request = ippRequest(GetJobs, requestedAttributes = requestedAttributes).apply { operationGroup.run { whichJobs?.keyword?.let { diff --git a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt index 8e397e36..04409dfe 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt @@ -43,9 +43,16 @@ class CupsClientTests { assertEquals(5, markers.size) assertTrue(isAcceptingJobs) assertTrue(isCups()) + assertEquals("2.2.5", cupsVersion) } } + @Test + fun getCupsVersion() { + ippClientMock.mockResponse("Get-Printer-Attributes.ipp", "printers/CUPS_HP_LaserJet_100_color_MFP_M175") + assertEquals("2.2.5", cupsClient.version) + } + @Test fun getPrinterFails() { assertFailsWith { // no such cups printer From e616ebf8203e4f41e8cf518dd0ec7548cb29e4e2 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 21:22:57 +0200 Subject: [PATCH 24/47] fixed bug in filename() --- src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt | 5 +++-- src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index a5f361c4..a6df950a 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -36,11 +36,12 @@ class IppDocument( } fun filenameSuffix() = when (format) { + "application/octetstream" -> "bin" "application/postscript" -> "ps" "application/pdf" -> "pdf" "image/jpeg" -> "jpg" "text/plain" -> "txt" - else -> "bin" + else -> format.split("/").get(1) } fun filename() = StringBuilder().run { @@ -54,7 +55,7 @@ class IppDocument( } job.getJobNameOrDocumentNameSuppliedOrAppleJobNameOrNull()?.let { append("-${it.take(100)}") - if (it.endsWith(".$suffix")) suffix = null + if (it.lowercase().endsWith(".$suffix")) suffix = null } } suffix?.let { append(".$it") } diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 38ad3029..988a9f10 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -172,13 +172,16 @@ class IppJobTests { log.info { toString() } log(log) save().delete() + assertEquals("job-2366-gmuth-A4-blank.pdf", filename()) } } @Test fun cupsGetDocument2() { ippClientMock.mockResponse(cupsDocumentResponse("application/postscript")) - job.cupsGetDocument().filename() + job.cupsGetDocument().run { + assertEquals("ps", filenameSuffix()) + } } @Test @@ -192,6 +195,7 @@ class IppJobTests { log.info { "${filename()} (${readBytes().size} bytes)" } job.attributes.remove("document-name-supplied") log.info { filename() } + assertEquals("bin", filenameSuffix()) } } From f274ff2a7d3e865a787258d60bac931001cced5f Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 21:23:40 +0200 Subject: [PATCH 25/47] use CUPS as root directory for saved documents --- src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index ee6f2f8c..00d64e4e 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -28,7 +28,7 @@ open class CupsClient( val log = getLogger(javaClass.name) val config: IppConfig by ippClient::config var userName: String? by config::userName - var cupsClientWorkDirectory = File("cups-${cupsUri.host}") + var cupsClientWorkDirectory = File("CUPS/${cupsUri.host}") init { if (cupsUri.scheme == "ipps") config.trustAnyCertificateAndSSLHostname() From 9385d9c1aacbdee65d74518cedddf71c49896a27 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 21:31:10 +0200 Subject: [PATCH 26/47] refactored option 'onReplaceWarn' --- src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt | 6 ++++-- src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt | 2 +- .../kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index a37e3ac1..e0276b50 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -18,7 +18,9 @@ open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap, onReplaceWarn: Boolean = false) = + var onReplaceWarn: Boolean = false + + open fun put(attribute: IppAttribute<*>) = put(attribute.name, attribute).also { if (it != null && onReplaceWarn) log.warning { "replaced '$it' with '${attribute.values.joinToString(",")}' in group $tag" } } @@ -50,7 +52,7 @@ open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap 1setOf log.fine { IppAttribute(currentAttribute.name, attribute.tag, attribute.value).toString() } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index 813aa0e7..270ec393 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -40,7 +40,8 @@ class IppAttributesGroupTests { @Test fun putWithReplacementWarning() { group.put(IppAttribute("number", Integer, 0)) - group.put(IppAttribute("number", Integer, 1, 2), true) + group.onReplaceWarn = true + group.put(IppAttribute("number", Integer, 1, 2)) assertEquals(1, group.size) assertEquals(group["number"]!!.values.size, 2) } From 403fd0f9e6a6f5b2b57157ac147df4c8212f49cd Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 21:43:34 +0200 Subject: [PATCH 27/47] fixed sonar issue --- src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index a6df950a..318c1ccf 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -41,7 +41,7 @@ class IppDocument( "application/pdf" -> "pdf" "image/jpeg" -> "jpg" "text/plain" -> "txt" - else -> format.split("/").get(1) + else -> format.split("/")[1] } fun filename() = StringBuilder().run { From a7091560eccbd49b247d493dc26c16d7c062099f Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 22:29:29 +0200 Subject: [PATCH 28/47] throw IppException on unknown tag name --- src/main/kotlin/de/gmuth/ipp/core/IppTag.kt | 10 +++++----- src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt index 5a44eaf5..4d4e0fd0 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ // [RFC 8010] and [RFC 3380] @@ -10,8 +10,7 @@ enum class IppTag( val registeredName: String, val valueHasValidClass: (Any) -> kotlin.Boolean = { true } ) { - - // delimiter tags + // Delimiter tags // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-7 Operation(0x01, "operation-attributes-tag"), Job(0x02, "job-attributes-tag"), @@ -24,7 +23,7 @@ enum class IppTag( Document(0x09, "document-attributes-tag"), System(0x0A, "system-attributes-tag"), - // out-of-band tags + // Out-of-band tags // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-8 Unsupported_(0x10, "unsupported"), Unknown(0x12, "unknown"), @@ -82,6 +81,7 @@ enum class IppTag( values().singleOrNull { it.code == code } ?: throw IppException("Unknown tag 0x%02X".format(code)) fun fromString(name: String): IppTag = - values().single { it.registeredName == name } + values().singleOrNull { it.registeredName == name } ?: throw IppException("Unknown tag name '$name'") } + } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt index 068a5824..03973a86 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt @@ -11,13 +11,13 @@ class IppTagTests { @Test fun validateTextWithLanguage() { assertFalse(IppTag.TextWithoutLanguage.valueHasValidClass(0)) - assertTrue(IppTag.TextWithoutLanguage.valueHasValidClass("ipp-string".toIppString())) + assertTrue(IppTag.TextWithoutLanguage.valueHasValidClass("string")) } @Test fun validateNameWithLanguage() { assertFalse(IppTag.NameWithoutLanguage.valueHasValidClass(0)) - assertTrue(IppTag.NameWithoutLanguage.valueHasValidClass("ipp-string".toIppString())) + assertTrue(IppTag.NameWithoutLanguage.valueHasValidClass("string")) } @Test @@ -41,7 +41,7 @@ class IppTagTests { @Test fun fromStringFails() { - assertFailsWith { IppTag.fromString("invalid-tag-name") } + assertFailsWith { IppTag.fromString("invalid-tag-name") } } @Test From efe694b68ec9be07299a735ee753e41ebd8b0a33 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 22:33:49 +0200 Subject: [PATCH 29/47] removed extension String.toIppString() -> remember: for text or name values without language you can also use String values! --- src/main/kotlin/de/gmuth/ipp/client/IppJob.kt | 2 +- .../kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt | 5 ++--- src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt | 2 +- src/main/kotlin/de/gmuth/ipp/core/IppString.kt | 7 ++----- src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt | 6 +++--- .../kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt | 4 ++-- .../kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt | 7 ++----- src/test/kotlin/de/gmuth/ipp/core/IppStringTests.kt | 9 ++++++--- 8 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index 98edf6db..5dc3c5eb 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -167,7 +167,7 @@ class IppJob( if (isCanceled()) log.warning { "job #$id is already 'canceled'" } if (isProcessingToStopPoint()) log.warning { "job #$id is already 'processing-to-stop-point'" } val request = ippRequest(CancelJob).apply { - messageForOperator?.let { operationGroup.attribute("message", TextWithoutLanguage, it.toIppString()) } + messageForOperator?.let { operationGroup.attribute("message", TextWithoutLanguage, it) } } log.info { "cancel job#$id" } return exchange(request).also { updateAttributes() } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt b/src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt index 144e7cfc..2fd338ba 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.client /** - * Copyright (c) 2020-21 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppAttribute @@ -10,7 +10,6 @@ import de.gmuth.ipp.core.IppResolution.Unit import de.gmuth.ipp.core.IppResolution.Unit.DPI import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* -import de.gmuth.ipp.core.toIppString /** * create common job attributes @@ -25,7 +24,7 @@ object IppTemplateAttributes { @JvmStatic fun jobName(name: String) = - IppAttribute("job-name", NameWithoutLanguage, name.toIppString()) + IppAttribute("job-name", NameWithoutLanguage, name) // for job group diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt index 629a8df2..31b9c216 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt @@ -44,7 +44,7 @@ class IppRequest : IppMessage { jobId?.let { attribute("job-id", Integer, it) } printerUri?.let { attribute("printer-uri", Uri, it) } requestedAttributes?.let { attribute("requested-attributes", Keyword, it) } - requestingUserName?.let { attribute("requesting-user-name", NameWithoutLanguage, it.toIppString()) } + requestingUserName?.let { attribute("requesting-user-name", NameWithoutLanguage, it) } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppString.kt b/src/main/kotlin/de/gmuth/ipp/core/IppString.kt index ee8a34b9..fef3d6d5 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppString.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppString.kt @@ -1,15 +1,12 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ // name or text value, optional language - data class IppString(val text: String, val language: String? = null) { override fun toString() = "${if (language == null) "" else "[$language] "}$text" -} - -fun String.toIppString(language: String? = null) = IppString(this, language) \ No newline at end of file +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 988a9f10..5a8adf45 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -6,9 +6,9 @@ package de.gmuth.ipp.client import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus.SuccessfulOk +import de.gmuth.ipp.core.IppString import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* -import de.gmuth.ipp.core.toIppString import org.junit.Test import java.io.File import java.io.FileInputStream @@ -33,7 +33,7 @@ class IppJobTests { val job: IppJob = printer.run { ippClientMock.mockResponse("Get-Job-Attributes.ipp") getJob(2366).apply { - attributes.attribute("document-name-supplied", NameWithLanguage, "blank.pdf".toIppString()) + attributes.attribute("document-name-supplied", NameWithoutLanguage, IppString("blank.pdf")) } } @@ -160,7 +160,7 @@ class IppJobTests { createAttributesGroup(Job).apply { attribute("document-format", MimeMediaType, format) attribute("document-number", Integer, 1) - attribute("document-name", NameWithoutLanguage, "cups-doc".toIppString()) + attribute("document-name", NameWithoutLanguage, "cups-doc") } documentInputStream = FileInputStream(blankPdf) } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index 270ec393..d744f7bf 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020-2022 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppTag.* @@ -75,7 +75,7 @@ class IppAttributesGroupTests { @Test fun getTextValue() { - group.attribute("foo", TextWithoutLanguage, "bar".toIppString()) + group.attribute("foo", TextWithoutLanguage, IppString("bar")) assertEquals("bar", group.getTextValue("foo")) } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt index c8e29e28..ad50d428 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt @@ -158,11 +158,8 @@ class IppOutputStreamTest { @Test fun writeAttributeValueNameWithLanguageFails() { - assertFailsWith { - ippOutputStream.writeAttributeValue( - IppTag.NameWithLanguage, - "text-without-language".toIppString() - ) + assertFailsWith { + ippOutputStream.writeAttributeValue(IppTag.NameWithLanguage, "text-without-language") } } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppStringTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppStringTests.kt index 75174351..6ba26ad4 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppStringTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppStringTests.kt @@ -1,11 +1,12 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull class IppStringTests { @@ -24,12 +25,14 @@ class IppStringTests { @Test fun toIppStringExtension() { - assertEquals("some-text", "some-text".toIppString().text) + assertEquals("string-without-language", withoutLanguage.text) + assertNull(withoutLanguage.language) } @Test fun toIppStringExtensionWithLanguage() { - assertEquals(withLanguage, "string-with-language".toIppString("en")) + assertEquals("string-with-language", withLanguage.text) + assertEquals("en", withLanguage.language) } } \ No newline at end of file From 251971a9bedbb0c6ab2d4f4a72c01de63a950954 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 22:40:23 +0200 Subject: [PATCH 30/47] throw on unknown status codes --- src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt | 13 +++++-------- src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt | 9 ++------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt b/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt index bcae6d81..f0122759 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ // https://www.rfc-editor.org/rfc/rfc8011.html#appendix-B @@ -60,22 +60,19 @@ enum class IppStatus(val code: Short) { ServerErrorMultipleDocumentJobsNotSupported(0x0509), ServerErrorPrinterIsDeactivated(0x050A), // https://datatracker.ietf.org/doc/html/rfc3998#page-23 ServerErrorTooManyJobs(0x050B), // https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf - ServerErrorTooManyDocuments(0x050C), - - // placeholder for all unknown status code - UnknownStatusCode(-1); + ServerErrorTooManyDocuments(0x050C); fun isSuccessful() = (0x0000..0x00FF).contains(code) fun isClientError() = (0x0400..0x04FF).contains(code) fun isServerError() = (0x0500..0x05FF).contains(code) override fun toString() = name - .replace(Regex("[A-Z]+")) { "-" + it.value.lowercase() } - .replace(Regex("^-"), "") + .replace(Regex("[A-Z]+")) { "-" + it.value.lowercase() } + .replace(Regex("^-"), "") companion object { fun fromShort(code: Short): IppStatus = - values().find { it.code == code } ?: UnknownStatusCode + values().find { it.code == code } ?: throw IppException("Unknown status code %04x".format(code)) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt index 7219e01b..6056ca7f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt @@ -31,14 +31,9 @@ class IppStatusTests { assertEquals(IppStatus.ClientErrorDocumentFormatNotSupported, IppStatus.fromShort(0x40A)) } - //@Test - fun fromShortFails() { - assertFailsWith { IppStatus.fromShort(10) } - } - @Test - fun unknownStatusCode() { - assertEquals(IppStatus.UnknownStatusCode, IppStatus.fromShort(0x333)) + fun fromShortFails() { + assertFailsWith { IppStatus.fromShort(10) } } } \ No newline at end of file From 22939073aa76576856a54173d158df86eb3483d3 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 22:54:57 +0200 Subject: [PATCH 31/47] removed hasStatusMessage() --- src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt | 2 +- src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt | 3 --- src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt index ccfa6f05..d10542c6 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt @@ -43,7 +43,7 @@ open class IppExchangeException( append("${request.operation} failed") response?.run { append(": '$status'") - if (hasStatusMessage()) append(", $statusMessage") + if (operationGroup.containsKey("status-message")) append(", $statusMessage") } toString() } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt index 645286b2..e035ec3f 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt @@ -23,9 +23,6 @@ class IppResponse : IppMessage { val statusMessage: IppString get() = operationGroup.getValue("status-message") - fun hasStatusMessage() = - operationGroup.containsKey("status-message") - val printerGroup: IppAttributesGroup get() = getSingleAttributesGroup(Printer) diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt index ea0e79e4..da47ed2f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppResponseTests.kt @@ -25,7 +25,6 @@ class IppResponseTests { assertEquals(0, jobGroup.size) assertEquals(0, unsupportedGroup.size) assertEquals("successful-ok", codeDescription) - assertTrue(hasStatusMessage()) assertEquals("not-infected", statusMessage.toString()) } } From 3582e77160d7d8685a411b00ad3d3546439b86a2 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Mon, 25 Sep 2023 23:27:14 +0200 Subject: [PATCH 32/47] ipp default version "2.0" --- src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt | 5 +++-- src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt | 9 +++++---- src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt | 3 ++- src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt | 4 ++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index 4e0ee798..2e4141a9 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -11,12 +11,13 @@ import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger import javax.net.ssl.SSLContext +import kotlin.text.Charsets.UTF_8 class IppConfig( // core IPP config options var userName: String? = System.getProperty("user.name"), - var ippVersion: String = "1.1", + var ippVersion: String = "2.0", var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", @@ -32,7 +33,7 @@ class IppConfig( ) { fun authorization() = - "Basic " + getEncoder().encodeToString("$userName:$password".toByteArray(Charsets.UTF_8)) + "Basic " + getEncoder().encodeToString("$userName:$password".toByteArray(UTF_8)) fun trustAnyCertificateAndSSLHostname() { sslContext = SSLHelper.sslContextForAnyCertificate() diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt index 31b9c216..aaad717e 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt @@ -23,8 +23,8 @@ class IppRequest : IppMessage { val attributesCharset: Charset get() = operationGroup.getValue("attributes-charset") - val requestingUserName: String? - get() = operationGroup.getValueOrNull("requesting-user-name")?.text + val requestingUserName: String + get() = operationGroup.getTextValue("requesting-user-name") constructor() : super() @@ -34,7 +34,7 @@ class IppRequest : IppMessage { jobId: Int? = null, requestedAttributes: List? = null, requestingUserName: String? = null, - version: String = "1.1", + version: String = "2.0", requestId: Int = 1, charset: Charset = Charsets.UTF_8, naturalLanguage: String = "en" @@ -44,7 +44,7 @@ class IppRequest : IppMessage { jobId?.let { attribute("job-id", Integer, it) } printerUri?.let { attribute("printer-uri", Uri, it) } requestedAttributes?.let { attribute("requested-attributes", Keyword, it) } - requestingUserName?.let { attribute("requesting-user-name", NameWithoutLanguage, it) } + requestingUserName?.let { attribute("requesting-user-name", NameWithoutLanguage, IppString(it)) } } } @@ -60,4 +60,5 @@ class IppRequest : IppMessage { notifyTimeInterval?.let { attribute("notify-time-interval", Integer, it.toMillis() / 1000) } notifyLeaseDuration?.let { attribute("notify-lease-duration", Integer, it.toMillis() / 1000) } } + } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt index e035ec3f..eeb842cd 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt @@ -35,11 +35,12 @@ class IppResponse : IppMessage { constructor( status: IppStatus, - version: String = "1.1", + version: String = "2.0", requestId: Int = 1, charset: Charset = Charsets.UTF_8, naturalLanguage: String = "en" ) : super(version, requestId, charset, naturalLanguage) { code = status.code } + } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt index cbefb69f..8eed4ba7 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppRequestTests.kt @@ -34,7 +34,7 @@ class IppRequestTests { fun requestConstructor2() { val request = IppRequest(IppOperation.StartupPrinter, URI.create("ipp://foo")) assertEquals(1, request.requestId) - assertEquals("1.1", request.version) + assertEquals("2.0", request.version) assertEquals(IppOperation.StartupPrinter, request.operation) assertEquals(Charsets.UTF_8, request.attributesCharset) assertEquals("en", request.operationGroup.getValue("attributes-natural-language")) @@ -57,7 +57,7 @@ class IppRequestTests { log.info { "encoded ${requestEncoded.size} bytes" } val requestDecoded = IppRequest() requestDecoded.decode(requestEncoded) - assertEquals("1.1", requestDecoded.version) + assertEquals("2.0", requestDecoded.version) assertEquals(IppOperation.PrintJob, requestDecoded.operation) assertEquals(1, requestDecoded.requestId) assertNotNull(requestDecoded.operationGroup) From 632bcc20309de66b70c74dee8623454edd5d21b5 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 00:24:26 +0200 Subject: [PATCH 33/47] private logger --- src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt | 2 +- src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt | 2 +- src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt | 3 --- src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt | 2 +- src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt | 2 +- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt index 9a21c9c5..bd26ab9b 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt @@ -15,7 +15,7 @@ import java.util.logging.Logger.getLogger data class IppAttribute constructor(val name: String, val tag: IppTag) : IppAttributeBuilder { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) val values: MutableCollection = mutableListOf() diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index e0276b50..7b15c1ee 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -12,7 +12,7 @@ import java.util.logging.Logger.getLogger open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap>() { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) init { if (!tag.isGroupTag()) throw IppException("'$tag' is not a group tag") diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt index b6b1f191..cb920df8 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt @@ -3,7 +3,6 @@ package de.gmuth.ipp.core import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger -import java.util.logging.Logger.getLogger /** * Copyright (c) 2020-2022 Gerhard Muth @@ -12,8 +11,6 @@ import java.util.logging.Logger.getLogger // RFC8010 3.1.6. data class IppCollection(val members: MutableCollection> = mutableListOf()) { - val log = getLogger(javaClass.name) - constructor(vararg attributes: IppAttribute<*>) : this(attributes.toMutableList()) fun addAttribute(name: String, tag: IppTag, vararg values: Any) = diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index ae3bca00..7da22c40 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -16,7 +16,7 @@ import java.util.logging.Logger.getLogger class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputStream) { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) // character encoding for text and name attributes, RFC 8011 4.1.4.1 internal lateinit var attributesCharset: Charset diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt index 5e9f51a7..c2681b6b 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt @@ -13,7 +13,7 @@ import java.util.logging.Logger.getLogger class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStream) { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) // charset for text and name attributes, rfc 8011 4.1.4.1 internal lateinit var attributesCharset: Charset From 9d6996431ba62493b5858df469d2e1224f6157b0 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 00:25:55 +0200 Subject: [PATCH 34/47] throw when raw bytes are missing --- .../kotlin/de/gmuth/ipp/core/IppMessage.kt | 20 +++++++++---------- .../de/gmuth/ipp/core/IppMessageTests.kt | 9 ++------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index 4963983b..99675081 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -15,13 +15,13 @@ import java.util.logging.Logger.getLogger abstract class IppMessage() { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) var code: Short? = null var requestId: Int? = null var version: String? = null set(value) { // validate version if (Regex("""^\d\.\d$""").matches(value!!)) field = value - else throw IppException("invalid version string: $value") + else throw IppException("Invalid version string: $value") } val attributesGroups = mutableListOf() var documentInputStream: InputStream? = null @@ -48,8 +48,8 @@ abstract class IppMessage() { fun getAttributesGroups(tag: IppTag) = attributesGroups.filter { it.tag == tag } - fun getSingleAttributesGroup(tag: IppTag) = with(getAttributesGroups(tag)) { - if (isEmpty()) throw IppException("no group found with tag '$tag' in $attributesGroups") + fun getSingleAttributesGroup(tag: IppTag) = getAttributesGroups(tag).run { + if (isEmpty()) throw IppException("No group found with tag '$tag' in $attributesGroups") single() } @@ -81,12 +81,10 @@ abstract class IppMessage() { fun write(file: File) = write(FileOutputStream(file)) - fun encode(): ByteArray = - with(ByteArrayOutputStream()) { - write(this) - log.fine { "ByteArrayOutputStream size: ${this.size()} bytes" } - toByteArray() - } + fun encode(): ByteArray = ByteArrayOutputStream().run { + write(this) + toByteArray() + } // -------- // DECODING @@ -133,7 +131,7 @@ abstract class IppMessage() { fun saveRawBytes(file: File) = if (rawBytes == null) { - log.warning { "no raw bytes to save. you must call read/decode or write/encode before." } + throw IppException("No raw bytes to save. You must call read/decode or write/encode before.") } else { file.writeBytes(rawBytes!!) log.info { "saved ${file.path} (${file.length()} bytes)" } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt index d9e7077f..2877bdb6 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppMessageTests.kt @@ -90,13 +90,8 @@ class IppMessageTests { fun withoutRawBytes() { assertEquals("codeDescription []", message.toString()) message.log(log) - // missing raw bytes - with(createTempFile("test", null)) { - try { - message.saveRawBytes(this) - } finally { - this.delete() - } + assertFailsWith { // missing raw bytes + message.saveRawBytes(createTempFile("test", null)) } } From a760922743c49eefa5d130e9bff0762227bd02bb Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 00:42:22 +0200 Subject: [PATCH 35/47] bugfix for filenameSuffix() --- src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index 318c1ccf..cc1f8856 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -36,7 +36,7 @@ class IppDocument( } fun filenameSuffix() = when (format) { - "application/octetstream" -> "bin" + "application/octet-stream" -> "bin" "application/postscript" -> "ps" "application/pdf" -> "pdf" "image/jpeg" -> "jpg" From a98e13db1869e3fdc18a5a17c3c67e11a3e7ece0 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 00:43:05 +0200 Subject: [PATCH 36/47] throw on unknown operation code --- src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt | 13 +++++-------- .../kotlin/de/gmuth/ipp/core/IppOperationsTests.kt | 10 ++++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt b/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt index ecbdeb43..a1b86f33 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-6 @@ -131,20 +131,17 @@ enum class IppOperation(val code: Short) { CupsAuthenticateJob(0x400E), CupsGetPPD(0x400F), CupsGetDocument(0x4027), - CupsCreateLocalPrinter(0x4028), - - // improve resilience - UnknownOperationCode(-1); + CupsCreateLocalPrinter(0x4028); override fun toString(): String = registeredName() fun registeredName() = name - .replace(Regex("[A-Z]+")) { "-" + it.value } - .replace(Regex("^-"), "") + .replace(Regex("[A-Z]+")) { "-" + it.value } + .replace(Regex("^-"), "") companion object { fun fromShort(code: Short): IppOperation = - values().find { it.code == code } ?: UnknownOperationCode + values().find { it.code == code } ?: throw IppException("Unknown operation code %04x".format(code)) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt index b83ede7a..b4fa17cb 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt @@ -1,17 +1,19 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import kotlin.test.Test -import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class IppOperationsTests { @Test - fun unknownOperationCode() { - assertEquals(IppOperation.UnknownOperationCode, IppOperation.fromShort(0)) + fun unknownOperationCodeFails() { + assertFailsWith { + IppOperation.fromShort(0) + } } } \ No newline at end of file From caa6d262eec688d129236a1a68e601e9f3f5db29 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 00:44:24 +0200 Subject: [PATCH 37/47] fixed test --- src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 5a8adf45..28d32fcb 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -187,7 +187,7 @@ class IppJobTests { @Test fun cupsGetDocument3() { printer.attributes.remove("cups-version") - ippClientMock.mockResponse(cupsDocumentResponse("application/octetstream").apply { + ippClientMock.mockResponse(cupsDocumentResponse("application/octet-stream").apply { jobGroup.remove("document-name") }) job.cupsGetDocument(2).apply { From d154f48a6311bdf849ee4916e639857a10509716 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 01:06:23 +0200 Subject: [PATCH 38/47] formatting --- .../kotlin/de/gmuth/ipp/core/IppDateTime.kt | 156 ++++++++++-------- 1 file changed, 83 insertions(+), 73 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt b/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt index 4a190fed..7040d612 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import java.time.LocalDateTime @@ -13,78 +13,88 @@ import kotlin.math.absoluteValue // RFC2579 data class IppDateTime( - val year: Int, - val month: Int, - val day: Int, - val hour: Int, - val minutes: Int, - val seconds: Int, - val deciSeconds: Int, - val directionFromUTC: Char, // '+' or '-' - val hoursFromUTC: Int, - val minutesFromUTC: Int + val year: Int, + val month: Int, + val day: Int, + val hour: Int, + val minutes: Int, + val seconds: Int, + val deciSeconds: Int, + val directionFromUTC: Char, // '+' or '-' + val hoursFromUTC: Int, + val minutesFromUTC: Int ) { override fun toString() = toISO8601() fun toRFC2579() = format("%d-%d-%d,%d:%d:%d.%d,%c%d:%d") - fun toISO8601() = format("%04d-%02d-%02dT%02d:%02d:%02d.%01d%c%02d:%02d") private fun format(format: String) = - String.format(format, year, month, day, hour, minutes, seconds, deciSeconds, directionFromUTC, hoursFromUTC, minutesFromUTC) + format.format( + year, + month, + day, + hour, + minutes, + seconds, + deciSeconds, + directionFromUTC, + hoursFromUTC, + minutesFromUTC + ) // support for java.time.ZonedDateTime constructor(zonedDateTime: ZonedDateTime) : this( - zonedDateTime.year, - zonedDateTime.monthValue, - zonedDateTime.dayOfMonth, - zonedDateTime.hour, - zonedDateTime.minute, - zonedDateTime.second, - deciSeconds = zonedDateTime[ChronoField.MILLI_OF_SECOND] / 100, - offsetMinutes = zonedDateTime.zone.rules.getOffset(zonedDateTime.toLocalDateTime()).totalSeconds / 60 + year = zonedDateTime.year, + month = zonedDateTime.monthValue, + day = zonedDateTime.dayOfMonth, + hour = zonedDateTime.hour, + minutes = zonedDateTime.minute, + seconds = zonedDateTime.second, + deciSeconds = zonedDateTime[ChronoField.MILLI_OF_SECOND] / 100, + offsetMinutes = zonedDateTime.zone.rules.getOffset(zonedDateTime.toLocalDateTime()).totalSeconds / 60 ) fun toZonedDateTime(): ZonedDateTime = - ZonedDateTime.of( - LocalDateTime.of( - year, - month, - day, - hour, - minutes, - seconds, - deciSeconds * 100 * 1000 * 1000 // nanoSeconds - ), - ZoneOffset.ofTotalSeconds(getOffsetMinutes() * 60) - ) + ZonedDateTime.of( + LocalDateTime.of( + year, + month, + day, + hour, + minutes, + seconds, + deciSeconds * 100 * 1000 * 1000 // nanoSeconds + ), + ZoneOffset.ofTotalSeconds(getOffsetMinutes() * 60) + ) // support for java.util.Calendar constructor(calendar: Calendar) : this( - calendar[Calendar.YEAR], - calendar[Calendar.MONTH] + 1, - calendar[Calendar.DAY_OF_MONTH], - calendar[Calendar.HOUR_OF_DAY], - calendar[Calendar.MINUTE], - calendar[Calendar.SECOND], - deciSeconds = calendar[Calendar.MILLISECOND] / 100, - offsetMinutes = calendar.dstSavingsOffsetMillis() / 1000 / 60 + calendar[Calendar.YEAR], + calendar[Calendar.MONTH] + 1, + calendar[Calendar.DAY_OF_MONTH], + calendar[Calendar.HOUR_OF_DAY], + calendar[Calendar.MINUTE], + calendar[Calendar.SECOND], + deciSeconds = calendar[Calendar.MILLISECOND] / 100, + offsetMinutes = calendar.dstSavingsOffsetMillis() / 1000 / 60 ) fun toCalendar(): Calendar = - Calendar.getInstance().apply { - set(Calendar.YEAR, year) - set(Calendar.MONTH, month - 1) - set(Calendar.DAY_OF_MONTH, day) - set(Calendar.HOUR_OF_DAY, hour) - set(Calendar.MINUTE, minutes) - set(Calendar.SECOND, seconds) - set(Calendar.MILLISECOND, deciSeconds * 100) - timeZone = TimeZone.getTimeZone(getTimeZoneId()) - } + Calendar.getInstance().apply { + set(Calendar.YEAR, year) + set(Calendar.MONTH, month - 1) + set(Calendar.DAY_OF_MONTH, day) + set(Calendar.HOUR_OF_DAY, hour) + set(Calendar.MINUTE, minutes) + set(Calendar.SECOND, seconds) + set(Calendar.MILLISECOND, deciSeconds * 100) + timeZone = TimeZone.getTimeZone(getTimeZoneId()) + } // support for java.util.Date @@ -95,40 +105,40 @@ data class IppDateTime( }) fun toDate(): Date = - toCalendar().time + toCalendar().time // support for offset minutes with direction private constructor( - year: Int, - month: Int, - day: Int, - hour: Int, - minutes: Int, - seconds: Int, - deciSeconds: Int, - offsetMinutes: Int + year: Int, + month: Int, + day: Int, + hour: Int, + minutes: Int, + seconds: Int, + deciSeconds: Int, + offsetMinutes: Int ) : this( - year, - month, - day, - hour, - minutes, - seconds, - deciSeconds, - directionFromUTC = if (offsetMinutes < 0) '-' else '+', - hoursFromUTC = offsetMinutes.absoluteValue / 60, - minutesFromUTC = offsetMinutes.absoluteValue % 60 + year, + month, + day, + hour, + minutes, + seconds, + deciSeconds, + directionFromUTC = if (offsetMinutes < 0) '-' else '+', + hoursFromUTC = offsetMinutes.absoluteValue / 60, + minutesFromUTC = offsetMinutes.absoluteValue % 60 ) internal fun getOffsetMinutes() = - (if (directionFromUTC == '-') -1 else 1) * (hoursFromUTC * 60 + minutesFromUTC) + (if (directionFromUTC == '-') -1 else 1) * (hoursFromUTC * 60 + minutesFromUTC) internal fun getTimeZoneId() = - "GMT%c%02d%02d".format(directionFromUTC, hoursFromUTC, minutesFromUTC) + "GMT%c%02d%02d".format(directionFromUTC, hoursFromUTC, minutesFromUTC) } // Calendar extension fun Calendar.dstSavingsOffsetMillis() = - with(timeZone) { rawOffset + if (useDaylightTime() && inDaylightTime(time)) dstSavings else 0 } \ No newline at end of file + with(timeZone) { rawOffset + if (useDaylightTime() && inDaylightTime(time)) dstSavings else 0 } \ No newline at end of file From a07c1d11d60325a090ae20aaa9efd86f983bf5d2 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 07:52:11 +0200 Subject: [PATCH 39/47] throw on unknown code --- src/main/kotlin/de/gmuth/ipp/core/IppResolution.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppResolution.kt b/src/main/kotlin/de/gmuth/ipp/core/IppResolution.kt index 1341c528..cac87157 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppResolution.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppResolution.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ // RFC 8011 5.1.16. @@ -19,7 +19,8 @@ data class IppResolution(val x: Int, val y: Int, val unit: Int) { override fun toString() = name.lowercase() companion object { - fun fromInt(code: Int) = values().single { it.code == code } + fun fromInt(code: Int) = + values().singleOrNull() { it.code == code } ?: throw IppException("Unknown unit code $code") } } From a2d36f1d635f41b98f2b831c7094e1e0b35570c1 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 07:58:29 +0200 Subject: [PATCH 40/47] throw on unknown code --- .../kotlin/de/gmuth/ipp/core/IppResolutionTests.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppResolutionTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppResolutionTests.kt index 5b7352fe..64ca8174 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppResolutionTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppResolutionTests.kt @@ -1,12 +1,13 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppResolution.Unit.DPC import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class IppResolutionTests { @@ -20,4 +21,11 @@ class IppResolutionTests { assertEquals("1200x600 dpi", IppResolution(1200, 600).toString()) } + @Test + fun failOnUnknownUnitCode() { + assertFailsWith { + IppResolution.Unit.fromInt(10) + } + } + } \ No newline at end of file From 77933678ee84bc1402b571c7ee957d4f9c1cb1f8 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 08:50:49 +0200 Subject: [PATCH 41/47] refactored is.. functions --- src/main/kotlin/de/gmuth/ipp/core/IppTag.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt index 4d4e0fd0..a6664bce 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt @@ -61,11 +61,11 @@ enum class IppTag( MemberAttrName(0x4A, "memberAttrName", { it is String }); fun isDelimiterTag() = code < 0x10 - fun isGroupTag() = code < 0x10 && code != End.code + fun isGroupTag() = code < 0x10 && this != End fun isValueTag() = 0x10 <= code fun isOutOfBandTag() = code in 0x10..0x1f - fun isMemberAttrName() = code == MemberAttrName.code - fun isMemberAttrValue() = 0x10 <= code && code != MemberAttrName.code && code != EndCollection.code + fun isMemberAttrName() = this == MemberAttrName + fun isMemberAttrValue() = this != MemberAttrName && isValueTag() && this != EndCollection override fun toString() = registeredName From b6bd76fbae537ab0a1c87d48180b6d461275c27b Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 08:52:48 +0200 Subject: [PATCH 42/47] removed logging --- src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index 7da22c40..a87a401d 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -203,12 +203,10 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt internal fun readLengthAndValue() = readBytes(readShort().toInt()) - // avoid Java-11-readNBytes(length) for compatibility with older jvms - internal fun readBytes(length: Int) = - ByteArray(length).apply { - log.finer { "read $length bytes" } - readFully(this) - } + // avoid readNBytes(length) for compatibility with JREs < 11 + internal fun readBytes(length: Int) = ByteArray(length).apply { + readFully(this) + } internal fun readExpectedValueLength(expected: Int, throwException: Boolean = true): Boolean { mark(2) From 78de9eb3340a6ceda51561182577cd6c33935ce1 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 08:53:35 +0200 Subject: [PATCH 43/47] test calendar west --- .../de/gmuth/ipp/core/IppDateTimeTests.kt | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppDateTimeTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppDateTimeTests.kt index 976f9494..b072d412 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppDateTimeTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppDateTimeTests.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import java.text.SimpleDateFormat @@ -10,7 +10,6 @@ import java.util.* import kotlin.test.Test import kotlin.test.assertEquals - class IppDateTimeTests { private val javaDateUtc = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse("2020-12-13T10:22:33.400+00:00") @@ -48,8 +47,8 @@ class IppDateTimeTests { } @Test - fun toCalendar() { - with(ippDateTime3HoursEast.toCalendar()) { + fun toCalendarEast() { + ippDateTime3HoursEast.toCalendar().run { assertEquals(2020, get(Calendar.YEAR)) assertEquals(12 - 1, get(Calendar.MONTH)) assertEquals(13, get(Calendar.DAY_OF_MONTH)) @@ -62,7 +61,21 @@ class IppDateTimeTests { } @Test - fun calendarConstructor() { + fun toCalendarWest() { + ippDateTime1HourWest.toCalendar().run { + assertEquals(2020, get(Calendar.YEAR)) + assertEquals(12 - 1, get(Calendar.MONTH)) + assertEquals(13, get(Calendar.DAY_OF_MONTH)) + assertEquals(9, get(Calendar.HOUR_OF_DAY)) + assertEquals(22, get(Calendar.MINUTE)) + assertEquals(33, get(Calendar.SECOND)) + assertEquals(400, get(Calendar.MILLISECOND)) + assertEquals("GMT-01:00", timeZone.id) + } + } + + @Test + fun calendarWestConstructor() { val calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT-01:00")).apply { set(Calendar.YEAR, 2020) set(Calendar.MONTH, 12 - 1) @@ -105,7 +118,7 @@ class IppDateTimeTests { @Test fun toDate() { - with(GregorianCalendar(TimeZone.getTimeZone("UTC"))) { + GregorianCalendar(TimeZone.getTimeZone("UTC")).run { time = ippDateTime3HoursEast.toDate() assertEquals(2020, get(Calendar.YEAR)) assertEquals(12 - 1, get(Calendar.MONTH)) @@ -120,7 +133,7 @@ class IppDateTimeTests { @Test fun dateConstructor() { - with(IppDateTime(javaDateUtc)) { + IppDateTime(javaDateUtc).run { assertEquals(2020, year) assertEquals(12, month) assertEquals(13, day) From 24de8f1d2e6d7085069bf52e798a26aa7f0684a5 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Tue, 26 Sep 2023 09:18:04 +0200 Subject: [PATCH 44/47] updated sonar plugin to 4.3.1 --- build.gradle.kts | 5 +++-- gradle.properties | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index addf4dae..fa836f6d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ import org.jetbrains.dokka.gradle.DokkaTask plugins { id("org.jetbrains.kotlin.jvm") version "1.7.22" id("org.jetbrains.dokka") version "1.7.20" - id("org.sonarqube") version "4.3.0.3225" // https://plugins.gradle.org/plugin/org.sonarqube + id("org.sonarqube") version "4.3.1.3277" // https://plugins.gradle.org/plugin/org.sonarqube //id("org.sonarqube") version "3.5.0.2730" // supports java 8, dropped with 4.1 id("maven-publish") id("signing") @@ -30,7 +30,8 @@ repositories { //} dependencies { - testImplementation("org.jetbrains.kotlin:kotlin-test-junit") + testImplementation("org.jetbrains.kotlin:kotlin-test") + //testImplementation("org.jetbrains.kotlin:kotlin-test-junit") } // gradlew clean -x test build publishToMavenLocal diff --git a/gradle.properties b/gradle.properties index 554f1f9e..c878a2c2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,8 @@ # solution for "sonarqube jvm out of memory" -org.gradle.jvmargs=-Xmx3g +#org.gradle.jvmargs=-Xmx3g + +# https://docs.sonarsource.com/sonarqube/latest/analyzing-source-code/scanners/sonarscanner-for-gradle/#troubleshooting +org.gradle.jvmargs=-XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M # java -XX:+PrintFlagsFinal -version|grep MaxHeapSize # Oracle-arm 11.0.5: MaxHeapSize = 2147483648 From 22c8f4e1cc81cc6ca9c07d59381d00ba91c3404b Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Wed, 4 Oct 2023 23:55:36 +0200 Subject: [PATCH 45/47] major refactoring --- README.md | 53 ++++---- .../kotlin/de/gmuth/io/ByteArrayExtension.kt | 23 ---- .../ColorMode.kt} | 4 +- .../ipp/attributes/CommunicationChannel.kt | 33 +++++ .../de/gmuth/ipp/attributes/DocumentFormat.kt | 32 +++++ .../Finishing.kt} | 6 +- .../IppJobState.kt => attributes/JobState.kt} | 19 ++- .../kotlin/de/gmuth/ipp/attributes/Marker.kt | 64 +++++++++ .../kotlin/de/gmuth/ipp/attributes/Media.kt | 25 ++++ .../gmuth/ipp/attributes/MediaCollection.kt | 49 +++++++ .../de/gmuth/ipp/attributes/MediaMargin.kt | 26 ++++ .../de/gmuth/ipp/attributes/MediaSize.kt | 23 ++++ .../de/gmuth/ipp/attributes/MediaSource.kt | 32 +++++ .../OrientationRequested.kt} | 6 +- .../PrintQuality.kt} | 6 +- .../de/gmuth/ipp/attributes/PrinterState.kt | 29 +++++ .../PrinterType.kt} | 16 ++- .../IppSides.kt => attributes/Sides.kt} | 6 +- .../TemplateAttributes.kt} | 28 ++-- .../de/gmuth/ipp/client/ApplePrintJobInfo.kt | 22 ---- .../kotlin/de/gmuth/ipp/client/CupsClient.kt | 44 +++---- .../kotlin/de/gmuth/ipp/client/CupsMarker.kt | 43 ------ .../kotlin/de/gmuth/ipp/client/IppClient.kt | 40 +++--- .../ipp/client/IppCommunicationChannel.kt | 16 --- .../kotlin/de/gmuth/ipp/client/IppConfig.kt | 4 +- .../kotlin/de/gmuth/ipp/client/IppDocument.kt | 4 +- .../gmuth/ipp/client/IppEventNotification.kt | 12 +- .../gmuth/ipp/client/IppExchangeException.kt | 6 +- src/main/kotlin/de/gmuth/ipp/client/IppJob.kt | 79 ++++++----- .../kotlin/de/gmuth/ipp/client/IppMedia.kt | 75 ----------- .../kotlin/de/gmuth/ipp/client/IppPrinter.kt | 123 +++++++----------- .../de/gmuth/ipp/client/IppPrinterState.kt | 21 --- .../de/gmuth/ipp/client/IppSubscription.kt | 4 +- .../client/{IppWhichJobs.kt => WhichJobs.kt} | 10 +- .../kotlin/de/gmuth/ipp/core/IppAttribute.kt | 40 +++--- .../de/gmuth/ipp/core/IppAttributesGroup.kt | 29 +++-- .../kotlin/de/gmuth/ipp/core/IppCollection.kt | 8 +- .../de/gmuth/ipp/core/IppInputStream.kt | 84 +++++++----- .../kotlin/de/gmuth/ipp/core/IppMessage.kt | 31 +++-- .../kotlin/de/gmuth/ipp/core/IppOperation.kt | 4 +- .../de/gmuth/ipp/core/IppOutputStream.kt | 20 +-- .../kotlin/de/gmuth/ipp/core/IppRequest.kt | 4 +- .../kotlin/de/gmuth/ipp/core/IppResponse.kt | 2 +- .../kotlin/de/gmuth/ipp/core/IppStatus.kt | 4 +- src/main/kotlin/de/gmuth/ipp/core/IppTag.kt | 4 + .../de/gmuth/{csv => ipp/iana}/CSVTable.kt | 4 +- .../ipp/iana/IppRegistrationsSection2.kt | 56 ++++---- .../ipp/iana/IppRegistrationsSection6.kt | 59 +++++---- .../de/gmuth/io/ByteArraySavingInputStream.kt | 0 .../gmuth/io/ByteArraySavingOutputStream.kt | 0 .../de/gmuth/ipp/client/CupsClientTests.kt | 3 +- .../de/gmuth/ipp/client/IppClientMock.kt | 16 +-- .../kotlin/de/gmuth/ipp/client/IppJobTests.kt | 11 +- .../de/gmuth/ipp/client/IppMediaTests.kt | 15 ++- .../de/gmuth/ipp/client/IppPrinterTests.kt | 72 +++++----- .../de/gmuth/ipp/core/IppAttributeTests.kt | 10 +- .../de/gmuth/ipp/core/IppOperationsTests.kt | 2 +- .../de/gmuth/ipp/core/IppStatusTests.kt | 4 +- src/test/resources/logging.properties | 13 +- 59 files changed, 821 insertions(+), 657 deletions(-) delete mode 100644 src/main/kotlin/de/gmuth/io/ByteArrayExtension.kt rename src/main/kotlin/de/gmuth/ipp/{client/IppColorMode.kt => attributes/ColorMode.kt} (86%) create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/CommunicationChannel.kt create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt rename src/main/kotlin/de/gmuth/ipp/{client/IppFinishing.kt => attributes/Finishing.kt} (93%) rename src/main/kotlin/de/gmuth/ipp/{client/IppJobState.kt => attributes/JobState.kt} (58%) create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/Marker.kt create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/Media.kt create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/MediaCollection.kt create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/MediaMargin.kt create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/MediaSize.kt create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/MediaSource.kt rename src/main/kotlin/de/gmuth/ipp/{client/IppOrientationRequested.kt => attributes/OrientationRequested.kt} (73%) rename src/main/kotlin/de/gmuth/ipp/{client/IppPrintQuality.kt => attributes/PrintQuality.kt} (70%) create mode 100644 src/main/kotlin/de/gmuth/ipp/attributes/PrinterState.kt rename src/main/kotlin/de/gmuth/ipp/{client/CupsPrinterType.kt => attributes/PrinterType.kt} (84%) rename src/main/kotlin/de/gmuth/ipp/{client/IppSides.kt => attributes/Sides.kt} (75%) rename src/main/kotlin/de/gmuth/ipp/{client/IppTemplateAttributes.kt => attributes/TemplateAttributes.kt} (65%) delete mode 100644 src/main/kotlin/de/gmuth/ipp/client/ApplePrintJobInfo.kt delete mode 100644 src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt delete mode 100644 src/main/kotlin/de/gmuth/ipp/client/IppCommunicationChannel.kt delete mode 100644 src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt delete mode 100644 src/main/kotlin/de/gmuth/ipp/client/IppPrinterState.kt rename src/main/kotlin/de/gmuth/ipp/client/{IppWhichJobs.kt => WhichJobs.kt} (76%) rename src/main/kotlin/de/gmuth/{csv => ipp/iana}/CSVTable.kt (98%) rename src/{main => test}/kotlin/de/gmuth/io/ByteArraySavingInputStream.kt (100%) rename src/{main => test}/kotlin/de/gmuth/io/ByteArraySavingOutputStream.kt (100%) diff --git a/README.md b/README.md index 1b694df0..fb9fe206 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ RFCs [8010](https://tools.ietf.org/html/rfc8010), [![Build](https://github.com/gmuth/ipp-client-kotlin/workflows/build/badge.svg)](https://github.com/gmuth/ipp-client-kotlin/actions?query=workflow%3Abuild) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=gmuth_ipp-client-kotlin&metric=alert_status)](https://sonarcloud.io/summary/overall?id=gmuth_ipp-client-kotlin) [![Sonar Coverage](https://img.shields.io/sonar/coverage/gmuth_ipp-client-kotlin?color=00AA00&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/component_measures?metric=Coverage&view=list&id=gmuth_ipp-client-kotlin) -[![Maven Central](https://img.shields.io/maven-central/v/de.gmuth/ipp-client.svg?label=maven%20central)](https://central.sonatype.com/artifact/de.gmuth/ipp-client/2.4/overview) +[![Maven Central](https://img.shields.io/maven-central/v/de.gmuth/ipp-client.svg?label=maven%20central)](https://central.sonatype.com/artifact/de.gmuth/ipp-client/2.5/overview) ## Usage @@ -34,22 +34,22 @@ println("black: ${ippPrinter.marker(BLACK).levelPercent()} %") val file = File("A4-ten-pages.pdf") val job = ippPrinter.printJob( file, - jobName(file.name), - jobPriority(30), - documentFormat("application/pdf"), copies(2), numberUp(2), + jobPriority(30), + jobName(file.name), + DocumentFormat.PDF, pageRanges(2..3, 8..10), - printerResolution(300), finishings(Punch, Staple), - IppPrintQuality.High, - IppColorMode.Monochrome, - IppSides.TwoSidedLongEdge, - media("iso_a4_210x297mm"), - mediaColSource("tray-1"), + printerResolution(300, DPI), + Sides.TwoSidedLongEdge, + ColorMode.Monochrome, + PrintQuality.High, + Media.ISO_A4, + mediaColWithSource("tray-1"), notifyEvents = listOf("job-state-changed", "job-stopped", "job-completed") // CUPS ) -job.subscription?.processEvents { println(it) } +job.subscription?.pollAndHandleNotifications { println(it) } // print remote file, make printer pull document from remote server val remoteFile = URI.create("http://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf") @@ -62,7 +62,7 @@ job.waitForTermination() // manage jobs ippPrinter.getJobs().forEach { println(it) } -ippPrinter.getJobs(IppWhichJobs.Completed) +ippPrinter.getJobs(WhichJobs.Completed) val job = ippPrinter.getJob(4) job.hold() @@ -75,9 +75,10 @@ ippPrinter.pause() ippPrinter.resume() ippPrinter.sound() // identify printer -// subscribe and log events (e.g. from CUPS) for 1 minute -ippPrinter.createPrinterSubscription(60) - .processEvents() +// subscribe and handle/log events (e.g. from CUPS) for 5 minutes +ippPrinter + .createPrinterSubscription(notifyLeaseDuration=Duration.ofMinutes(5)) + .pollAndHandleNotifications() ``` ### Printer Capabilities @@ -85,10 +86,10 @@ ippPrinter.createPrinterSubscription(60) `IppPrinter` checks, if attribute values are supported by looking into `'...-supported'` printer attributes. ``` -documentFormat("application/pdf") +DocumentFormat("application/pcl") -WARN: according to printer attributes value 'application/pdf' is not supported. -document-format-supported (1setOf mimeMediaType) = application/PCL,application/postscript +WARN: according to printer attributes value 'application/pcl' is not supported. +document-format-supported (1setOf mimeMediaType) = application/pdf,application/postscript ``` ### exchange [IppRequest](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt) for [IppResponse](https://github.com/gmuth/ipp-client-kotlin/blob/master/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt) @@ -126,7 +127,7 @@ cupsClient.getPrinters().forEach { // list all completed jobs for queue cupsClient.getPrinter("ColorJet_HP") - .getJobs(IppWhichJobs.Completed) + .getJobs(WhichJobs.Completed) .forEach { println(it) } // default printer @@ -146,17 +147,15 @@ cupsClient.getJobsAndSaveDocuments(IppWhichJobs.Canceled) ```kotlin val printer = IppPrinter(URI.create("ipp://192.168.2.64")) -val width = 2540 * 2 // hundreds of mm - -val jpegFile = File("label.jpg") +val jpegFile = File("label.jpeg") val image = javax.imageio.ImageIO.read(jpegFile) +val width = 2540 * 2 // hundreds of mm +val margin = 300 // 3 mm printer.printJob( - jpegFile, documentFormat("image/jpeg"), - IppMedia.Collection( - size = IppMedia.Size(width, width * image.height / image.width), - margins = IppMedia.Margins(0) - ) + jpegFile, + DocumentFormat.JPEG, + mediaColWithSize(width, width*image.height/image.width).withMargin(margin) ) ``` diff --git a/src/main/kotlin/de/gmuth/io/ByteArrayExtension.kt b/src/main/kotlin/de/gmuth/io/ByteArrayExtension.kt deleted file mode 100644 index 903f9f3f..00000000 --- a/src/main/kotlin/de/gmuth/io/ByteArrayExtension.kt +++ /dev/null @@ -1,23 +0,0 @@ -package de.gmuth.io - -import java.io.ByteArrayOutputStream -import java.io.OutputStream - -fun ByteArray.hexdump(maxRows: Int = 32, dump: (String) -> Unit) { - val hexStringBuilder = StringBuilder() - val charStringBuilder = StringBuilder() - fun dumpLine() = dump("%-${maxRows * 3}s '%s'".format(hexStringBuilder, charStringBuilder)) - for ((index, b) in withIndex()) { - hexStringBuilder.append("%02X ".format(b)) - charStringBuilder.append(b.toInt().toChar()) - if ((index + 1) % maxRows == 0) { - dumpLine() - hexStringBuilder.clear() - charStringBuilder.clear() - } - } - if (isNotEmpty()) dumpLine() -} - -fun ByteArray(writeBytes: (OutputStream) -> Unit): ByteArray = - ByteArrayOutputStream().also { writeBytes(it) }.toByteArray() \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt b/src/main/kotlin/de/gmuth/ipp/attributes/ColorMode.kt similarity index 86% rename from src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/ColorMode.kt index 1c5994fe..b1be1988 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppColorMode.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/ColorMode.kt @@ -1,4 +1,4 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** * Copyright (c) 2020-2023 Gerhard Muth @@ -9,7 +9,7 @@ import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppTag.Keyword -enum class IppColorMode(private val keyword: String) : IppAttributeBuilder { +enum class ColorMode(private val keyword: String) : IppAttributeBuilder { Auto("auto"), Color("color"), diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/CommunicationChannel.kt b/src/main/kotlin/de/gmuth/ipp/attributes/CommunicationChannel.kt new file mode 100644 index 00000000..2f57d458 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/CommunicationChannel.kt @@ -0,0 +1,33 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2021-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttributesGroup +import java.net.URI + +// RFC 8011, page 26 +class CommunicationChannel( + val uri: URI, + val security: String, + val authentication: String +) { + override fun toString() = "$uri, security=$security, authentication=$authentication" + + companion object { + fun getCommunicationChannelsSupported(attributes: IppAttributesGroup) = with(attributes) { + val printerUriSupportedList = getValues>("printer-uri-supported") + val uriSecuritySupportedList = getValues>("uri-security-supported") + val uriAuthenticationSupportedList = getValues>("uri-authentication-supported") + printerUriSupportedList.indices.map { + CommunicationChannel( + printerUriSupportedList[it], + uriSecuritySupportedList[it], + uriAuthenticationSupportedList[it] + ) + } + } + } + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt b/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt new file mode 100644 index 00000000..fe3c9cfe --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt @@ -0,0 +1,32 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppTag +import de.gmuth.ipp.core.IppTag.MimeMediaType + +open class DocumentFormat(val mediaMimeType: String) : IppAttributeBuilder { + + // application + object OCTET_STREAM : DocumentFormat("application/octet-stream") + object POSTSCRIPT : DocumentFormat("application/postscript") + object PDF : DocumentFormat("application/pdf") + + // image + object PWG_RASTER : DocumentFormat("image/pwg-raster") + object TIFF : DocumentFormat("image/tiff") + object JPEG : DocumentFormat("image/jpeg") + object PNG : DocumentFormat("image/png") + + // vnd + object HP_PCL : DocumentFormat("vnd.hp-PCL") + + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("document-format", MimeMediaType, mediaMimeType) + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppFinishing.kt b/src/main/kotlin/de/gmuth/ipp/attributes/Finishing.kt similarity index 93% rename from src/main/kotlin/de/gmuth/ipp/client/IppFinishing.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/Finishing.kt index cc9109c6..06edf441 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppFinishing.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/Finishing.kt @@ -1,10 +1,10 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** - * Copyright (c) 2021 Gerhard Muth + * Copyright (c) 2021-2023 Gerhard Muth */ -enum class IppFinishing(val code: Int) { +enum class Finishing(val code: Int) { None(3), Staple(4), Punch(5), diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJobState.kt b/src/main/kotlin/de/gmuth/ipp/attributes/JobState.kt similarity index 58% rename from src/main/kotlin/de/gmuth/ipp/client/IppJobState.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/JobState.kt index c1009753..6e9e7d5d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJobState.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/JobState.kt @@ -1,9 +1,14 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppTag + // "job-state": type1 enum [RFC8011] // +----> canceled // / @@ -13,7 +18,7 @@ package de.gmuth.ipp.client // | v v / // +----> pending-held processing-stopped ---+ -enum class IppJobState(val code: Int, private val registeredName: String) { +enum class JobState(val code: Int, private val registeredName: String) : IppAttributeBuilder { Pending(3, "pending"), PendingHeld(4, "pending-held"), @@ -23,13 +28,15 @@ enum class IppJobState(val code: Int, private val registeredName: String) { Aborted(8, "aborted"), Completed(9, "completed"); - fun isTerminated() = this in listOf(Canceled, Aborted, Completed) - // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-6 override fun toString() = registeredName + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("job-state", IppTag.Enum, code) + companion object { - fun fromInt(code: Int) = values().single { it.code == code } + private fun fromInt(code: Int) = values().single { it.code == code } + fun fromAttributes(attributes: IppAttributesGroup) = fromInt(attributes.getValue("job-state")) } } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/Marker.kt b/src/main/kotlin/de/gmuth/ipp/attributes/Marker.kt new file mode 100644 index 00000000..a34fa5fd --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/Marker.kt @@ -0,0 +1,64 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppString +import java.util.logging.Logger.getLogger + +// https://www.cups.org/doc/spec-ipp.html +class Marker( + val type: String, + val name: String, + val level: Int, + val lowLevel: Int, + val highLevel: Int, + val colorCode: String +) { + val color: Color = Color.fromString(colorCode) + + fun levelPercent() = 100 * level / highLevel + fun levelIsLow() = level < lowLevel + + override fun toString() = "%-10s %3d %% %5s %-12s %-8s %s" + .format(color, levelPercent(), if (levelIsLow()) "(low)" else "", type, colorCode, name) + + enum class Color(val code: String) { + NONE("NONE"), + CYAN("#00FFFF"), + BLACK("#000000"), + YELLOW("#FFFF00"), + MAGENTA("#FF00FF"), + TRI_COLOR("#00FFFF#FF00FF#FFFF00"), // Cyan, Magenta, Yellow + UNKNOWN("#?"); + + companion object { + val log = getLogger(Color::class.java.name) + fun fromString(code: String) = values().find { it.code == code.uppercase() } + ?: UNKNOWN.apply { log.warning { "Unknown color code: $code" } } + } + } + + companion object { + fun getMarkers(attributes: IppAttributesGroup): Collection = with(attributes) { + val types = getValues>("marker-types") + val names = getValues>("marker-names") + val levels = getValues>("marker-levels") + val lowLevels = getValues>("marker-low-levels") + val highLevels = getValues>("marker-high-levels") + val colors = getValues>("marker-colors") + types.indices.map { + Marker( + types[it], + names[it].text, + levels[it], + lowLevels[it], + highLevels[it], + colors[it].text + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/Media.kt b/src/main/kotlin/de/gmuth/ipp/attributes/Media.kt new file mode 100644 index 00000000..b4ac2bd6 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/Media.kt @@ -0,0 +1,25 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppTag.Keyword + +open class Media(val keyword: String) : IppAttributeBuilder { + + object ISO_A3 : Media("iso_a3_297x420mm") + object ISO_A4 : Media("iso_a4_210x297mm") + object ISO_A5 : Media("iso_a5_148x210mm") + object ISO_A6 : Media("iso_a6_105x148mm") + object NA_LEGAL : Media("na_legal_8.5x14in") + object NA_LETTER : Media("na_letter_8.5x11in") + object NA_LEDGER : Media("na_ledger_11x17in") + + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("media", Keyword, keyword) + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/MediaCollection.kt b/src/main/kotlin/de/gmuth/ipp/attributes/MediaCollection.kt new file mode 100644 index 00000000..ac3f05b7 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/MediaCollection.kt @@ -0,0 +1,49 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppCollection +import de.gmuth.ipp.core.IppTag.BegCollection +import de.gmuth.ipp.core.IppTag.Keyword +import java.util.logging.Logger.getLogger + +// https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext21-20230210-5100.7.pdf 6.3 +class MediaCollection( + var size: MediaSize? = null, + var margin: MediaMargin? = null, + var source: MediaSource? = null, + var type: String? = null + +) : IppAttributeBuilder { + + val log = getLogger(javaClass.name) + + fun withType(keyword: String) = this + .apply { type = keyword } + + fun withSource(keyword: String) = this + .apply { source = MediaSource(keyword) } + + fun withSize(xDimension: Int, yDimension: Int) = this + .apply { size = MediaSize(xDimension, yDimension) } + + fun withMargin(value: Int) = this + .apply { margin = MediaMargin(value, value, value, value) } + + fun withMargin(left: Int? = null, right: Int? = null, top: Int? = null, bottom: Int? = null) = this + .apply { margin = MediaMargin(left, right, top, bottom) } + + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("media-col", BegCollection, IppCollection().apply { + type?.let { addAttribute("media-type", Keyword, it) } + size?.let { add(it.buildIppAttribute(printerAttributes)) } + margin?.let { addAll(it.buildIppAttributes()) } + source?.let { add(it.buildIppAttribute(printerAttributes)) } + }) + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/MediaMargin.kt b/src/main/kotlin/de/gmuth/ipp/attributes/MediaMargin.kt new file mode 100644 index 00000000..5f1c4742 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/MediaMargin.kt @@ -0,0 +1,26 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppTag.Integer + +open class MediaMargin( + val left: Int? = null, + val right: Int? = null, + val top: Int? = null, + val bottom: Int? = null +) { + constructor(margin: Int) : this(margin, margin, margin, margin) + + fun buildIppAttributes(): Collection> = + ArrayList>().apply { + fun addMargin(side: String, value: Int) = add(IppAttribute("media-$side-margin", Integer, value)) + top?.let { addMargin("top", it) } + left?.let { addMargin("left", it) } + right?.let { addMargin("right", it) } + bottom?.let { addMargin("bottom", it) } + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/MediaSize.kt b/src/main/kotlin/de/gmuth/ipp/attributes/MediaSize.kt new file mode 100644 index 00000000..ff56ea8e --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/MediaSize.kt @@ -0,0 +1,23 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppCollection +import de.gmuth.ipp.core.IppTag.BegCollection +import de.gmuth.ipp.core.IppTag.Integer + +// unit: 1/100 mm, e.g. 2540 = 1 inch +class MediaSize(val xDimension: Int, val yDimension: Int) : IppAttributeBuilder { + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute( + "media-size", BegCollection, IppCollection( + IppAttribute("x-dimension", Integer, xDimension), + IppAttribute("y-dimension", Integer, yDimension) + ) + ) +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/MediaSource.kt b/src/main/kotlin/de/gmuth/ipp/attributes/MediaSource.kt new file mode 100644 index 00000000..3a58a057 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/MediaSource.kt @@ -0,0 +1,32 @@ +package de.gmuth.ipp.attributes + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppTag.Keyword +import java.util.logging.Logger.getLogger + +open class MediaSource(val keyword: String) : IppAttributeBuilder { + + val log = getLogger(javaClass.name) + + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("media-source", Keyword, keyword) + .apply { validateSource(printerAttributes) } + + private fun validateSource(printerAttributes: IppAttributesGroup) { + val mediaSourceSupported = printerAttributes["media-source-supported"] + if (mediaSourceSupported == null) { + log.fine { "printer does not provide attribute 'media-source-supported'" } + } else { + if (!mediaSourceSupported.values.contains(keyword)) { + log.warning { "media-source '$keyword' not supported by printer" } + log.warning { mediaSourceSupported.toString() } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppOrientationRequested.kt b/src/main/kotlin/de/gmuth/ipp/attributes/OrientationRequested.kt similarity index 73% rename from src/main/kotlin/de/gmuth/ipp/client/IppOrientationRequested.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/OrientationRequested.kt index 31bc7648..bf62a216 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppOrientationRequested.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/OrientationRequested.kt @@ -1,7 +1,7 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppAttribute @@ -9,7 +9,7 @@ import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppTag.Enum -enum class IppOrientationRequested(private val code: Int) : IppAttributeBuilder { +enum class OrientationRequested(private val code: Int) : IppAttributeBuilder { Portrait(3), Landscape(4), diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrintQuality.kt b/src/main/kotlin/de/gmuth/ipp/attributes/PrintQuality.kt similarity index 70% rename from src/main/kotlin/de/gmuth/ipp/client/IppPrintQuality.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/PrintQuality.kt index 07c3b469..89aecf8c 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrintQuality.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/PrintQuality.kt @@ -1,7 +1,7 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppAttribute @@ -9,7 +9,7 @@ import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppTag.Enum -enum class IppPrintQuality(private val code: Int) : IppAttributeBuilder { +enum class PrintQuality(private val code: Int) : IppAttributeBuilder { Draft(3), Normal(4), High(5); diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/PrinterState.kt b/src/main/kotlin/de/gmuth/ipp/attributes/PrinterState.kt new file mode 100644 index 00000000..79301679 --- /dev/null +++ b/src/main/kotlin/de/gmuth/ipp/attributes/PrinterState.kt @@ -0,0 +1,29 @@ +package de.gmuth.ipp.attributes + +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppTag + +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + +enum class PrinterState(val code: Int) : IppAttributeBuilder { + + Idle(3), + Processing(4), + Stopped(5); + + // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-6 + override fun toString() = name.lowercase() + + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("printer-state", IppTag.Enum, code) + + companion object { + private fun fromInt(code: Int) = values().single { it.code == code } + fun fromAttributes(attributes: IppAttributesGroup) = fromInt(attributes.getValue("printer-state")) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt b/src/main/kotlin/de/gmuth/ipp/attributes/PrinterType.kt similarity index 84% rename from src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/PrinterType.kt index b8f27dcd..a7ac2380 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsPrinterType.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/PrinterType.kt @@ -1,16 +1,20 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** * Copyright (c) 2020-2023 Gerhard Muth */ +import de.gmuth.ipp.core.IppAttribute +import de.gmuth.ipp.core.IppAttributeBuilder +import de.gmuth.ipp.core.IppAttributesGroup +import de.gmuth.ipp.core.IppTag import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html -class CupsPrinterType(val value: Int) { +class PrinterType(val value: Int) : IppAttributeBuilder { val log = getLogger(javaClass.name) @@ -61,4 +65,12 @@ class CupsPrinterType(val value: Int) { } } + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = + IppAttribute("printer-type", IppTag.Enum, value) + + companion object { + fun fromAttributes(attributes: IppAttributesGroup) = + PrinterType(attributes.getValue("printer-type")) + } + } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppSides.kt b/src/main/kotlin/de/gmuth/ipp/attributes/Sides.kt similarity index 75% rename from src/main/kotlin/de/gmuth/ipp/client/IppSides.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/Sides.kt index 5e349888..fe0163b9 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppSides.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/Sides.kt @@ -1,7 +1,7 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import de.gmuth.ipp.core.IppAttribute @@ -9,7 +9,7 @@ import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppTag.Keyword -enum class IppSides(private val keyword: String) : IppAttributeBuilder { +enum class Sides(private val keyword: String) : IppAttributeBuilder { OneSided("one-sided"), TwoSidedLongEdge("two-sided-long-edge"), diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt b/src/main/kotlin/de/gmuth/ipp/attributes/TemplateAttributes.kt similarity index 65% rename from src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt rename to src/main/kotlin/de/gmuth/ipp/attributes/TemplateAttributes.kt index 2fd338ba..eb1a16b4 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppTemplateAttributes.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/TemplateAttributes.kt @@ -1,4 +1,4 @@ -package de.gmuth.ipp.client +package de.gmuth.ipp.attributes /** * Copyright (c) 2020-2023 Gerhard Muth @@ -12,16 +12,12 @@ import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* /** - * create common job attributes + * Create usual job attributes */ -object IppTemplateAttributes { +object TemplateAttributes { // for operation group - @JvmStatic - fun documentFormat(format: String) = - IppAttribute("document-format", MimeMediaType, format) - @JvmStatic fun jobName(name: String) = IppAttribute("job-name", NameWithoutLanguage, name) @@ -49,16 +45,20 @@ object IppTemplateAttributes { IppAttribute("page-ranges", RangeOfInteger, ranges) @JvmStatic - fun media(keyword: String) = - IppAttribute("media", Keyword, keyword) + fun finishings(values: Collection) = + IppAttribute("finishings", IppTag.Enum, values.map { it.code }) @JvmStatic - fun finishings(finishings: Collection) = - IppAttribute("finishings", IppTag.Enum, finishings.map { it.code }) + fun mediaSource(keyword: String) = + IppAttribute("media-source", Keyword, keyword) @JvmStatic // input tray - fun mediaColSource(value: String) = - IppMedia.Collection(source = value) + fun mediaColWithSource(keyword: String) = + MediaCollection(source = MediaSource(keyword)) + + @JvmStatic // unit: hundreds of mm + fun mediaColWithSize(xDimension: Int, yDimension: Int) = + MediaCollection(size = MediaSize(xDimension, yDimension)) // support vararg parameter for convenience @@ -66,6 +66,6 @@ object IppTemplateAttributes { fun pageRanges(vararg ranges: IntRange) = pageRanges(ranges.toList()) @JvmStatic - fun finishings(vararg finishings: IppFinishing) = finishings(finishings.toList()) + fun finishings(vararg finishings: Finishing) = finishings(finishings.toList()) } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/ApplePrintJobInfo.kt b/src/main/kotlin/de/gmuth/ipp/client/ApplePrintJobInfo.kt deleted file mode 100644 index 66dbc3e5..00000000 --- a/src/main/kotlin/de/gmuth/ipp/client/ApplePrintJobInfo.kt +++ /dev/null @@ -1,22 +0,0 @@ -package de.gmuth.ipp.client - -/** - * Copyright (c) 2020-2023 Gerhard Muth - */ - -import de.gmuth.ipp.core.IppAttributesGroup -import de.gmuth.ipp.core.IppString - -// custom job attributes provided by Apple CUPS - -data class ApplePrintJobInfo( - val applicationName: String, - val jobOwner: String, - val jobName: String?, // not available in CUPS 2.2.5 -) { - constructor(jobAttributes: IppAttributesGroup) : this( - jobAttributes.getTextValue("com.apple.print.JobInfo.PMApplicationName"), - jobAttributes.getTextValue("com.apple.print.JobInfo.PMJobOwner"), - jobAttributes.getValueOrNull("com.apple.print.JobInfo.PMJobName")?.text, - ) -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt index 00d64e4e..99987def 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt @@ -4,8 +4,8 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ +import de.gmuth.ipp.client.WhichJobs.All import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException -import de.gmuth.ipp.client.IppWhichJobs.All import de.gmuth.ipp.core.IppOperation import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppRequest @@ -19,13 +19,13 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.logging.Logger.getLogger // https://www.cups.org/doc/spec-ipp.html -open class CupsClient( +class CupsClient( val cupsUri: URI = URI.create("ipp://localhost"), val ippClient: IppClient = IppClient() ) { constructor(host: String = "localhost") : this(URI.create("ipp://$host")) - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) val config: IppConfig by ippClient::config var userName: String? by config::userName var cupsClientWorkDirectory = File("CUPS/${cupsUri.host}") @@ -75,7 +75,7 @@ open class CupsClient( }.cupsVersion } - fun cupsPrinterUri(printerName: String) = with(cupsUri) { + internal fun cupsPrinterUri(printerName: String) = with(cupsUri) { val optionalPort = if (port > 0) ":$port" else "" URI("$scheme://$host$optionalPort/printers/$printerName") }.apply { @@ -127,10 +127,10 @@ open class CupsClient( ) // -------------------------------------- - // build request for a named CUPS printer + // Build request for a named CUPS printer // -------------------------------------- - protected fun cupsPrinterRequest( + internal fun cupsPrinterRequest( operation: IppOperation, printerName: String, deviceUri: URI? = null, @@ -151,13 +151,13 @@ open class CupsClient( } //---------------------- - // delegate to IppClient + // Delegate to IppClient //---------------------- - fun ippRequest(operation: IppOperation, printerURI: URI = cupsUri) = + internal fun ippRequest(operation: IppOperation, printerURI: URI = cupsUri) = ippClient.ippRequest(operation, printerURI) - fun exchange(ippRequest: IppRequest) = + internal fun exchange(ippRequest: IppRequest) = ippClient.exchange(ippRequest) fun basicAuth(user: String, password: String) = @@ -167,13 +167,13 @@ open class CupsClient( // Delegate to IppPrinter //----------------------- - val ippPrinter: IppPrinter by lazy { + internal val ippPrinter: IppPrinter by lazy { IppPrinter(cupsUri, ippClient = ippClient, getPrinterAttributesOnInit = false) .apply { workDirectory = cupsClientWorkDirectory } } fun getJobs( - whichJobs: IppWhichJobs? = null, + whichJobs: WhichJobs? = null, limit: Int? = null, requestedAttributes: List? = ippPrinter.getJobsRequestedAttributes ) = @@ -224,12 +224,12 @@ open class CupsClient( // https://github.com/apple/cups/issues/5919 log.info { "Waiting for CUPS to generate IPP Everywhere PPD." } - log.info { this.toString() } + log.info { toString() } do { Thread.sleep(1000) updateAttributes("printer-make-and-model") } while (!makeAndModel.text.lowercase().contains("everywhere")) - log.info { this.toString() } + log.info { toString() } // make printer permanent exchange( @@ -244,7 +244,7 @@ open class CupsClient( enable() resume() updateAttributes() - log.info { this.toString() } + log.info { toString() } } } @@ -255,7 +255,7 @@ open class CupsClient( private val jobOwners = mutableSetOf() fun getJobsAndSaveDocuments( - whichJobs: IppWhichJobs = All, + whichJobs: WhichJobs = All, updateJobAttributes: Boolean = false, commandToHandleSavedFile: String? = null ): Collection { @@ -266,18 +266,14 @@ open class CupsClient( requestedAttributes = listOf( "job-id", "job-uri", "job-printer-uri", "job-originating-user-name", "job-name", "job-state", "job-state-reasons", - if(version < "1.6.0") "document-count" else "number-of-documents" + if (version < "1.6.0") "document-count" else "number-of-documents" ) // wired: do not modify above set // job-originating-user-name is missing when document-count or job-originating-host-name ist requested // once hidden in response, wait for one minute and user-name should show up again ) .onEach { job -> // update attributes and lookup job owners - if (updateJobAttributes) { // update could remove job-origination-user-name - // important: no requested-attributes is different to group "all" - // job updateAttributes "all" - job.attributes = job.getJobAttributes().jobGroup - } + if (updateJobAttributes) job.updateAttributes() log.info { job.toString() } job.getOriginatingUserNameOrAppleJobOwnerOrNull()?.let { jobOwners.add(it) } } @@ -310,7 +306,7 @@ open class CupsClient( .pollAndHandleNotifications(pollEvery, autoRenewSubscription = autoRenewLease) { event -> log.info { event.toString() } with(event.getJob()) { - while (jobIsIncoming()) { + while (isIncoming()) { log.info { toString() } Thread.sleep(1000) updateAttributes() @@ -324,7 +320,7 @@ open class CupsClient( // Get and save documents for job // ------------------------------ - private fun getAndSaveDocuments( + internal fun getAndSaveDocuments( job: IppJob, onSuccessUpdateJobAttributes: Boolean = false, optionalCommandToHandleFile: String? = null @@ -339,7 +335,7 @@ open class CupsClient( ippExchangeException.httpStatus!! != 401 } - if(!getDocuments()) { + if (!getDocuments()) { val configuredUserName = config.userName jobOwners.forEach { config.userName = it diff --git a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt b/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt deleted file mode 100644 index c7e21559..00000000 --- a/src/main/kotlin/de/gmuth/ipp/client/CupsMarker.kt +++ /dev/null @@ -1,43 +0,0 @@ -package de.gmuth.ipp.client - -/** - * Copyright (c) 2020-2023 Gerhard Muth - */ - -import java.util.logging.Logger.getLogger - -// https://www.cups.org/doc/spec-ipp.html -class CupsMarker( - val type: String, - val name: String, - val level: Int, - val lowLevel: Int, - val highLevel: Int, - val colorCode: String -) { - val color: Color = Color.fromString(colorCode) - - fun levelPercent() = 100 * level / highLevel - fun levelIsLow() = level <= lowLevel - - override fun toString() = "%-10s %3d %% %5s %-6s %-7s %s".format( - color, levelPercent(), if (levelIsLow()) "(low)" else "", type, colorCode, name - ) - - enum class Color(val code: String) { - NONE("none"), - CYAN("#00FFFF"), - BLACK("#000000"), - YELLOW("#FFFF00"), - MAGENTA("#FF00FF"), - TRI_COLOR("#00FFFF#FF00FF#FFFF00"), // Cyan, Magenta, Yellow - UNKNOWN("#?"); - - companion object { - val log = getLogger(Color::class.java.name) - fun fromString(code: String) = values().find { it.code == code } - ?: UNKNOWN.apply { log.warning { "unknown color code: $code" } } - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt index 414f6088..9123ccac 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppClient.kt @@ -24,9 +24,12 @@ import javax.net.ssl.HostnameVerifier import javax.net.ssl.HttpsURLConnection open class IppClient(val config: IppConfig = IppConfig()) { - val log = getLogger(javaClass.name) + protected val log = getLogger(javaClass.name) + var saveMessages: Boolean = false var saveMessagesDirectory = File("ipp-messages") + var onExceptionSaveMessages: Boolean = false + var throwWhenNotSuccessful: Boolean = true fun basicAuth(user: String, password: String) { config.userName = user @@ -41,13 +44,13 @@ open class IppClient(val config: IppConfig = IppConfig()) { // build IppRequest //----------------- - private val requestCounter = AtomicInteger(1) + protected val requestCounter = AtomicInteger(1) fun ippRequest( operation: IppOperation, printerUri: URI, jobId: Int? = null, - requestedAttributes: List? = null, + requestedAttributes: Collection? = null, userName: String? = config.userName ) = IppRequest( operation, @@ -62,23 +65,23 @@ open class IppClient(val config: IppConfig = IppConfig()) { ) //------------------------------------ - // exchange IppRequest for IppResponse + // Exchange IppRequest for IppResponse //------------------------------------ fun exchange(request: IppRequest): IppResponse { val ippUri: URI = request.printerUri log.finer { "send '${request.operation}' request to $ippUri" } - val response = postRequest(toHttpUri(ippUri), request) + val response = httpPost(toHttpUri(ippUri), request) log.fine { "$ippUri: $request => $response" } if (saveMessages) saveMessages(ippUri, request, response) - validateResponse(request, response, throwWhenNotSuccessful = true) + validateResponse(request, response) return response } //---------------------------------------------- - // http post IPP request and decode IPP response + // HTTP post IPP request and decode IPP response //---------------------------------------------- - open fun postRequest(httpUri: URI, request: IppRequest): IppResponse { + open fun httpPost(httpUri: URI, request: IppRequest): IppResponse { with(httpUri.toURL().openConnection() as HttpURLConnection) { if (this is HttpsURLConnection && config.sslContext != null) { sslSocketFactory = config.sslContext!!.socketFactory @@ -96,7 +99,7 @@ open class IppClient(val config: IppConfig = IppConfig()) { } } - private fun saveMessages(ippUri: URI, request: IppRequest, response: IppResponse) { + protected fun saveMessages(ippUri: URI, request: IppRequest, response: IppResponse) { val messageSubDirectory = File(saveMessagesDirectory, ippUri.host).apply { if (!mkdirs() && !isDirectory) throw IppException("failed to create directory: $path") } @@ -106,26 +109,27 @@ open class IppClient(val config: IppConfig = IppConfig()) { response.saveRawBytes(file("response")) } - private fun validateResponse(request: IppRequest, response: IppResponse, throwWhenNotSuccessful: Boolean = true) { + protected fun validateResponse(request: IppRequest, response: IppResponse) { with(response) { if (status == ClientErrorBadRequest) request.log(log, SEVERE, prefix = "BAD-REQUEST: ") if (containsGroup(Unsupported)) unsupportedGroup.values.forEach { log.warning() { "unsupported: $it" } } if (!isSuccessful()) { IppRegistrationsSection2.validate(request) - if (throwWhenNotSuccessful) + if (throwWhenNotSuccessful) { throw if (status == ClientErrorNotFound) ClientErrorNotFoundException(request, response) else IppExchangeException(request, response) + } } } } - private fun toHttpUri(ippUri: URI): URI = with(ippUri) { + protected fun toHttpUri(ippUri: URI): URI = with(ippUri) { val scheme = scheme.replace("ipp", "http") val port = if (port == -1) 631 else port URI.create("$scheme://$host:$port$rawPath") } - private fun HttpURLConnection.configure(chunked: Boolean) { + protected fun HttpURLConnection.configure(chunked: Boolean) { config.run { connectTimeout = timeout.toMillis().toInt() readTimeout = timeout.toMillis().toInt() @@ -139,17 +143,20 @@ open class IppClient(val config: IppConfig = IppConfig()) { setRequestProperty("Accept-Encoding", "identity") // avoid 'gzip' with Androids OkHttp } - private fun HttpURLConnection.validateResponse(request: IppRequest, contentStream: InputStream) = + protected fun HttpURLConnection.validateResponse(request: IppRequest, contentStream: InputStream) = when { responseCode == 401 -> with(request) { "User '$requestingUserName' is unauthorized for operation '$operation'" } + responseCode != 200 -> { "HTTP request failed: $responseCode, $responseMessage" } + contentType != null && !contentType.startsWith(APPLICATION_IPP) -> { "Invalid Content-Type: $contentType" } + else -> null }?.let { throw IppExchangeException( @@ -162,11 +169,10 @@ open class IppClient(val config: IppConfig = IppConfig()) { ) } - private fun decodeContentStream( + protected fun decodeContentStream( request: IppRequest, httpStatus: Int, - contentStream: InputStream, - onExceptionSaveMessages: Boolean = true + contentStream: InputStream ) = IppResponse().apply { try { read(contentStream) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppCommunicationChannel.kt b/src/main/kotlin/de/gmuth/ipp/client/IppCommunicationChannel.kt deleted file mode 100644 index d2df3b3b..00000000 --- a/src/main/kotlin/de/gmuth/ipp/client/IppCommunicationChannel.kt +++ /dev/null @@ -1,16 +0,0 @@ -package de.gmuth.ipp.client - -/** - * Copyright (c) 2021 Gerhard Muth - */ - -import java.net.URI - -// RFC 8011, page 26 -class IppCommunicationChannel( - val uri: URI, - val security: String, - val authentication: String -) { - override fun toString() = "$uri, security=$security, authentication=$authentication" -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt index 2e4141a9..387f3454 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppConfig.kt @@ -15,13 +15,13 @@ import kotlin.text.Charsets.UTF_8 class IppConfig( - // core IPP config options + // IPP config var userName: String? = System.getProperty("user.name"), var ippVersion: String = "2.0", var charset: Charset = Charsets.UTF_8, var naturalLanguage: String = "en", - // HTTP config options + // HTTP config var timeout: Duration = Duration.ofSeconds(30), var userAgent: String? = "ipp-client/3.0", var password: String? = null, diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt index cc1f8856..9edf4eff 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppDocument.kt @@ -18,7 +18,7 @@ class IppDocument( val attributes: IppAttributesGroup, val inputStream: InputStream ) { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) val number: Int get() = attributes.getValue("document-number") @@ -51,7 +51,7 @@ class IppDocument( if (numberOfDocuments > 1) append("-doc-$number") job.getOriginatingUserNameOrAppleJobOwnerOrNull()?.let { append("-$it") } if (attributes.containsKey("com.apple.print.JobInfo.PMApplicationName")) { - append("-${applePrintJobInfo.applicationName}") + append("-${attributes.getTextValue("com.apple.print.JobInfo.PMApplicationName")}") } job.getJobNameOrDocumentNameSuppliedOrAppleJobNameOrNull()?.let { append("-${it.take(100)}") diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt index 75d81101..9badd91d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt @@ -4,6 +4,8 @@ package de.gmuth.ipp.client * Copyright (c) 2021-2023 Gerhard Muth */ +import de.gmuth.ipp.attributes.JobState +import de.gmuth.ipp.attributes.PrinterState import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppString import java.util.logging.Logger @@ -13,8 +15,6 @@ class IppEventNotification( val subscription: IppSubscription, val attributes: IppAttributesGroup ) { - val log = getLogger(javaClass.name) - val sequenceNumber: Int get() = attributes.getValue("notify-sequence-number") @@ -30,8 +30,8 @@ class IppEventNotification( val jobId: Int get() = attributes.getValue("notify-job-id") - val jobState: IppJobState - get() = IppJobState.fromInt(attributes.getValue("job-state")) + val jobState: JobState + get() = JobState.fromAttributes(attributes) val jobStateReasons: List get() = attributes.getValues("job-state-reasons") @@ -42,8 +42,8 @@ class IppEventNotification( val printerName: IppString get() = attributes.getValue("printer-name") - val printerState: IppPrinterState - get() = IppPrinterState.fromInt(attributes.getValue("printer-state")) + val printerState: PrinterState + get() = PrinterState.fromAttributes(attributes) val printerStateReasons: List get() = attributes.getValues("printer-state-reasons") diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt index d10542c6..16af8002 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt @@ -49,10 +49,6 @@ open class IppExchangeException( } } - init { - if (httpStatus == 400) saveMessages("http_400") - } - fun statusIs(status: IppStatus) = response?.status == status fun log(logger: Logger, level: Level = INFO) = logger.run { @@ -65,7 +61,7 @@ open class IppExchangeException( } fun saveMessages( - fileNameWithoutSuffix: String = "ipp_exchange_exception", + fileNameWithoutSuffix: String = "ipp_exchange_exception_$httpStatus", directory: String = createTempDirectory("ipp-client-").pathString ) { request.saveRawBytes(File(directory, "$fileNameWithoutSuffix.request")) diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index 5dc3c5eb..92456b36 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -4,7 +4,8 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.ipp.client.IppJobState.* +import de.gmuth.ipp.attributes.JobState +import de.gmuth.ipp.attributes.JobState.* import de.gmuth.ipp.core.* import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppTag.* @@ -12,6 +13,8 @@ import java.io.File import java.io.FileInputStream import java.io.InputStream import java.net.URI +import java.time.Duration +import java.time.ZonedDateTime import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger @@ -22,12 +25,13 @@ class IppJob( var attributes: IppAttributesGroup, subscriptionAttributes: IppAttributesGroup? = null ) { + companion object { - var defaultDelayMillis: Long = 3000 + var defaultDelay: Duration = Duration.ofSeconds(1) var useJobOwnerAsUserName: Boolean = false } - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) var subscription: IppSubscription? = subscriptionAttributes?.let { IppSubscription(printer, it) } val ippConfig = printer.ippConfig @@ -44,8 +48,8 @@ class IppJob( val printerUri: URI get() = attributes.getValue("job-printer-uri") - val state: IppJobState - get() = IppJobState.fromInt(attributes.getValue("job-state")) + val state: JobState + get() = JobState.fromAttributes(attributes) val stateReasons: List get() = attributes.getValues("job-state-reasons") @@ -78,9 +82,17 @@ class IppJob( val documentNameSupplied: IppString get() = attributes.getValue("document-name-supplied") - // only supported by Apple CUPS - val applePrintJobInfo: ApplePrintJobInfo - get() = ApplePrintJobInfo(attributes) + val timeAtCreation: ZonedDateTime + get() = attributes.getTimeValue("time-at-creation") + + val timeAtProcessing: ZonedDateTime + get() = attributes.getTimeValue("time-at-processing") + + val timeAtCompleted: ZonedDateTime + get() = attributes.getTimeValue("time-at-completed") + + val appleJobOwner: String // only supported by Apple CUPS + get() = attributes.getTextValue("com.apple.print.JobInfo.PMJobOwner") fun hasStateReasons() = attributes.containsKey("job-state-reasons") @@ -89,24 +101,24 @@ class IppJob( fun isCanceled() = state == Canceled fun isProcessing() = state == Processing fun isProcessingStopped() = state == ProcessingStopped - fun isTerminated() = state.isTerminated() + fun isTerminated() = state in listOf(Canceled, Aborted, Completed) // https://datatracker.ietf.org/doc/html/rfc8011#section-5.3.8 fun stateReasonsContain(reason: String) = hasStateReasons() && stateReasons.contains(reason) fun isProcessingToStopPoint() = stateReasonsContain("processing-to-stop-point") fun resourcesAreNotReady() = stateReasonsContain("resources-are-not-ready") - fun jobIsIncoming() = stateReasonsContain("job-incoming") + fun isIncoming() = stateReasonsContain("job-incoming") fun getOriginatingUserNameOrAppleJobOwnerOrNull() = when { attributes.containsKey("job-originating-user-name") -> originatingUserName.text - attributes.containsKey("com.apple.print.JobInfo.PMJobOwner") -> applePrintJobInfo.jobOwner + attributes.containsKey("com.apple.print.JobInfo.PMJobOwner") -> appleJobOwner else -> null } fun getJobNameOrDocumentNameSuppliedOrAppleJobNameOrNull() = when { attributes.containsKey("job-name") -> name.text attributes.containsKey("document-name-supplied") -> documentNameSupplied.text - attributes.containsKey("com.apple.print.JobInfo.PMJobName") -> applePrintJobInfo.jobName + attributes.containsKey("com.apple.print.JobInfo.PMJobName") -> attributes.getTextValue("com.apple.print.JobInfo.PMJobName") else -> null } @@ -115,30 +127,30 @@ class IppJob( //------------------- // RFC 8011 4.3.4 groups: 'all', 'job-template', 'job-description' - - @JvmOverloads fun getJobAttributes(requestedAttributes: List? = null) = - exchange(ippRequest(GetJobAttributes, requestedAttributes)) + exchange(ippRequest(GetJobAttributes, requestedAttributes)).jobGroup fun getJobAttributes(vararg requestedAttribute: String) = getJobAttributes(requestedAttribute.toList()) - fun updateAttributes(jobAttributeGroupName: String = "all") { - attributes = getJobAttributes(jobAttributeGroupName).jobGroup - } + fun updateAttributes(requestedAttributes: List? = null) = + attributes.put(getJobAttributes(requestedAttributes)) + + fun updateAttributes(vararg requestedAttributes: String) = + updateAttributes(requestedAttributes.toList()) //------------------------------------------ // Wait for terminal state (RFC 8011 5.3.7.) //------------------------------------------ @JvmOverloads - fun waitForTermination(delayMillis: Long = defaultDelayMillis) { - log.info { "wait for termination of job #$id" } + fun waitForTermination(delay: Duration = defaultDelay) { + log.info { "Wait for termination of job #$id" } var lastPrinterString = "" var lastJobString = toString() log.info { lastJobString } while (!isTerminated()) { - Thread.sleep(delayMillis) + Thread.sleep(delay.toMillis()) updateAttributes() if (toString() != lastJobString) { lastJobString = toString() @@ -146,6 +158,7 @@ class IppJob( } if (isProcessingStopped() || lastPrinterString.isNotEmpty()) { printer.updatePrinterStateAttributes() + printer.updateAttributes() if (printer.toString() != lastPrinterString) { lastPrinterString = printer.toString() log.info { lastPrinterString } @@ -159,18 +172,19 @@ class IppJob( // Job administration //------------------- - fun hold() = exchange(ippRequest(HoldJob)).also { updateAttributes() } - fun release() = exchange(ippRequest(ReleaseJob)).also { updateAttributes() } - fun restart() = exchange(ippRequest(RestartJob)).also { updateAttributes() } + fun hold() = exchange(ippRequest(HoldJob)) + fun release() = exchange(ippRequest(ReleaseJob)) + fun restart() = exchange(ippRequest(RestartJob)) - fun cancel(messageForOperator: String? = null): IppResponse { // RFC 8011 4.3.3 - if (isCanceled()) log.warning { "job #$id is already 'canceled'" } - if (isProcessingToStopPoint()) log.warning { "job #$id is already 'processing-to-stop-point'" } + @JvmOverloads + fun cancel(messageForOperator: String? = null, updateAttributes: Boolean = true): IppResponse { // RFC 8011 4.3.3 + if (isCanceled()) log.warning { "Job #$id is already 'canceled'" } + if (isProcessingToStopPoint()) log.warning { "Job #$id is already 'processing-to-stop-point'" } val request = ippRequest(CancelJob).apply { messageForOperator?.let { operationGroup.attribute("message", TextWithoutLanguage, it) } } - log.info { "cancel job#$id" } - return exchange(request).also { updateAttributes() } + log.info { "Cancel job #$id" } + return exchange(request).also { if (updateAttributes) updateAttributes() } } //-------------- @@ -274,6 +288,7 @@ class IppJob( return IppDocument(this, response.jobGroup, response.documentInputStream!!) } + @JvmOverloads fun cupsGetDocuments( save: Boolean = false, optionalCommandToHandleFile: String? = null @@ -299,7 +314,7 @@ class IppJob( operation, id, requestedAttributes, userName = when { useJobOwnerAsUserName && attributes.containsKey("job-originating-user-name") -> originatingUserName.text - useJobOwnerAsUserName && attributes.containsKey("com.apple.print.JobInfo.PMJobOwner") -> applePrintJobInfo.jobOwner + useJobOwnerAsUserName && attributes.containsKey("com.apple.print.JobInfo.PMJobOwner") -> appleJobOwner else -> ippConfig.userName } ) @@ -321,9 +336,9 @@ class IppJob( if (containsKey("job-impressions-completed")) append(", impressions-completed=$impressionsCompleted") if (containsKey("job-originating-host-name")) append(", originating-host-name=$originatingHostName") if (containsKey("job-originating-user-name")) append(", originating-user-name=$originatingUserName") - if (containsKey("com.apple.print.JobInfo.PMJobName")) append(", $applePrintJobInfo") + if (containsKey("com.apple.print.JobInfo.PMJobOwner")) append(", appleJobOwner=$appleJobOwner") if (containsKey("number-of-documents") || containsKey("document-count")) append(", number-of-documents=$numberOfDocuments") - if (containsKey("job-printer-uri")) append(", job-printer-uri=$printerUri") + if (containsKey("job-printer-uri")) append(", printer-uri=$printerUri") toString() } } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt b/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt deleted file mode 100644 index 9a7d9394..00000000 --- a/src/main/kotlin/de/gmuth/ipp/client/IppMedia.kt +++ /dev/null @@ -1,75 +0,0 @@ -package de.gmuth.ipp.client - -/** - * Copyright (c) 2020-2021 Gerhard Muth - */ - -import de.gmuth.ipp.core.IppAttribute -import de.gmuth.ipp.core.IppAttributeBuilder -import de.gmuth.ipp.core.IppAttributesGroup -import de.gmuth.ipp.core.IppCollection -import de.gmuth.ipp.core.IppTag.* -import java.util.logging.Logger.getLogger - -object IppMedia { - - val log = getLogger(javaClass.name) - - // unit: 1/100 mm, e.g. 2540 = 1 inch - class Size(val xDimension: Int, val yDimension: Int) : IppAttributeBuilder { - - override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = - IppAttribute( - "media-size", BegCollection, IppCollection( - IppAttribute("x-dimension", Integer, xDimension), - IppAttribute("y-dimension", Integer, yDimension) - ) - ) - } - - class Margins(left: Int? = null, right: Int? = null, top: Int? = null, bottom: Int? = null) : - ArrayList>() { - constructor(margin: Int) : this(margin, margin, margin, margin) - - init { - if (top != null) add(IppAttribute("media-top-margin", Integer, top)) - if (left != null) add(IppAttribute("media-left-margin", Integer, left)) - if (right != null) add(IppAttribute("media-right-margin", Integer, right)) - if (bottom != null) add(IppAttribute("media-bottom-margin", Integer, bottom)) - } - } - - // PWG 5100.3, 3.13 - class Collection( - var size: Size? = null, - var margins: Margins? = null, - var source: String? = null, - var type: String? = null - - ) : IppAttributeBuilder { - - override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = - IppAttribute("media-col", BegCollection, IppCollection().apply { - if (source != null) { - checkIfSourceIsSupported(printerAttributes) - addAttribute("media-source", Keyword, source!!) - } - type?.let { addAttribute("media-type", Keyword, it) } - size?.let { add(it.buildIppAttribute(printerAttributes)) } - margins?.let { addAll(it) } - }) - - private fun checkIfSourceIsSupported(printerAttributes: IppAttributesGroup) { - val mediaSourceSupported = printerAttributes["media-source-supported"] - if (mediaSourceSupported == null) { - log.fine { "printer does not provide attribute 'media-source-supported'" } - } else { - if (!mediaSourceSupported.values.contains(source)) { - log.warning { "media-source '$source' not supported by printer" } - log.warning { mediaSourceSupported.toString() } - } - } - } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt index 2e569012..fb0d1232 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt @@ -4,10 +4,14 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.ipp.client.IppPrinterState.* +import de.gmuth.ipp.attributes.* +import de.gmuth.ipp.attributes.CommunicationChannel.Companion.getCommunicationChannelsSupported +import de.gmuth.ipp.attributes.Marker.Companion.getMarkers +import de.gmuth.ipp.attributes.PrinterState.* import de.gmuth.ipp.core.* import de.gmuth.ipp.core.IppOperation.* import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound +import de.gmuth.ipp.core.IppStatus.SuccessfulOk import de.gmuth.ipp.core.IppTag.* import de.gmuth.ipp.iana.IppRegistrationsSection2 import java.io.* @@ -28,7 +32,7 @@ open class IppPrinter( getPrinterAttributesOnInit: Boolean = true, requestedAttributesOnInit: List? = null ) { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) var workDirectory: File = createTempDirectory().toFile() companion object { @@ -74,10 +78,10 @@ open class IppPrinter( if (ippExchangeException.statusIs(ClientErrorNotFound)) log.severe { ippExchangeException.message } else { - log.log(SEVERE, ippExchangeException, { "failed to get printer attributes on init" }) + log.severe { "Failed to get printer attributes on init. Workaround: getPrinterAttributesOnInit=false" } ippExchangeException.response?.let { if (it.containsGroup(Printer)) log.info { "${it.printerGroup.size} attributes parsed" } - else log.warning { it.toString() } + else log.warning { "RESPONSE: $it" } } } throw ippExchangeException @@ -105,7 +109,7 @@ open class IppPrinter( ) //--------------- - // ipp attributes + // IPP attributes //--------------- val name: IppString @@ -123,8 +127,8 @@ open class IppPrinter( val isAcceptingJobs: Boolean get() = attributes.getValue("printer-is-accepting-jobs") - val state: IppPrinterState - get() = IppPrinterState.fromInt(attributes.getValue("printer-state")) + val state: PrinterState + get() = PrinterState.fromAttributes(attributes) val stateReasons: List get() = attributes.getValues("printer-state-reasons") @@ -136,9 +140,7 @@ open class IppPrinter( get() = attributes.getValues("document-format-supported") val operationsSupported: List - get() = attributes.getValues>("operations-supported").map { - IppOperation.fromShort(it.toShort()) - } + get() = attributes.getValues>("operations-supported").map { IppOperation.fromInt(it) } val colorSupported: Boolean get() = attributes.getValue("color-supported") @@ -155,25 +157,14 @@ open class IppPrinter( val mediaDefault: String get() = attributes.getValue("media-default") + val mediaSourceSupported: List + get() = attributes.getValues("media-source-supported") + val versionsSupported: List get() = attributes.getValues("ipp-versions-supported") - val communicationChannelsSupported: List - get() = mutableListOf().apply { - with(attributes) { - val printerUriSupportedList = getValues>("printer-uri-supported") - val uriSecuritySupportedList = getValues>("uri-security-supported") - val uriAuthenticationSupportedList = getValues>("uri-authentication-supported") - for ((index, printerUriSupported) in printerUriSupportedList.withIndex()) - add( - IppCommunicationChannel( - printerUriSupported, - uriSecuritySupportedList[index], - uriAuthenticationSupportedList[index] - ) - ) - } - } + val communicationChannelsSupported: List + get() = getCommunicationChannelsSupported(attributes) val alert: List? // PWG 5100.9 get() = attributes.getValuesOrNull("printer-alert") @@ -182,49 +173,39 @@ open class IppPrinter( get() = attributes.getValuesOrNull("printer-alert-description") // ---------------------------------------------- - // extensions supported by cups and some printers + // Extensions supported by cups and some printers // https://www.cups.org/doc/spec-ipp.html // ---------------------------------------------- + val markers: Collection + get() = getMarkers(attributes) + + fun marker(color: Marker.Color) = + markers.single { it.color == color } + val deviceUri: URI get() = attributes.getValue("device-uri") - val printerType: CupsPrinterType - get() = CupsPrinterType(attributes.getValue("printer-type")) + val printerType: PrinterType + get() = PrinterType.fromAttributes(attributes) - fun hasCapability(capability: CupsPrinterType.Capability) = + fun hasCapability(capability: PrinterType.Capability) = printerType.contains(capability) - val markers: Collection - get() = with(attributes) { - val types = getValues>("marker-types") - val names = getValues>("marker-names") - val levels = getValues>("marker-levels") - val lowLevels = getValues>("marker-low-levels") - val highLevels = getValues>("marker-high-levels") - val colors = getValues>("marker-colors") - (0..types.size - 1).map { - CupsMarker( - types[it], - names[it].text, - levels[it], - lowLevels[it], - highLevels[it], - colors[it].text - ) - } - } - - fun marker(color: CupsMarker.Color) = markers.single { it.color == color } - val cupsVersion: String get() = attributes.getTextValue("cups-version") - //----------------- + val supportedAttributes: Collection> = attributes.values + .filter { it.name.endsWith("-supported") } + .sortedBy { it.name } + + //------------------------------------------------------- fun isIdle() = state == Idle fun isStopped() = state == Stopped fun isProcessing() = state == Processing + fun isMediaJam() = stateReasons.contains("media-jam") + fun isMediaLow() = stateReasons.contains("media-low") fun isMediaEmpty() = stateReasons.contains("media-empty") fun isMediaNeeded() = stateReasons.contains("media-needed") fun isDuplexSupported() = sidesSupported.any { it.startsWith("two-sided") } @@ -268,19 +249,14 @@ open class IppPrinter( // names of attribute groups: RFC 8011 4.2.5 //------------------------------------------ - fun getPrinterAttributes(requestedAttributes: List? = null) = - exchange(ippRequest(GetPrinterAttributes, requestedAttributes = requestedAttributes)) + fun getPrinterAttributes(requestedAttributes: Collection? = null) = + exchange(ippRequest(GetPrinterAttributes, requestedAttributes = requestedAttributes)).printerGroup fun getPrinterAttributes(vararg requestedAttributes: String) = getPrinterAttributes(requestedAttributes.toList()) - fun updateAttributes(requestedAttributes: List? = null) { - log.fine { "update attributes: $requestedAttributes" } - getPrinterAttributes(requestedAttributes).run { - if (containsGroup(Printer)) attributes.put(printerGroup) - else log.warning { "no printerGroup in response for requested attributes: $requestedAttributes" } - } - } + fun updateAttributes(requestedAttributes: List? = null) = + attributes.put(getPrinterAttributes(requestedAttributes)) fun updateAttributes(vararg requestedAttributes: String) = updateAttributes(requestedAttributes.toList()) @@ -381,23 +357,21 @@ open class IppPrinter( // Get-Job-Attributes (as IppJob) //------------------------------- - fun getJob(jobId: Int): IppJob { - val request = ippRequest(GetJobAttributes, jobId) - return exchangeForIppJob(request) - } + fun getJob(jobId: Int) = + exchangeForIppJob(ippRequest(GetJobAttributes, jobId)) - //--------------------------- - // Get-Jobs (as List) - //--------------------------- + //--------------------------------- + // Get-Jobs (as Collection) + //--------------------------------- @JvmOverloads fun getJobs( - whichJobs: IppWhichJobs? = null, + whichJobs: WhichJobs? = null, myJobs: Boolean? = null, limit: Int? = null, requestedAttributes: List? = getJobsRequestedAttributes ): Collection { - log.fine { "getJobs(whichJobs=$whichJobs, requestedAttributes=$requestedAttributes)"} + log.fine { "getJobs(whichJobs=$whichJobs, requestedAttributes=$requestedAttributes)" } val request = ippRequest(GetJobs, requestedAttributes = requestedAttributes).apply { operationGroup.run { whichJobs?.keyword?.let { @@ -413,7 +387,7 @@ open class IppPrinter( .map { IppJob(this, it) } } - fun getJobs(whichJobs: IppWhichJobs? = null, vararg requestedAttributes: String) = + fun getJobs(whichJobs: WhichJobs? = null, vararg requestedAttributes: String) = getJobs(whichJobs, requestedAttributes = requestedAttributes.toList()) //---------------------------- @@ -479,7 +453,7 @@ open class IppPrinter( fun ippRequest( operation: IppOperation, jobId: Int? = null, - requestedAttributes: List? = null, + requestedAttributes: Collection? = null, userName: String? = ippConfig.userName ) = ippClient .ippRequest(operation, printerUri, jobId, requestedAttributes, userName) @@ -494,6 +468,7 @@ open class IppPrinter( protected fun exchangeForIppJob(request: IppRequest): IppJob { val response = exchange(request) + if (response.status != SuccessfulOk) log.warning { "Job response status: ${response.status}" } if (request.containsGroup(Subscription) && !response.containsGroup(Subscription)) { request.log(log, WARNING, prefix = "REQUEST: ") val events: List = request.getSingleAttributesGroup(Subscription).getValues("notify-events") @@ -586,7 +561,7 @@ open class IppPrinter( fun savePrinterAttributes(directory: String = ".") { val printerModel: String = makeAndModel.text.replace("\\s+".toRegex(), "_") - getPrinterAttributes().run { + exchange(ippRequest(GetPrinterAttributes)).run { saveRawBytes(File(directory, "$printerModel.bin")) printerGroup.saveText(File(directory, "$printerModel.txt")) } diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppPrinterState.kt b/src/main/kotlin/de/gmuth/ipp/client/IppPrinterState.kt deleted file mode 100644 index ad22a2e4..00000000 --- a/src/main/kotlin/de/gmuth/ipp/client/IppPrinterState.kt +++ /dev/null @@ -1,21 +0,0 @@ -package de.gmuth.ipp.client - -/** - * Copyright (c) 2020 Gerhard Muth - */ - -// "printer-state": type1 enum [RFC8011] -enum class IppPrinterState(val code: Int) { - - Idle(3), - Processing(4), - Stopped(5); - - // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-6 - override fun toString() = name.lowercase() - - companion object { - fun fromInt(code: Int) = values().single { it.code == code } - } - -} \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt index 9396113c..4a7b9155 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt @@ -24,7 +24,7 @@ class IppSubscription( val printer: IppPrinter, var attributes: IppAttributesGroup ) { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) private var lastSequenceNumber: Int = 0 private var leaseStartedAt = now() @@ -67,7 +67,7 @@ class IppSubscription( exchange(ippRequest(GetSubscriptionAttributes, requestedAttributes = requestedAttributes)) fun updateAttributes() { - attributes = getSubscriptionAttributes().getSingleAttributesGroup(Subscription) + attributes.put(getSubscriptionAttributes().getSingleAttributesGroup(Subscription)) } //------------------ diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppWhichJobs.kt b/src/main/kotlin/de/gmuth/ipp/client/WhichJobs.kt similarity index 76% rename from src/main/kotlin/de/gmuth/ipp/client/IppWhichJobs.kt rename to src/main/kotlin/de/gmuth/ipp/client/WhichJobs.kt index 545e3e11..1493b52d 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppWhichJobs.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/WhichJobs.kt @@ -1,16 +1,16 @@ package de.gmuth.ipp.client /** - * Copyright (c) 2021 Gerhard Muth + * Copyright (c) 2021-2023 Gerhard Muth */ // PWG Job, Printer and shared Infrastructure Extensions -enum class IppWhichJobs(val keyword: String) { +enum class WhichJobs(val keyword: String) { // RFC 8011 Completed("completed"), NotCompleted("not-completed"), - // PWG5100.7 + // PWG 5100.7 All("all"), Aborted("aborted"), Canceled("canceled"), @@ -19,10 +19,10 @@ enum class IppWhichJobs(val keyword: String) { PendingHeld("pending-held"), ProcessingStopped("processing-stopped"), - // PWG5100.11 + // PWG 5100.11 ProofPrint("proof-print"), Saved("saved"), - // PWG5100.18 + // PWG 5100.18 Fetchable("fetchable") } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt index bd26ab9b..a9840138 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt @@ -13,7 +13,7 @@ import java.util.logging.Level.INFO import java.util.logging.Logger import java.util.logging.Logger.getLogger -data class IppAttribute constructor(val name: String, val tag: IppTag) : IppAttributeBuilder { +data class IppAttribute(val name: String, val tag: IppTag) : IppAttributeBuilder { private val log = getLogger(javaClass.name) @@ -24,42 +24,40 @@ data class IppAttribute constructor(val name: String, val tag: IppTag) : IppA } constructor(name: String, tag: IppTag, values: Collection) : this(name, tag) { - if (values.isNotEmpty()) validateValueClass(values.first() as Any) + if (values.isNotEmpty()) tag.validateValueClass(values.first() as Any) this.values.addAll(values) } constructor(name: String, tag: IppTag, vararg values: T) : this(name, tag, values.toList()) val value: T - get() = values.single().apply { - if (attributeIs1setOf(name) == true) log.warning { "'$name' is registered as '1setOf', use 'values' instead" } - } + get() = + if (attributeIs1setOf(name) == true) throw IppException("'$name' is registered as '1setOf', use 'values' instead") + else values.single() @Suppress("UNCHECKED_CAST") - fun additionalValue(attribute: IppAttribute<*>): Any = when { - attribute.name.isNotEmpty() -> throw IppException("for additional '$name' values attribute name must be empty") - attribute.values.size != 1 -> throw IppException("expected 1 additional value, not ${attribute.values.size}") - attribute.tag != tag -> log.warning { "$name: ignore additional value \"$attribute\" - tag is not '$tag'" } - else -> { - validateValueClass(attribute.value as Any) - values.add(attribute.value as T) + fun additionalValue(attribute: IppAttribute<*>) { + when { + attribute.name.isNotEmpty() -> throw IppException("For additional '$name' values attribute name must be empty") + attribute.values.size != 1 -> throw IppException("Expected 1 additional value, not ${attribute.values.size}") + attribute.tag != tag -> log.warning { "$name: ignore additional value \"$attribute\" - tag is not '$tag'" } + else -> { + attribute.tag.validateValueClass(attribute.values.single() as Any) + values.add(attribute.values.single() as T) + } } } - internal fun validateValueClass(value: Any) { - if (!tag.valueHasValidClass(value)) throw IppException("value class ${value::class.java.name} not valid for tag $tag") - } - fun is1setOf() = values.size > 1 || attributeIs1setOf(name) == true fun isCollection() = tag == BegCollection - override fun buildIppAttribute(printerAttributes: IppAttributesGroup): IppAttribute<*> = this + override fun buildIppAttribute(printerAttributes: IppAttributesGroup) = this - override fun toString(): String { - val tagString = "${if (is1setOf()) "1setOf " else ""}$tag" - val valuesString = if (values.isEmpty()) "no-value" else values.joinToString(",") { valueToString(it) } - return "$name ($tagString) = $valuesString" + override fun toString() = StringBuilder(name).run { + append(" (${if (is1setOf()) "1setOf " else ""}$tag) = ") + append(if (values.isEmpty()) "no-value" else values.joinToString(",") { valueToString(it) }) + toString() } internal fun valueToString(value: T) = when { diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index 7b15c1ee..2e4815f3 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -5,30 +5,33 @@ package de.gmuth.ipp.core */ import java.io.File +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger import java.util.logging.Logger.getLogger -open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap>() { +class IppAttributesGroup(val tag: IppTag) : LinkedHashMap>() { private val log = getLogger(javaClass.name) + var onReplaceWarn: Boolean = false init { if (!tag.isGroupTag()) throw IppException("'$tag' is not a group tag") } - var onReplaceWarn: Boolean = false - - open fun put(attribute: IppAttribute<*>) = - put(attribute.name, attribute).also { - if (it != null && onReplaceWarn) log.warning { "replaced '$it' with '${attribute.values.joinToString(",")}' in group $tag" } + fun put(attribute: IppAttribute<*>) = put(attribute.name, attribute).also { + if (it != null && onReplaceWarn) { + log.warning { "replaced '$it' with '${attribute.values.joinToString(",")}' in group $tag" } } + } fun attribute(name: String, tag: IppTag, vararg values: Any) = put(IppAttribute(name, tag, values.toList())) - fun attribute(name: String, tag: IppTag, values: List) = + fun attribute(name: String, tag: IppTag, values: Collection) = put(IppAttribute(name, tag, values)) @Suppress("UNCHECKED_CAST") @@ -41,23 +44,23 @@ open class IppAttributesGroup(val tag: IppTag) : LinkedHashMap getValue(name: String) = - get(name)?.value as T ?: throw IppException("attribute '$name' not found in group $tag") + get(name)?.value as T ?: throw IppException("Attribute '$name' not found in group $tag") @Suppress("UNCHECKED_CAST") fun getValues(name: String) = - get(name)?.values as T ?: throw IppException("attribute '$name' not found in group $tag") + get(name)?.values as T ?: throw IppException("Attribute '$name' not found in group $tag") fun getTextValue(name: String) = getValue(name).text - fun put(attributesGroup: IppAttributesGroup) { - log.fine { "put ${attributesGroup.size} attributes" } + fun getTimeValue(name: String, zoneId: ZoneId = ZoneId.systemDefault()): ZonedDateTime = + Instant.ofEpochSecond(getValue(name).toLong()).atZone(zoneId) + + fun put(attributesGroup: IppAttributesGroup) = attributesGroup.values.forEach { put(it) } - } override fun toString() = "'$tag' $size attributes" - @JvmOverloads fun log(logger: Logger, level: Level = INFO, prefix: String = "", title: String = "$tag") { logger.log(level) { "${prefix}$title" } keys.forEach { logger.log(level) { "$prefix ${get(it)}" } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt index cb920df8..2f9fd1d4 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppCollection.kt @@ -1,13 +1,13 @@ package de.gmuth.ipp.core +/** + * Copyright (c) 2020-2023 Gerhard Muth + */ + import java.util.logging.Level import java.util.logging.Level.INFO import java.util.logging.Logger -/** - * Copyright (c) 2020-2022 Gerhard Muth - */ - // RFC8010 3.1.6. data class IppCollection(val members: MutableCollection> = mutableListOf()) { diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index a87a401d..feb5b978 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -4,11 +4,8 @@ package de.gmuth.ipp.core * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.io.hexdump import de.gmuth.ipp.core.IppTag.* -import java.io.BufferedInputStream -import java.io.DataInputStream -import java.io.EOFException +import java.io.* import java.net.URI import java.nio.charset.Charset import java.util.logging.Level @@ -24,13 +21,13 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt fun readMessage(message: IppMessage) { with(message) { version = "${readUnsignedByte()}.${readUnsignedByte()}" - log.fine { "version = $version" } + log.finest { "version = $version" } - code = readShort() - log.fine { "code = $code ($codeDescription)" } + code = readUnsignedShort() + log.finest { "code = $code ($codeDescription)" } requestId = readInt() - log.fine { "requestId = $requestId" } + log.finest { "requestId = $requestId" } } lateinit var currentGroup: IppAttributesGroup @@ -42,14 +39,14 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt tag.isGroupTag() -> { currentGroup = message.createAttributesGroup(tag) } + tag.isValueTag() -> { val attribute = readAttribute(tag) + log.finest { attribute.toString() } if (attribute.name.isNotEmpty()) { - log.fine { attribute.toString() } currentGroup.put(attribute) currentAttribute = attribute } else { // name.isEmpty() -> 1setOf - log.fine { IppAttribute(currentAttribute.name, attribute.tag, attribute.value).toString() } currentAttribute.additionalValue(attribute) } } @@ -58,29 +55,26 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt } catch (throwable: Throwable) { if (throwable !is EOFException) readBytes().apply { if (isNotEmpty()) { - log.warning { "skipped $size unparsed bytes" } - hexdump { log.warning { it } } + log.warning { "Skipped $size unparsed bytes" } + hexdump { log.finest { it } } } } - throw throwable + throw IppException("Failed to read ipp message", throwable) } } - internal fun readTag() = - IppTag.fromByte(readByte()).apply { - if (isDelimiterTag()) log.fine { "--- $this ---" } - } + internal fun readTag() = IppTag.fromByte(readByte()).apply { + if (isDelimiterTag()) log.finest { "--- $this ---" } + } - internal fun readAttribute(tag: IppTag): IppAttribute { - val name = readString() - val value = try { - readAttributeValue(tag) + internal fun readAttribute(tag: IppTag) = IppAttribute(name = readString(), tag).apply { + try { + values.add(readAttributeValue(tag)) } catch (throwable: Throwable) { - throw IppException("failed to read attribute value of '$name' ($tag)", throwable) + throw IppException("Failed to read attribute value for '$name' ($tag)", throwable) } // remember attributes-charset for name and text value decoding if (name == "attributes-charset") attributesCharset = value as Charset - return IppAttribute(name, tag, value) } internal fun readAttributeValue(tag: IppTag): Any = @@ -114,9 +108,13 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt ) } - Charset -> Charset.forName(readString()) + Charset -> { + Charset.forName(readString()) + } - Uri -> URI.create(readString().replace(" ", "%20")) + Uri -> { + URI.create(readString().replace(" ", "%20")) + } // String with rfc 8011 3.9 and rfc 8011 4.1.4.1 attribute value encoding Keyword, @@ -124,10 +122,14 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt OctetString, MimeMediaType, MemberAttrName, - NaturalLanguage -> readString() + NaturalLanguage -> { + readString() + } TextWithoutLanguage, - NameWithoutLanguage -> IppString(readString(attributesCharset)) + NameWithoutLanguage -> { + IppString(readString(attributesCharset)) + } TextWithLanguage, NameWithLanguage -> { @@ -160,7 +162,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt readCollection() } else { // Xerox B210: workaround for invalid 'media-col' without members - log.warning { "invalid value length for IppCollection, trying to recover" } + log.warning { "Invalid value length for IppCollection, trying to recover" } IppCollection() } } @@ -169,8 +171,8 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt else -> { // ByteArray - possibly empty readLengthAndValue().apply { if (isNotEmpty()) { - val level = if (tag == Unsupported_) Level.FINE else Level.WARNING - log.log(level) { "ignore $size value bytes tagged '$tag'" } + val level = if (tag == Unsupported_) Level.FINEST else Level.WARNING + log.log(level) { "Ignore $size value bytes tagged '$tag'" } hexdump { log.log(level) { it } } } } @@ -201,7 +203,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt String(readLengthAndValue(), charset) internal fun readLengthAndValue() = - readBytes(readShort().toInt()) + readBytes(readUnsignedShort()) // avoid readNBytes(length) for compatibility with JREs < 11 internal fun readBytes(length: Int) = ByteArray(length).apply { @@ -210,15 +212,31 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt internal fun readExpectedValueLength(expected: Int, throwException: Boolean = true): Boolean { mark(2) - val length = readShort().toInt() + val length = readUnsignedShort() return (length == expected).apply { if (!this) { // unexpected value length reset() // revert 'readShort()' - with("expected value length of $expected bytes but found $length") { + with("Expected value length of $expected bytes but found $length") { if (throwException) throw IppException(this) else log.warning { this } } } } } + fun ByteArray.hexdump(maxRows: Int = 32, dump: (String) -> Unit) { + val hexStringBuilder = StringBuilder() + val charStringBuilder = StringBuilder() + fun dumpLine() = dump("%-${maxRows * 3}s '%s'".format(hexStringBuilder, charStringBuilder)) + for ((index, b) in withIndex()) { + hexStringBuilder.append("%02X ".format(b)) + charStringBuilder.append(b.toInt().toChar()) + if ((index + 1) % maxRows == 0) { + dumpLine() + hexStringBuilder.clear() + charStringBuilder.clear() + } + } + if (isNotEmpty()) dumpLine() + } + } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index 99675081..22d7c019 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -4,8 +4,6 @@ package de.gmuth.ipp.core * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.io.ByteArraySavingInputStream -import de.gmuth.io.ByteArraySavingOutputStream import de.gmuth.ipp.core.IppTag.* import java.io.* import java.util.logging.Level @@ -16,7 +14,7 @@ import java.util.logging.Logger.getLogger abstract class IppMessage() { private val log = getLogger(javaClass.name) - var code: Short? = null + var code: Int? = null // unsigned short (16 bits) var requestId: Int? = null var version: String? = null set(value) { // validate version @@ -67,15 +65,17 @@ abstract class IppMessage() { // -------- fun write(outputStream: OutputStream) { - val byteArraySavingOutputStream = ByteArraySavingOutputStream(outputStream) + val byteArrayOutputStream = ByteArrayOutputStream() + val byteArraySavingOutputStream = object : OutputStream() { + override fun write(byte: Int) = outputStream.write(byte) + .also { byteArrayOutputStream.write(byte) } + } try { IppOutputStream(byteArraySavingOutputStream).writeMessage(this) } finally { - rawBytes = byteArraySavingOutputStream.toByteArray() - log.fine { "wrote raw ipp message: ${rawBytes!!.size} bytes" } - byteArraySavingOutputStream.saveBytes = false // stop saving document bytes + rawBytes = byteArrayOutputStream.toByteArray() } - if (hasDocument()) copyDocumentStream(byteArraySavingOutputStream) + if (hasDocument()) copyDocumentStream(outputStream) } fun write(file: File) = @@ -91,24 +91,27 @@ abstract class IppMessage() { // -------- fun read(inputStream: InputStream) { - val byteArraySavingInputStream = ByteArraySavingInputStream(inputStream) + val byteArrayOutputStream = ByteArrayOutputStream() + val byteArraySavingInputStream = object : InputStream() { + override fun read() = inputStream.read() + .also { if (it != -1) byteArrayOutputStream.write(it) } + } val bufferedInputStream = byteArraySavingInputStream.buffered() try { IppInputStream(bufferedInputStream).readMessage(this) documentInputStream = bufferedInputStream } finally { - rawBytes = byteArraySavingInputStream.toByteArray() - log.fine { "read ${rawBytes!!.size} raw bytes" } + rawBytes = byteArrayOutputStream.toByteArray() } } fun read(file: File) { - log.fine { "read file ${file.absolutePath}: ${file.length()} bytes" } + log.fine { "Read file ${file.absolutePath}: ${file.length()} bytes" } read(FileInputStream(file)) } fun decode(byteArray: ByteArray) { - log.fine { "decode ${byteArray.size} bytes" } + log.fine { "Decode ${byteArray.size} bytes" } read(ByteArrayInputStream(byteArray)) } @@ -134,7 +137,7 @@ abstract class IppMessage() { throw IppException("No raw bytes to save. You must call read/decode or write/encode before.") } else { file.writeBytes(rawBytes!!) - log.info { "saved ${file.path} (${file.length()} bytes)" } + log.info { "Saved ${file.path} (${file.length()} bytes)" } } // ------- diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt b/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt index a1b86f33..d3969aa9 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppOperation.kt @@ -6,7 +6,7 @@ package de.gmuth.ipp.core // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-6 -enum class IppOperation(val code: Short) { +enum class IppOperation(val code: Int) { // RFC 8011 PrintJob(0x0002), @@ -140,7 +140,7 @@ enum class IppOperation(val code: Short) { .replace(Regex("^-"), "") companion object { - fun fromShort(code: Short): IppOperation = + fun fromInt(code: Int): IppOperation = values().find { it.code == code } ?: throw IppException("Unknown operation code %04x".format(code)) } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt index c2681b6b..4094819a 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppOutputStream.kt @@ -15,20 +15,20 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea private val log = getLogger(javaClass.name) - // charset for text and name attributes, rfc 8011 4.1.4.1 + // Charset for text and name attributes, RFC 8011 4.1.4.1 internal lateinit var attributesCharset: Charset fun writeMessage(message: IppMessage) = message.run { attributesCharset = operationGroup.getValue("attributes-charset") writeVersion(version ?: throw IppException("missing version")) - log.fine { "version = $version" } + log.finest { "version = $version" } - writeShort(code?.toInt() ?: throw IppException("missing operation or status code")) - log.fine { "code = $code ($codeDescription)" } + writeShort(code ?: throw IppException("missing operation or status code")) + log.finest { "code = $code ($codeDescription)" } writeInt(requestId ?: throw IppException("missing requestId")) - log.fine { "requestId = $requestId" } + log.finest { "requestId = $requestId" } for (group in attributesGroups) { writeTag(group.tag) @@ -51,7 +51,7 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea } internal fun writeTag(tag: IppTag) { - if (tag.isDelimiterTag()) log.fine { "--- $tag ---" } + if (tag.isDelimiterTag()) log.finest { "--- $tag ---" } writeByte(tag.code.toInt()) } @@ -63,7 +63,7 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea } internal fun writeAttribute(attribute: IppAttribute<*>) { - log.fine { "$attribute" } + log.finest { "$attribute" } with(attribute) { if (values.isEmpty() || tag.isOutOfBandTag()) { writeTag(tag) @@ -128,12 +128,12 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea NameWithoutLanguage -> when (value) { is String -> writeString(value, attributesCharset) is IppString -> writeString(value.text, attributesCharset) - else -> throw IppException("expected value of type String or IppString") + else -> throw IppException("expecting value class String or IppString") } TextWithLanguage, NameWithLanguage -> with(value as IppString) { - if (language == null) throw IppException("expected IppString with language") + if (language == null) throw IppException("expecting IppString with language") writeShort(4 + text.length + language.length) writeString(language, attributesCharset) writeString(text, attributesCharset) @@ -164,7 +164,7 @@ class IppOutputStream(outputStream: OutputStream) : DataOutputStream(outputStrea writeAttribute(IppAttribute("", EndCollection)) } - else -> throw IppException("unknown tag 0x%02X %s".format(tag.code, tag)) + else -> throw IppException("Unknown tag 0x%02X %s".format(tag.code, tag)) } } } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt index aaad717e..e24eb7ef 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppRequest.kt @@ -18,7 +18,7 @@ class IppRequest : IppMessage { get() = operation.toString() val operation: IppOperation - get() = IppOperation.fromShort(code!!) + get() = IppOperation.fromInt(code!!) val attributesCharset: Charset get() = operationGroup.getValue("attributes-charset") @@ -32,7 +32,7 @@ class IppRequest : IppMessage { operation: IppOperation, printerUri: URI? = null, jobId: Int? = null, - requestedAttributes: List? = null, + requestedAttributes: Collection? = null, requestingUserName: String? = null, version: String = "2.0", requestId: Int = 1, diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt index eeb842cd..bd49c64b 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppResponse.kt @@ -14,7 +14,7 @@ class IppResponse : IppMessage { get() = status.toString() var status: IppStatus - get() = IppStatus.fromShort(code!!) + get() = IppStatus.fromInt(code!!) set(ippStatus) { code = ippStatus.code } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt b/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt index f0122759..7923461c 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppStatus.kt @@ -7,7 +7,7 @@ package de.gmuth.ipp.core // https://www.rfc-editor.org/rfc/rfc8011.html#appendix-B // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-11 -enum class IppStatus(val code: Short) { +enum class IppStatus(val code: Int) { SuccessfulOk(0x0000), SuccessfulOkIgnoredOrSubstitutedAttributes(0x0001), @@ -71,7 +71,7 @@ enum class IppStatus(val code: Short) { .replace(Regex("^-"), "") companion object { - fun fromShort(code: Short): IppStatus = + fun fromInt(code: Int): IppStatus = values().find { it.code == code } ?: throw IppException("Unknown status code %04x".format(code)) } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt index a6664bce..4f6d7ecc 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppTag.kt @@ -76,6 +76,10 @@ enum class IppTag( else -> registeredName } + fun validateValueClass(value: Any) { + if (!valueHasValidClass(value)) throw IppException("Value class ${value::class.java.name} not valid for tag $this") + } + companion object { fun fromByte(code: Byte): IppTag = values().singleOrNull { it.code == code } ?: throw IppException("Unknown tag 0x%02X".format(code)) diff --git a/src/main/kotlin/de/gmuth/csv/CSVTable.kt b/src/main/kotlin/de/gmuth/ipp/iana/CSVTable.kt similarity index 98% rename from src/main/kotlin/de/gmuth/csv/CSVTable.kt rename to src/main/kotlin/de/gmuth/ipp/iana/CSVTable.kt index 65ef6116..039b5b23 100644 --- a/src/main/kotlin/de/gmuth/csv/CSVTable.kt +++ b/src/main/kotlin/de/gmuth/ipp/iana/CSVTable.kt @@ -1,7 +1,7 @@ -package de.gmuth.csv +package de.gmuth.ipp.iana /** - * Copyright (c) 2021 Gerhard Muth + * Copyright (c) 2021-2023 Gerhard Muth */ import java.io.InputStream diff --git a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt index b3570661..97468430 100644 --- a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt +++ b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection2.kt @@ -4,7 +4,6 @@ package de.gmuth.ipp.iana * Copyright (c) 2020-2023 Gerhard Muth */ -import de.gmuth.csv.CSVTable import de.gmuth.ipp.core.IppAttribute import de.gmuth.ipp.core.IppCollection import de.gmuth.ipp.core.IppMessage @@ -12,9 +11,7 @@ import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.* import java.util.logging.Logger.getLogger -/** - * https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-2 - */ +// https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xml#ipp-registrations-2 object IppRegistrationsSection2 { val log = getLogger(javaClass.name) @@ -71,33 +68,38 @@ object IppRegistrationsSection2 { fun collectionGroupTag() = when (collection) { "Operation" -> Operation "Job Template" -> Job - else -> error("no IppTag defined for $collection") + else -> error("No IppTag defined for $collection") } } - // source: https://www.iana.org/assignments/ipp-registrations/ipp-registrations-2.csv - val allAttributes = CSVTable("/ipp-registrations-2.csv", ::Attribute).rows - - val attributesMap = allAttributes.associateBy(Attribute::key) - - // alias example: Printer Description,media-col-default,"" - val aliasMap = mutableMapOf().apply { - allAttributes - .filter { it.memberAttribute.lowercase().contains("same as") } - .forEach { put(it.name, it.memberAttribute.replace("^.*\"(.+)\".*$".toRegex(), "$1")) } - // apple cups extension 'output-mode' was standardized to 'print-color-mode' - put("output-mode-default", "print-color-mode-default") - put("output-mode-supported", "print-color-mode-supported") - // 'media-col-default' resolves to 'media-col' and 'media-source-feed-...' values are registered for 'media-col-ready' - put( - "media-col/media-source-properties/media-source-feed-direction", - "media-col-ready/media-source-properties/media-source-feed-direction" - ) - put( - "media-col/media-source-properties/media-source-feed-orientation", - "media-col-ready/media-source-properties/media-source-feed-orientation" - ) + private val attributesMap: Map + private val aliasMap: Map + + init { + // source: https://www.iana.org/assignments/ipp-registrations/ipp-registrations-2.csv + val allAttributes = CSVTable("/ipp-registrations-2.csv", ::Attribute).rows + + attributesMap = allAttributes.associateBy(Attribute::key) + + // alias example: Printer Description,media-col-default,"" + aliasMap = mutableMapOf().apply { + allAttributes + .filter { it.memberAttribute.lowercase().contains("same as") } + .forEach { put(it.name, it.memberAttribute.replace("^.*\"(.+)\".*$".toRegex(), "$1")) } + // apple cups extension 'output-mode' was standardized to 'print-color-mode' + put("output-mode-default", "print-color-mode-default") + put("output-mode-supported", "print-color-mode-supported") + // 'media-col-default' resolves to 'media-col' and 'media-source-feed-...' values are registered for 'media-col-ready' + put( + "media-col/media-source-properties/media-source-feed-direction", + "media-col-ready/media-source-properties/media-source-feed-direction" + ) + put( + "media-col/media-source-properties/media-source-feed-orientation", + "media-col-ready/media-source-properties/media-source-feed-orientation" + ) + } } fun resolveAlias(name: String) = (aliasMap[name] ?: name).also { diff --git a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt index a74bb26f..4cc442f1 100644 --- a/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt +++ b/src/main/kotlin/de/gmuth/ipp/iana/IppRegistrationsSection6.kt @@ -1,53 +1,56 @@ package de.gmuth.ipp.iana -import de.gmuth.csv.CSVTable -import java.util.logging.Logger.getLogger - /** - * https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml#ipp-registrations-6 + * Copyright (c) 2020-2023 Gerhard Muth */ + +// https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml#ipp-registrations-6 object IppRegistrationsSection6 { data class EnumAttributeValue( - val attribute: String, - val value: String, - val name: String, - val syntax: String, - val reference: String + val attribute: String, + val value: String, + val name: String, + val syntax: String, + val reference: String ) { constructor(columns: List) : this( - attribute = columns[0], - value = columns[1], - name = columns[2], - syntax = columns[3], - reference = columns[4] + attribute = columns[0], + value = columns[1], + name = columns[2], + syntax = columns[3], + reference = columns[4] ) override fun toString() = "$attribute/$value ($syntax) = $name $reference " } - val log = getLogger(javaClass.name) + private val enumAttributeValuesMap: Map + private val aliasMap: Map - // source: https://www.iana.org/assignments/ipp-registrations/ipp-registrations-6.csv - val allEnumAttributeValues = CSVTable("/ipp-registrations-6.csv", ::EnumAttributeValue).rows + init { + // source: https://www.iana.org/assignments/ipp-registrations/ipp-registrations-6.csv + val allEnumAttributeValues = CSVTable("/ipp-registrations-6.csv", ::EnumAttributeValue).rows - val enumAttributeValuesMap = allEnumAttributeValues.associateBy { "${it.attribute}/${it.value}" } + enumAttributeValuesMap = allEnumAttributeValues.associateBy { "${it.attribute}/${it.value}" } - // alias example: finishings-default, - val aliasMap = mutableMapOf().apply { - allEnumAttributeValues + // alias example: finishings-default, + aliasMap = mutableMapOf().apply { + allEnumAttributeValues .filter { it.value.lowercase().contains("any") } .forEach { put(it.attribute, it.value.replace("^.*\"(.+)\".*$".toRegex(), "$1")) } + } } - fun getEnumAttributeValue(attribute: String, value: Any) = enumAttributeValuesMap["$attribute/$value"] + fun getEnumAttributeValue(attribute: String, value: Any) = + enumAttributeValuesMap["$attribute/$value"] fun getEnumName(attribute: String, value: Any) = - if (attribute == "operations-supported" && value is Number) { - // lookup the name in IppOperation because CUPS operations are not iana registered - de.gmuth.ipp.core.IppOperation.fromShort(value.toShort()).toString() - } else { - getEnumAttributeValue(aliasMap[attribute] ?: attribute, value)?.name - } ?: value + if (attribute == "operations-supported" && value is Number) { + // lookup the name in IppOperation because CUPS operations are not iana registered + de.gmuth.ipp.core.IppOperation.fromInt(value.toInt()).registeredName() + } else { + getEnumAttributeValue(aliasMap[attribute] ?: attribute, value)?.name + } ?: value } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/io/ByteArraySavingInputStream.kt b/src/test/kotlin/de/gmuth/io/ByteArraySavingInputStream.kt similarity index 100% rename from src/main/kotlin/de/gmuth/io/ByteArraySavingInputStream.kt rename to src/test/kotlin/de/gmuth/io/ByteArraySavingInputStream.kt diff --git a/src/main/kotlin/de/gmuth/io/ByteArraySavingOutputStream.kt b/src/test/kotlin/de/gmuth/io/ByteArraySavingOutputStream.kt similarity index 100% rename from src/main/kotlin/de/gmuth/io/ByteArraySavingOutputStream.kt rename to src/test/kotlin/de/gmuth/io/ByteArraySavingOutputStream.kt diff --git a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt index 04409dfe..4b1a8dc2 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt @@ -4,6 +4,7 @@ package de.gmuth.ipp.client * Copyright (c) 2020-2023 Gerhard Muth */ +import de.gmuth.ipp.attributes.PrinterState import de.gmuth.ipp.core.IppException import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound import org.junit.Test @@ -39,7 +40,7 @@ class CupsClientTests { cupsClient.getPrinter("ColorJet_HP").run { log(log) assertEquals("HP LaserJet 100 color MFP M175", makeAndModel.text) - assertEquals(IppPrinterState.Idle, state) + assertEquals(PrinterState.Idle, state) assertEquals(5, markers.size) assertTrue(isAcceptingJobs) assertTrue(isCups()) diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt b/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt index 15ac8b68..4c08b451 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppClientMock.kt @@ -4,12 +4,13 @@ package de.gmuth.ipp.client * Copyright (c) 2023 Gerhard Muth */ -import de.gmuth.io.ByteArray import de.gmuth.ipp.core.IppRequest import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus import de.gmuth.log.Logging +import java.io.ByteArrayOutputStream import java.io.File +import java.io.OutputStream import java.net.URI class IppClientMock( @@ -40,12 +41,11 @@ class IppClientMock( // changes to an attribute group would affect other tests as well // therefor it's important to produce a fresh response for each call - override fun postRequest(httpUri: URI, request: IppRequest): IppResponse { - ByteArray { request.write(it) }.run { - log.info { "mocked post $size IPP bytes to $httpUri" } - } - return IppResponse().apply { - decode(rawResponse) - } + override fun httpPost(httpUri: URI, request: IppRequest) = IppResponse().apply { + ByteArrayOutputStream() + .also { request.write(it) } + .toByteArray() + .run { log.info { "mocked post $size IPP bytes to $httpUri" } } + decode(rawResponse) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt index 28d32fcb..4be4c81c 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppJobTests.kt @@ -4,6 +4,8 @@ package de.gmuth.ipp.client * Copyright (c) 2021-2023 Gerhard Muth */ +import de.gmuth.ipp.attributes.JobState +import de.gmuth.ipp.attributes.PrinterState import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppStatus.SuccessfulOk import de.gmuth.ipp.core.IppString @@ -63,9 +65,10 @@ class IppJobTests { @Test fun updateAttributes() { job.apply { + attributes.onReplaceWarn = true updateAttributes() log(log) - assertEquals(31, attributes.size) + assertEquals(32, attributes.size) } } @@ -97,7 +100,7 @@ class IppJobTests { @Test fun isProcessing() { job.apply { - attributes.attribute("job-state", IppTag.Enum, IppJobState.Processing.code) + attributes.attribute("job-state", IppTag.Enum, JobState.Processing.code) assertTrue(isProcessing()) assertFalse(isTerminated()) } @@ -106,7 +109,7 @@ class IppJobTests { @Test fun isProcessingStopped() { job.apply { - attributes.attribute("job-state", IppTag.Enum, IppJobState.ProcessingStopped.code) + attributes.attribute("job-state", IppTag.Enum, JobState.ProcessingStopped.code) assertTrue(isProcessingStopped()) } } @@ -153,7 +156,7 @@ class IppJobTests { @Test fun printerState() { - assertEquals(IppPrinterState.Idle, job.printer.state) + assertEquals(PrinterState.Idle, job.printer.state) } fun cupsDocumentResponse(format: String) = IppResponse(SuccessfulOk).apply { diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppMediaTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppMediaTests.kt index d1d6fd66..d770852c 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppMediaTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppMediaTests.kt @@ -4,6 +4,9 @@ package de.gmuth.ipp.client * Copyright (c) 2021 Gerhard Muth */ +import de.gmuth.ipp.attributes.MediaCollection +import de.gmuth.ipp.attributes.MediaMargin +import de.gmuth.ipp.attributes.MediaSource import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppResponse import de.gmuth.ipp.core.IppTag @@ -14,13 +17,13 @@ class IppMediaTests { @Test fun defaultConstructor() { - IppMedia.Collection().buildIppAttribute(IppAttributesGroup(IppTag.Printer)) + MediaCollection().buildIppAttribute(IppAttributesGroup(IppTag.Printer)) } @Test - fun margins() { - IppMedia.Margins() - IppMedia.Margins(0) + fun margin() { + MediaMargin() + MediaMargin(0) } @Test @@ -28,12 +31,12 @@ class IppMediaTests { val attributes = IppResponse().apply { read(File("printers/Simulated_Laser_Printer/Get-Printer-Attributes.ipp")) }.printerGroup - IppMedia.Collection(source = "invalid").buildIppAttribute(attributes) + MediaCollection(source = MediaSource("invalid")).buildIppAttribute(attributes) } @Test fun notProvided() { - IppMedia.Collection(source = "main").buildIppAttribute(IppAttributesGroup(IppTag.Printer)) + MediaCollection(source = MediaSource("main")).buildIppAttribute(IppAttributesGroup(IppTag.Printer)) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt index b2f056dc..0bf43e9a 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppPrinterTests.kt @@ -4,20 +4,20 @@ package de.gmuth.ipp.client * Copyright (c) 2021-2023 Gerhard Muth */ -import de.gmuth.ipp.client.CupsPrinterType.Capability.CanPunchOutput -import de.gmuth.ipp.client.IppFinishing.Punch -import de.gmuth.ipp.client.IppFinishing.Staple -import de.gmuth.ipp.client.IppTemplateAttributes.copies -import de.gmuth.ipp.client.IppTemplateAttributes.documentFormat -import de.gmuth.ipp.client.IppTemplateAttributes.finishings -import de.gmuth.ipp.client.IppTemplateAttributes.jobName -import de.gmuth.ipp.client.IppTemplateAttributes.jobPriority -import de.gmuth.ipp.client.IppTemplateAttributes.media -import de.gmuth.ipp.client.IppTemplateAttributes.numberUp -import de.gmuth.ipp.client.IppTemplateAttributes.pageRanges -import de.gmuth.ipp.client.IppTemplateAttributes.printerResolution -import de.gmuth.ipp.client.IppWhichJobs.Completed +import de.gmuth.ipp.attributes.* +import de.gmuth.ipp.attributes.Finishing.Punch +import de.gmuth.ipp.attributes.Finishing.Staple +import de.gmuth.ipp.attributes.PrinterType.Capability.CanPunchOutput +import de.gmuth.ipp.attributes.TemplateAttributes.copies +import de.gmuth.ipp.attributes.TemplateAttributes.finishings +import de.gmuth.ipp.attributes.TemplateAttributes.jobName +import de.gmuth.ipp.attributes.TemplateAttributes.jobPriority +import de.gmuth.ipp.attributes.TemplateAttributes.numberUp +import de.gmuth.ipp.attributes.TemplateAttributes.pageRanges +import de.gmuth.ipp.attributes.TemplateAttributes.printerResolution +import de.gmuth.ipp.client.WhichJobs.Completed import de.gmuth.ipp.core.IppOperation.GetPrinterAttributes +import de.gmuth.ipp.attributes.Marker import java.io.File import java.io.FileInputStream import java.net.URI @@ -31,7 +31,7 @@ import kotlin.test.assertTrue class IppPrinterTests { - val tlog = getLogger(javaClass.name) + val log = getLogger(javaClass.name) val blankPdf = File("tool/A4-blank.pdf") val ippClientMock = IppClientMock("printers/Simulated_Laser_Printer") val printer = IppPrinter( @@ -42,7 +42,7 @@ class IppPrinterTests { @Test fun printerAttributes() { printer.run { - tlog.info { toString() } + log.info { toString() } assertTrue(isAcceptingJobs) assertTrue(documentFormatSupported.contains("application/pdf")) assertTrue(supportsOperations(GetPrinterAttributes)) @@ -55,7 +55,7 @@ class IppPrinterTests { assertTrue(supportsVersion("1.1")) assertEquals(URI.create("urf:///20"), deviceUri) assertTrue(hasCapability(CanPunchOutput)) - marker(CupsMarker.Color.BLACK).apply { + marker(Marker.Color.BLACK).apply { assertEquals(80, level) assertEquals(10, lowLevel) assertEquals(100, highLevel) @@ -72,13 +72,13 @@ class IppPrinterTests { assertFalse(isMediaNeeded()) assertFalse(isCups()) printerType.apply { - tlog.info { toString() } - log(tlog) + log.info { toString() } + log(log) } communicationChannelsSupported.forEach { - tlog.info { "${it.uri}, ${it.security}, ${it.authentication}, $it" } + log.info { "${it.uri}, ${it.security}, ${it.authentication}, $it" } } - ippConfig.log(tlog) + ippConfig.log(log) } } @@ -93,7 +93,7 @@ class IppPrinterTests { ippClientMock.mockResponse("Get-Printer-Attributes.ipp") printer.run { updateAttributes() - log(tlog) + log(log) assertEquals(122, attributes.size) } } @@ -101,8 +101,8 @@ class IppPrinterTests { @Test fun validateJob() { printer.validateJob( - documentFormat("application/pdf"), - media("iso_a4_210x297mm"), + DocumentFormat.PDF, + Media.ISO_A4 ) } @@ -112,27 +112,27 @@ class IppPrinterTests { printer.printJob( File("tool/A4-blank.pdf"), jobName("A4.pdf"), - documentFormat("application/pdf"), - media("iso_a4_210x297mm"), + DocumentFormat("application/pdf"), + Media("iso_a4_210x297mm"), jobPriority(30), copies(1), numberUp(1), pageRanges(1..5), printerResolution(600), - IppOrientationRequested.Portrait, - IppColorMode.Monochrome, - IppSides.TwoSidedLongEdge, - IppPrintQuality.High, + OrientationRequested.Portrait, + ColorMode.Monochrome, + Sides.TwoSidedLongEdge, + PrintQuality.High, finishings(Staple, Punch), - IppMedia.Collection( - size = IppMedia.Size(20, 30), - margins = IppMedia.Margins(10, 10, 10, 10), - source = "main", + MediaCollection( + size = MediaSize(20, 30), + margin = MediaMargin(10, 10, 10, 10), + source = MediaSource("main"), type = "stationery" ) ).apply { assertEquals(461881017, id) - assertEquals(IppJobState.Pending, state) + assertEquals(JobState.Pending, state) assertEquals("pending", state.toString()) assertTrue(stateReasons.contains("none")) } @@ -142,9 +142,9 @@ class IppPrinterTests { fun printJobInputStream() { ippClientMock.mockResponse("Print-Job.ipp") printer.printJob(FileInputStream(blankPdf)).run { - log(tlog) + log(log) assertEquals(461881017, id) - assertEquals(IppJobState.Pending, state) + assertEquals(JobState.Pending, state) assertEquals(listOf("none"), stateReasons) assertEquals("ipp://SpaceBook-2.local.:8632/jobs/461881017", uri.toString()) } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt index d8cae28a..3ee2874c 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt @@ -25,9 +25,8 @@ class IppAttributeTests { } @Test - fun valueIsSet() { - // coverage for warning - assertEquals("none", ippAttribute.value) + fun accessingSetAsValueFails() { + assertFailsWith { ippAttribute.value } } @Test @@ -118,4 +117,9 @@ class IppAttributeTests { assertFalse(IppAttribute("some-integer", Integer, 0).isCollection()) } + @Test + fun attributeToString() { + assertEquals("foo (1setOf integer) = 1,2,3", IppAttribute("foo", Integer, 1, 2, 3).toString()) + } + } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt index b4fa17cb..e316c03b 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppOperationsTests.kt @@ -12,7 +12,7 @@ class IppOperationsTests { @Test fun unknownOperationCodeFails() { assertFailsWith { - IppOperation.fromShort(0) + IppOperation.fromInt(0) } } diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt index 6056ca7f..8da7ed55 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppStatusTests.kt @@ -28,12 +28,12 @@ class IppStatusTests { @Test fun fromShort() { - assertEquals(IppStatus.ClientErrorDocumentFormatNotSupported, IppStatus.fromShort(0x40A)) + assertEquals(IppStatus.ClientErrorDocumentFormatNotSupported, IppStatus.fromInt(0x40A)) } @Test fun fromShortFails() { - assertFailsWith { IppStatus.fromShort(10) } + assertFailsWith { IppStatus.fromInt(10) } } } \ No newline at end of file diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties index f587ff64..3d1345d3 100644 --- a/src/test/resources/logging.properties +++ b/src/test/resources/logging.properties @@ -1,10 +1,13 @@ # ------ levels ------ .level=INFO -de.gmuth.level=ALL +de.gmuth.level=INFO #de.gmuth.ipp.core.level=INFO -#de.gmuth.ipp.client.level=INFO -sun.net.www.protocol.level=FINEST +#de.gmuth.ipp.client.level=FINE +#de.gmuth.ipp.client.IppClient.level=FINE +#de.gmuth.ipp.client.IppPrinter.level=FINE +#de.gmuth.ipp.client.IppJob.level=FINE +#sun.net.www.protocol.level=FINEST # ------- formatters ------- @@ -31,9 +34,9 @@ handlers=de.gmuth.log.StdoutHandler,java.util.logging.FileHandler #de.gmuth.log.StdoutHandler #de.gmuth.log.ConsoleHandler -de.gmuth.log.StdoutHandler.level=INFO +de.gmuth.log.StdoutHandler.level=FINER #java.util.logging.ConsoleHandler.level=ALL -java.util.logging.FileHandler.level=FINE +java.util.logging.FileHandler.level=FINER java.util.logging.FileHandler.append=false java.util.logging.FileHandler.pattern=ipp-client.log \ No newline at end of file From d1ba9fd8a51f4da8a9bcdd355ab4867edadca57a Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 5 Oct 2023 00:07:22 +0200 Subject: [PATCH 46/47] fix sonar issues --- src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt | 1 - src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt | 1 - src/main/kotlin/de/gmuth/ipp/client/IppJob.kt | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt b/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt index fe3c9cfe..f06e160d 100644 --- a/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt +++ b/src/main/kotlin/de/gmuth/ipp/attributes/DocumentFormat.kt @@ -7,7 +7,6 @@ package de.gmuth.ipp.attributes import de.gmuth.ipp.core.IppAttribute import de.gmuth.ipp.core.IppAttributeBuilder import de.gmuth.ipp.core.IppAttributesGroup -import de.gmuth.ipp.core.IppTag import de.gmuth.ipp.core.IppTag.MimeMediaType open class DocumentFormat(val mediaMimeType: String) : IppAttributeBuilder { diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt index 9badd91d..8995704e 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppEventNotification.kt @@ -9,7 +9,6 @@ import de.gmuth.ipp.attributes.PrinterState import de.gmuth.ipp.core.IppAttributesGroup import de.gmuth.ipp.core.IppString import java.util.logging.Logger -import java.util.logging.Logger.getLogger class IppEventNotification( val subscription: IppSubscription, diff --git a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt index 92456b36..5cb54ddf 100644 --- a/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt +++ b/src/main/kotlin/de/gmuth/ipp/client/IppJob.kt @@ -92,6 +92,7 @@ class IppJob( get() = attributes.getTimeValue("time-at-completed") val appleJobOwner: String // only supported by Apple CUPS + @SuppressWarnings("kotlin:S1192") get() = attributes.getTextValue("com.apple.print.JobInfo.PMJobOwner") fun hasStateReasons() = attributes.containsKey("job-state-reasons") From b58bd6c3cc58492c8a0b6cb2f37032deef0942a2 Mon Sep 17 00:00:00 2001 From: Gerhard Muth Date: Thu, 5 Oct 2023 16:50:38 +0200 Subject: [PATCH 47/47] added test for better coverage --- .../kotlin/de/gmuth/ipp/core/IppAttribute.kt | 8 +- .../de/gmuth/ipp/core/IppAttributesGroup.kt | 6 +- .../kotlin/de/gmuth/ipp/core/IppDateTime.kt | 5 + .../de/gmuth/ipp/core/IppInputStream.kt | 4 +- .../kotlin/de/gmuth/ipp/core/IppMessage.kt | 2 +- .../de/gmuth/ipp/client/IppClientTests.kt | 4 + .../IppExchangeExceptionTests.kt | 5 +- .../de/gmuth/ipp/core/IppAttributeTests.kt | 51 +++---- .../gmuth/ipp/core/IppAttributesGroupTests.kt | 27 ++-- .../de/gmuth/ipp/core/IppCollectionTests.kt | 10 +- .../de/gmuth/ipp/core/IppInputStreamTests.kt | 49 +++---- .../de/gmuth/ipp/core/IppOutputStreamTests.kt | 81 ++++++----- .../kotlin/de/gmuth/ipp/core/IppTagTests.kt | 131 ++++++++++++++++-- src/test/resources/logging.properties | 2 +- 14 files changed, 258 insertions(+), 127 deletions(-) rename src/test/kotlin/de/gmuth/ipp/{core => client}/IppExchangeExceptionTests.kt (85%) diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt index a9840138..b8c683d1 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttribute.kt @@ -70,17 +70,17 @@ data class IppAttribute(val name: String, val tag: IppTag) : IppAttributeBuil fun enumNameOrValue(value: Any) = if (tag == IppTag.Enum) getEnumName(name, value) else value - fun log(logger: Logger, level: Level = INFO, prefix: String = "") = logger.run { + fun log(logger: Logger, level: Level = INFO, prefix: String = "") = logger.also { val string = toString() if (string.length < 160) { - log(level) { "$prefix$string" } + it.log(level) { "$prefix$string" } } else { - log(level) { "$prefix$name ($tag) =" } + it.log(level) { "$prefix$name ($tag) =" } for (value in values) { if (value is IppCollection) { (value as IppCollection).log(logger, level, "$prefix ") } else { - log(level) { "$prefix ${enumNameOrValue(value as Any)}" } + it.log(level) { "$prefix ${enumNameOrValue(value as Any)}" } } } } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt index 2e4815f3..b76ab946 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppAttributesGroup.kt @@ -6,7 +6,7 @@ package de.gmuth.ipp.core import java.io.File import java.time.Instant -import java.time.ZoneId +import java.time.ZoneOffset import java.time.ZonedDateTime import java.util.logging.Level import java.util.logging.Level.INFO @@ -53,8 +53,8 @@ class IppAttributesGroup(val tag: IppTag) : LinkedHashMap(name).text - fun getTimeValue(name: String, zoneId: ZoneId = ZoneId.systemDefault()): ZonedDateTime = - Instant.ofEpochSecond(getValue(name).toLong()).atZone(zoneId) + fun getTimeValue(name: String): ZonedDateTime = + Instant.ofEpochSecond(getValue(name).toLong()).atZone(ZoneOffset.UTC) fun put(attributesGroup: IppAttributesGroup) = attributesGroup.values.forEach { put(it) } diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt b/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt index 7040d612..c4c665a5 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppDateTime.kt @@ -5,6 +5,7 @@ package de.gmuth.ipp.core */ import java.time.LocalDateTime +import java.time.ZoneId import java.time.ZoneOffset import java.time.ZonedDateTime import java.time.temporal.ChronoField @@ -137,6 +138,10 @@ data class IppDateTime( internal fun getTimeZoneId() = "GMT%c%02d%02d".format(directionFromUTC, hoursFromUTC, minutesFromUTC) + companion object { + fun now(zone: ZoneId = ZoneId.systemDefault()) = + IppDateTime(ZonedDateTime.now(zone)) + } } // Calendar extension diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt index feb5b978..f429853f 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppInputStream.kt @@ -13,7 +13,7 @@ import java.util.logging.Logger.getLogger class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputStream) { - private val log = getLogger(javaClass.name) + internal val log = getLogger(javaClass.name) // character encoding for text and name attributes, RFC 8011 4.1.4.1 internal lateinit var attributesCharset: Charset @@ -236,7 +236,7 @@ class IppInputStream(inputStream: BufferedInputStream) : DataInputStream(inputSt charStringBuilder.clear() } } - if (isNotEmpty()) dumpLine() + if (hexStringBuilder.isNotEmpty()) dumpLine() } } \ No newline at end of file diff --git a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt index 22d7c019..786ea934 100644 --- a/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt +++ b/src/main/kotlin/de/gmuth/ipp/core/IppMessage.kt @@ -13,7 +13,7 @@ import java.util.logging.Logger.getLogger abstract class IppMessage() { - private val log = getLogger(javaClass.name) + internal val log = getLogger(javaClass.name) var code: Int? = null // unsigned short (16 bits) var requestId: Int? = null var version: String? = null diff --git a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt index ce3f2edb..85e671d3 100644 --- a/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppClientTests.kt @@ -1,5 +1,9 @@ package de.gmuth.ipp.client +/** + * Copyright (c) 2023 Gerhard Muth + */ + import de.gmuth.ipp.core.IppOperation.GetPrinterAttributes import org.junit.Test import java.net.URI diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt b/src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt similarity index 85% rename from src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt rename to src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt index 5d3f527f..d8c5eff5 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppExchangeExceptionTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt @@ -1,10 +1,11 @@ -package de.gmuth.ipp.core +package de.gmuth.ipp.client /** * Copyright (c) 2020 Gerhard Muth */ -import de.gmuth.ipp.client.IppExchangeException +import de.gmuth.ipp.core.IppOperation +import de.gmuth.ipp.core.IppRequest import java.util.logging.Logger.getLogger import kotlin.test.Test import kotlin.test.assertEquals diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt index 3ee2874c..31e316a4 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributeTests.kt @@ -5,14 +5,19 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppTag.* +import de.gmuth.log.Logging import java.io.File import java.util.logging.Logger.getLogger import kotlin.test.* class IppAttributeTests { - private val ippAttribute = IppAttribute("printer-state-reasons", Keyword, "none") - val tlog = getLogger(javaClass.name) + init { + Logging.configure() + } + + private val log = getLogger(javaClass.name) + private val attribute = IppAttribute("printer-state-reasons", Keyword, "none") @Test fun constructorFailsDueToDelimiterTag() { @@ -26,28 +31,28 @@ class IppAttributeTests { @Test fun accessingSetAsValueFails() { - assertFailsWith { ippAttribute.value } + assertFailsWith { attribute.value } } @Test fun additionalValue() { - ippAttribute.additionalValue(IppAttribute("", Keyword, "media-empty")) - assertEquals(2, ippAttribute.values.size) + attribute.additionalValue(IppAttribute("", Keyword, "media-empty")) + assertEquals(2, attribute.values.size) } @Test fun additionalValueIgnore1() { - ippAttribute.additionalValue(IppAttribute("", Integer, 2.1)) + attribute.additionalValue(IppAttribute("", Integer, 2.1)) } @Test fun additionalValueFails2() { - assertFailsWith { ippAttribute.additionalValue(IppAttribute("", Keyword)) } + assertFailsWith { attribute.additionalValue(IppAttribute("", Keyword)) } } @Test fun additionalValueFails3() { - assertFailsWith { ippAttribute.additionalValue(IppAttribute("invalid-name", Keyword, "wtf")) } + assertFailsWith { attribute.additionalValue(IppAttribute("invalid-name", Keyword, "wtf")) } } @Test @@ -60,13 +65,13 @@ class IppAttributeTests { @Test fun buildAttribute() { - assertEquals(ippAttribute, ippAttribute.buildIppAttribute(IppAttributesGroup(Printer))) + assertEquals(attribute, attribute.buildIppAttribute(IppAttributesGroup(Printer))) } @Test fun toStringNoValue() { - ippAttribute.values.clear() - assertTrue(ippAttribute.toString().endsWith("no-value")) + attribute.values.clear() + assertTrue(attribute.toString().endsWith("no-value")) } @Test @@ -81,21 +86,10 @@ class IppAttributeTests { } @Test - fun toStringTime1() { - IppAttribute("some-time", Integer, 1000).toString() + fun toStringTest() { + assertEquals("christmas-time (integer) = 1608160102", IppAttribute("christmas-time", Integer, 1608160102).toString()) } - @Test - fun toStringTime2() { - IppAttribute("christmas-time", Integer, 1608160102).toString() - } - - @Test - fun toStringTimeOut() { - IppAttribute("some-time-out", Integer, 1000).toString() - } - - @Test fun enumNameOrValue() { assertEquals("processing", IppAttribute("printer-state", IppTag.Enum, 0).enumNameOrValue(4)) @@ -104,7 +98,7 @@ class IppAttributeTests { @Test fun log() { // cover an output with more than 160 characters and a collection value - IppAttribute("media-col".padEnd(160, '-'), BegCollection, IppCollection()).log(tlog) + IppAttribute("media-col".padEnd(160, '-'), BegCollection, IppCollection()).log(log) } @Test @@ -122,4 +116,11 @@ class IppAttributeTests { assertEquals("foo (1setOf integer) = 1,2,3", IppAttribute("foo", Integer, 1, 2, 3).toString()) } + @Test + fun attributeWithDateTimeHasValidValueClass() { + IppAttribute("datetime-now", DateTime, IppDateTime.now()).run { + assertTrue(tag.valueHasValidClass(value)) + } + } + } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt index d744f7bf..de725e93 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppAttributesGroupTests.kt @@ -14,7 +14,7 @@ import kotlin.test.assertTrue class IppAttributesGroupTests { - val log = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) private val group = IppAttributesGroup(Operation) @Test @@ -28,8 +28,9 @@ class IppAttributesGroupTests { } @Test - fun putWithReplacementAllowed() { - with(IppAttributesGroup(Printer)) { + fun putWithReplacement() { + group.run { + onReplaceWarn = false attribute("number", Integer, 0) attribute("number", Integer, 1, 2) assertEquals(1, size) @@ -39,11 +40,13 @@ class IppAttributesGroupTests { @Test fun putWithReplacementWarning() { - group.put(IppAttribute("number", Integer, 0)) - group.onReplaceWarn = true - group.put(IppAttribute("number", Integer, 1, 2)) - assertEquals(1, group.size) - assertEquals(group["number"]!!.values.size, 2) + group.run { + onReplaceWarn = true + put(IppAttribute("number", Integer, 0)) + put(IppAttribute("number", Integer, 1, 2)) + assertEquals(1, size) + assertEquals(get("number")!!.values.size, 2) + } } @Test @@ -70,7 +73,7 @@ class IppAttributesGroupTests { @Test fun getValue() { group.attribute("foo", Keyword, "bar") - assertEquals("bar", group.getValue("foo") as String) + assertEquals("bar", group.getValue("foo")) } @Test @@ -79,6 +82,12 @@ class IppAttributesGroupTests { assertEquals("bar", group.getTextValue("foo")) } + @Test + fun getEpochTimeValue() { + group.attribute("epoch-seconds", Integer, 62) + assertEquals("1970-01-01T00:01:02", group.getTimeValue("epoch-seconds").toLocalDateTime().toString()) + } + @Test fun getValueOrNull() { group.attribute("foo0", Keyword, "bar0") diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt index 8887410e..d4d8f14c 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppCollectionTests.kt @@ -1,7 +1,7 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import java.util.NoSuchElementException @@ -12,7 +12,7 @@ import kotlin.test.assertFailsWith class IppCollectionTests { - val tlog = getLogger(javaClass.name) + private val log = getLogger(javaClass.name) private val collection = IppCollection(IppAttribute("foo", IppTag.Keyword, "a", "b")) @Test @@ -21,7 +21,7 @@ class IppCollectionTests { } @Test - fun attribute() { + fun addAttribute() { collection.addAttribute("year", IppTag.Integer, 2021) assertEquals(2, collection.members.size) } @@ -41,13 +41,13 @@ class IppCollectionTests { @Test fun logNarrow() { - collection.log(tlog) + collection.log(log) } @Test fun logWide() { collection.addAll(listOf(IppAttribute("bar", IppTag.Keyword, "c".repeat(160)))) - collection.log(tlog) + collection.log(log) } } \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt index d43334d5..79e1b89d 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppInputStreamTests.kt @@ -5,10 +5,9 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppResolution.Unit.DPI -import de.gmuth.log.Logging import java.io.ByteArrayInputStream import java.net.URI -import java.util.logging.Logger +import java.util.logging.Level.ALL import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith @@ -16,10 +15,6 @@ import kotlin.test.assertTrue class IppInputStreamTest { - init { - Logging.configure() - } - private val message = object : IppMessage() { override val codeDescription: String get() = "codeDescription" @@ -175,23 +170,25 @@ class IppInputStreamTest { assertEquals(8, requestId) with(getSingleAttributesGroup(IppTag.Job)) { assertEquals(2, size) - with(get("1")!!) { - assertEquals(IppTag.Boolean, this.tag) + get("1")!!.run { + assertEquals(IppTag.Boolean, tag) assertEquals(listOf(true, false), values as List<*>) } - with(get("0")!!) { - assertEquals(IppTag.NoValue, this.tag) - } + assertEquals(IppTag.NoValue, get("0")!!.tag) } } } @Test fun readMessageFails() { - val encoded = "01 01 00 0B 00 00 00 08 01 47 00 01 61 00 01 66 0A 0B 0C 0D" - assertFailsWith { - encoded.toIppInputStream().readMessage(message) - } + val encoded = + "01 01 00 0B 00 00 00 08 01 47 00 01 61 00 01 66 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF AA" + assertFailsWith { + encoded.toIppInputStream().run { + log.level = ALL + readMessage(message) + } + } } @Test @@ -203,7 +200,9 @@ class IppInputStreamTest { @Test fun readOutOfBandAttribute() { val encoded = "00 01 65 00 01 02" - encoded.toIppInputStream().readAttribute(IppTag.NoValue) + encoded.toIppInputStream().run { + assertEquals(1, readAttribute(IppTag.NoValue).values.size) + } } @Test @@ -214,16 +213,14 @@ class IppInputStreamTest { with(attribute.value as ByteArray) { assertEquals(1, size) } } -} - -// hex utility extensions + // Hex utility extensions -fun String.toByteArray() = - ByteArray((length + 1) / 3) { substring(3 * it, 3 * it + 2).toInt(16).toByte() } + private fun String.toByteArray() = + ByteArray((length + 1) / 3) { substring(3 * it, 3 * it + 2).toInt(16).toByte() } -fun String.toIppInputStream() = IppInputStream(ByteArrayInputStream(toByteArray()).buffered()).apply { - attributesCharset = Charsets.US_ASCII -} + private fun String.toIppInputStream() = IppInputStream(ByteArrayInputStream(toByteArray()).buffered()) + .apply { attributesCharset = Charsets.US_ASCII } -fun String.readAttributeValue(tag: IppTag) = - toIppInputStream().readAttributeValue(tag) \ No newline at end of file + private fun String.readAttributeValue(tag: IppTag) = + toIppInputStream().readAttributeValue(tag) +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt index ad50d428..753246d6 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppOutputStreamTests.kt @@ -5,14 +5,20 @@ package de.gmuth.ipp.core */ import de.gmuth.ipp.core.IppResolution.Unit.DPI +import de.gmuth.ipp.core.IppTag.* +import de.gmuth.ipp.core.IppTag.Boolean as IppBoolean +import de.gmuth.ipp.core.IppTag.Enum as IppEnum import java.io.ByteArrayOutputStream import java.net.URI +import java.util.logging.Level +import java.util.logging.Logger import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith class IppOutputStreamTest { + private val log = Logger.getLogger(javaClass.name) private val byteArrayOutputStream = ByteArrayOutputStream() private val ippOutputStream = IppOutputStream(byteArrayOutputStream).apply { attributesCharset = Charsets.US_ASCII } @@ -21,7 +27,7 @@ class IppOutputStreamTest { get() = "codeDescription" init { - createAttributesGroup(IppTag.Operation).attribute("attributes-charset", IppTag.Charset, Charsets.UTF_8) + createAttributesGroup(Operation).attribute("attributes-charset", IppTag.Charset, Charsets.UTF_8) } } @@ -33,114 +39,114 @@ class IppOutputStreamTest { @Test fun writeAttribute() { - ippOutputStream.writeAttribute(IppAttribute("0", IppTag.NotSettable, ByteArray(0))) + ippOutputStream.writeAttribute(IppAttribute("0", NotSettable, ByteArray(0))) assertEquals("15 00 01 30 00 00", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueBooleanFalse() { - ippOutputStream.writeAttributeValue(IppTag.Boolean, false) + ippOutputStream.writeAttributeValue(IppBoolean, false) assertEquals("00 01 00", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueBooleanTrue() { - ippOutputStream.writeAttributeValue(IppTag.Boolean, true) + ippOutputStream.writeAttributeValue(IppBoolean, true) assertEquals("00 01 01", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueInteger() { - ippOutputStream.writeAttributeValue(IppTag.Integer, 0x12345678) + ippOutputStream.writeAttributeValue(Integer, 0x12345678) assertEquals("00 04 12 34 56 78", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueEnum() { - ippOutputStream.writeAttributeValue(IppTag.Enum, 0x01020304) + ippOutputStream.writeAttributeValue(IppEnum, 0x01020304) assertEquals("00 04 01 02 03 04", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueRangeOfInteger() { - ippOutputStream.writeAttributeValue(IppTag.RangeOfInteger, 16..1024) + ippOutputStream.writeAttributeValue(RangeOfInteger, 16..1024) assertEquals("00 08 00 00 00 10 00 00 04 00", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueResolution() { - ippOutputStream.writeAttributeValue(IppTag.Resolution, IppResolution(600, DPI)) + ippOutputStream.writeAttributeValue(Resolution, IppResolution(600, DPI)) assertEquals("00 09 00 00 02 58 00 00 02 58 03", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueCharset() { - ippOutputStream.writeAttributeValue(IppTag.Charset, Charsets.US_ASCII) + ippOutputStream.writeAttributeValue(Charset, Charsets.US_ASCII) assertEquals("00 08 75 73 2D 61 73 63 69 69", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueUri() { - ippOutputStream.writeAttributeValue(IppTag.Uri, URI.create("ipp://joelle")) + ippOutputStream.writeAttributeValue(Uri, URI.create("ipp://joelle")) assertEquals("00 0C 69 70 70 3A 2F 2F 6A 6F 65 6C 6C 65", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueKeyword() { - ippOutputStream.writeAttributeValue(IppTag.Keyword, "aKeyword") + ippOutputStream.writeAttributeValue(Keyword, "aKeyword") assertEquals("00 08 61 4B 65 79 77 6F 72 64", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueUriScheme() { - ippOutputStream.writeAttributeValue(IppTag.UriScheme, "ipps") + ippOutputStream.writeAttributeValue(UriScheme, "ipps") assertEquals("00 04 69 70 70 73", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueOctetString() { - ippOutputStream.writeAttributeValue(IppTag.OctetString, "anOctetString") + ippOutputStream.writeAttributeValue(OctetString, "anOctetString") assertEquals("00 0D 61 6E 4F 63 74 65 74 53 74 72 69 6E 67", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueMimeMediaType() { - ippOutputStream.writeAttributeValue(IppTag.MimeMediaType, "application/pdf") + ippOutputStream.writeAttributeValue(MimeMediaType, "application/pdf") assertEquals("00 0F 61 70 70 6C 69 63 61 74 69 6F 6E 2F 70 64 66", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueMemberAttrName() { - ippOutputStream.writeAttributeValue(IppTag.MemberAttrName, "a-member-attr-name") + ippOutputStream.writeAttributeValue(MemberAttrName, "a-member-attr-name") assertEquals("00 12 61 2D 6D 65 6D 62 65 72 2D 61 74 74 72 2D 6E 61 6D 65", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueNaturalLanguage() { - ippOutputStream.writeAttributeValue(IppTag.NaturalLanguage, "en-us") + ippOutputStream.writeAttributeValue(NaturalLanguage, "en-us") assertEquals("00 05 65 6E 2D 75 73", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueTextWithoutLanguage() { - ippOutputStream.writeAttributeValue(IppTag.TextWithoutLanguage, IppString("aTextWithoutLanguage")) + ippOutputStream.writeAttributeValue(TextWithoutLanguage, IppString("aTextWithoutLanguage")) assertEquals("00 14 61 54 65 78 74 57 69 74 68 6F 75 74 4C 61 6E 67 75 61 67 65", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueNameWithoutLanguage() { - ippOutputStream.writeAttributeValue(IppTag.NameWithoutLanguage, IppString("aNameWithoutLanguage")) + ippOutputStream.writeAttributeValue(NameWithoutLanguage, IppString("aNameWithoutLanguage")) assertEquals("00 14 61 4E 61 6D 65 57 69 74 68 6F 75 74 4C 61 6E 67 75 61 67 65", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueNameWithoutLanguageFails() { - assertFailsWith { ippOutputStream.writeAttributeValue(IppTag.NameWithoutLanguage, 0) } + assertFailsWith { ippOutputStream.writeAttributeValue(NameWithoutLanguage, 0) } } @Test fun writeAttributeValueTextWithLanguage() { - ippOutputStream.writeAttributeValue(IppTag.TextWithLanguage, IppString("aTextWithLanguage", "en")) + ippOutputStream.writeAttributeValue(TextWithLanguage, IppString("aTextWithLanguage", "en")) assertEquals( "00 17 00 02 65 6E 00 11 61 54 65 78 74 57 69 74 68 4C 61 6E 67 75 61 67 65", byteArrayOutputStream.toHex() @@ -149,7 +155,7 @@ class IppOutputStreamTest { @Test fun writeAttributeValueNameWithLanguage() { - ippOutputStream.writeAttributeValue(IppTag.NameWithLanguage, IppString("einNameMitSprache", "de")) + ippOutputStream.writeAttributeValue(NameWithLanguage, IppString("einNameMitSprache", "de")) assertEquals( "00 17 00 02 64 65 00 11 65 69 6E 4E 61 6D 65 4D 69 74 53 70 72 61 63 68 65", byteArrayOutputStream.toHex() @@ -159,21 +165,21 @@ class IppOutputStreamTest { @Test fun writeAttributeValueNameWithLanguageFails() { assertFailsWith { - ippOutputStream.writeAttributeValue(IppTag.NameWithLanguage, "text-without-language") + ippOutputStream.writeAttributeValue(NameWithLanguage, "text-without-language") } } @Test fun writeAttributeValueDateTime() { - ippOutputStream.writeAttributeValue(IppTag.DateTime, IppDateTime(2007, 3, 15, 2, 15, 37, 0, '+', 1, 0)) + ippOutputStream.writeAttributeValue(DateTime, IppDateTime(2007, 3, 15, 2, 15, 37, 0, '+', 1, 0)) assertEquals("00 0B 07 D7 03 0F 02 0F 25 00 2B 01 00", byteArrayOutputStream.toHex()) } @Test fun writeAttributeValueCollection() { ippOutputStream.writeAttributeValue( - IppTag.BegCollection, - IppCollection(IppAttribute("foo", IppTag.Keyword, "a", "b")) + BegCollection, + IppCollection(IppAttribute("foo", Keyword, "a", "b")) ) assertEquals( "00 00 4A 00 00 00 03 66 6F 6F 44 00 00 00 01 61 44 00 00 00 01 62 37 00 00 00 00", @@ -192,9 +198,9 @@ class IppOutputStreamTest { version = "2.1" code = IppOperation.GetPrinterAttributes.code requestId = 8 - with(createAttributesGroup(IppTag.Job)) { - attribute("1", IppTag.Boolean, true, false) // cover 1setOf - attribute("0", IppTag.NoValue) // cover OutOfBand + with(createAttributesGroup(Job)) { + attribute("1", IppBoolean, true, false) // cover 1setOf + attribute("0", NoValue) // cover OutOfBand } } ippOutputStream.writeMessage(message) @@ -226,20 +232,21 @@ class IppOutputStreamTest { } @Test - fun writeMessageFails() { + fun writeMessageTextWithLanguageFails() { with(message) { version = "2.1" code = IppOperation.GetPrinterAttributes.code requestId = 8 - operationGroup.attribute("foo", IppTag.TextWithLanguage, IppString("text-without-language")) + operationGroup.attribute("foo", TextWithLanguage, IppString("text-without-language")) + } + assertFailsWith { ippOutputStream.writeMessage(message) }.run { + assertEquals("failed to write attribute: foo (textWithLanguage) = text-without-language", message) + assertEquals("expecting IppString with language", cause!!.message) } - assertFailsWith { ippOutputStream.writeMessage(message) } } -} - -// hex utility extensions - -fun ByteArray.toHex() = joinToString(" ") { "%02X".format(it) } + // Hex utility extensions + private fun ByteArray.toHex() = joinToString(" ") { "%02X".format(it) } + private fun ByteArrayOutputStream.toHex() = toByteArray().toHex() -fun ByteArrayOutputStream.toHex() = toByteArray().toHex() \ No newline at end of file +} \ No newline at end of file diff --git a/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt b/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt index 03973a86..4f97bf9f 100644 --- a/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt +++ b/src/test/kotlin/de/gmuth/ipp/core/IppTagTests.kt @@ -1,42 +1,149 @@ package de.gmuth.ipp.core /** - * Copyright (c) 2020 Gerhard Muth + * Copyright (c) 2020-2023 Gerhard Muth */ import kotlin.test.* +import de.gmuth.ipp.core.IppTag.* +import java.net.URI class IppTagTests { + @Test + fun validInteger() { + assertFalse(Integer.valueHasValidClass("no-integer")) + assertTrue(Integer.valueHasValidClass(1)) + } + + @Test + fun validBoolean() { + assertFalse(IppTag.Boolean.valueHasValidClass("no-boolean")) + assertTrue(IppTag.Boolean.valueHasValidClass(true)) + } + + @Test + fun validEnum() { + assertFalse(IppTag.Enum.valueHasValidClass("no-enum")) + assertTrue(IppTag.Enum.valueHasValidClass(1)) + } + + @Test + fun validOctetString() { + assertFalse(OctetString.valueHasValidClass(0)) + assertTrue(OctetString.valueHasValidClass("string")) + } + + @Test + fun validDateTime() { + assertFalse(DateTime.valueHasValidClass(0)) + assertTrue(DateTime.valueHasValidClass(IppDateTime.now())) + } + + @Test + fun validResolution() { + assertFalse(Resolution.valueHasValidClass(0)) + assertTrue(Resolution.valueHasValidClass(IppResolution(600))) + } + + @Test + fun validRangeOfInteger() { + assertFalse(RangeOfInteger.valueHasValidClass(0)) + assertTrue(RangeOfInteger.valueHasValidClass(0..1)) + } + + @Test + fun validBegCollection() { + assertFalse(BegCollection.valueHasValidClass(0)) + assertTrue(BegCollection.valueHasValidClass(IppCollection())) + } + @Test fun validateTextWithLanguage() { - assertFalse(IppTag.TextWithoutLanguage.valueHasValidClass(0)) - assertTrue(IppTag.TextWithoutLanguage.valueHasValidClass("string")) + assertFalse(TextWithLanguage.valueHasValidClass(0)) + assertTrue(TextWithLanguage.valueHasValidClass(IppString(""))) } @Test fun validateNameWithLanguage() { - assertFalse(IppTag.NameWithoutLanguage.valueHasValidClass(0)) - assertTrue(IppTag.NameWithoutLanguage.valueHasValidClass("string")) + assertFalse(NameWithLanguage.valueHasValidClass(0)) + assertTrue(NameWithLanguage.valueHasValidClass(IppString(""))) } + @Test + fun validEndCollection() { + assertTrue(EndCollection.valueHasValidClass(0)) + assertTrue(EndCollection.valueHasValidClass("")) + } + + @Test + fun validateTextWithoutLanguage() { + assertFalse(TextWithoutLanguage.valueHasValidClass(0)) + assertTrue(TextWithoutLanguage.valueHasValidClass("string")) + assertTrue(TextWithoutLanguage.valueHasValidClass(IppString("ipp-string"))) + } + + @Test + fun validateNameWithoutLanguage() { + assertFalse(NameWithoutLanguage.valueHasValidClass(0)) + assertTrue(NameWithoutLanguage.valueHasValidClass("string")) + assertTrue(NameWithoutLanguage.valueHasValidClass(IppString("ipp-string"))) + } + + private fun validString(tag: IppTag) = tag.run { + assertFalse(valueHasValidClass(0)) + assertTrue(valueHasValidClass("string")) + } + + @Test + fun validKeyword() = + validString(Keyword) + + @Test + fun validUri() { + assertFalse(Uri.valueHasValidClass(0)) + assertTrue(Uri.valueHasValidClass(URI.create("ipp://0"))) + } + + @Test + fun validUriScheme() = + validString(UriScheme) + + @Test + fun validCharset() { + assertFalse(Charset.valueHasValidClass(0)) + assertTrue(Charset.valueHasValidClass(java.nio.charset.Charset.defaultCharset())) + } + + @Test + fun validNaturalLanguage() = + validString(NaturalLanguage) + + @Test + fun validMimeMediaType() = + validString(MimeMediaType) + + @Test + fun validMemberAttrName() = + validString(MemberAttrName) + @Test fun tagClassification() { - assertFalse(IppTag.Printer.isOutOfBandTag()) - assertFalse(IppTag.Printer.isMemberAttrValue()) - assertFalse(IppTag.MemberAttrName.isMemberAttrValue()) + assertFalse(Printer.isOutOfBandTag()) + assertFalse(Printer.isMemberAttrValue()) + assertFalse(MemberAttrName.isMemberAttrValue()) } @Test fun registeredSyntax() { - assertEquals("name", IppTag.NameWithoutLanguage.registeredSyntax()) - assertEquals("text", IppTag.TextWithoutLanguage.registeredSyntax()) - assertEquals("keyword", IppTag.Keyword.registeredSyntax()) + assertEquals("name", NameWithoutLanguage.registeredSyntax()) + assertEquals("text", TextWithoutLanguage.registeredSyntax()) + assertEquals("keyword", Keyword.registeredSyntax()) } @Test fun fromString() { - assertEquals(IppTag.Uri, IppTag.fromString("uri")) + assertEquals(Uri, IppTag.fromString("uri")) } @Test diff --git a/src/test/resources/logging.properties b/src/test/resources/logging.properties index 3d1345d3..fc483c4f 100644 --- a/src/test/resources/logging.properties +++ b/src/test/resources/logging.properties @@ -34,7 +34,7 @@ handlers=de.gmuth.log.StdoutHandler,java.util.logging.FileHandler #de.gmuth.log.StdoutHandler #de.gmuth.log.ConsoleHandler -de.gmuth.log.StdoutHandler.level=FINER +de.gmuth.log.StdoutHandler.level=ALL #java.util.logging.ConsoleHandler.level=ALL java.util.logging.FileHandler.level=FINER