Skip to content

Commit

Permalink
Refactored IppExchangeException into HttpPostException and IppOperati…
Browse files Browse the repository at this point in the history
…onException
  • Loading branch information
gmuth committed May 20, 2024
1 parent becb27c commit dc82a08
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 96 deletions.
42 changes: 24 additions & 18 deletions src/main/kotlin/de/gmuth/ipp/client/CupsClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package de.gmuth.ipp.client
* Copyright (c) 2020-2024 Gerhard Muth
*/

import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException
import de.gmuth.ipp.client.IppOperationException.ClientErrorNotFoundException
import de.gmuth.ipp.client.WhichJobs.All
import de.gmuth.ipp.core.IppOperation
import de.gmuth.ipp.core.IppOperation.*
Expand Down Expand Up @@ -276,6 +276,7 @@ class CupsClient(
): Collection<IppJob> {
val numberOfJobsWithoutDocuments = AtomicInteger(0)
val numberOfSavedDocuments = AtomicInteger(0)
with(cupsClientWorkDirectory) { if (!exists()) mkdirs() }
return getJobs(
whichJobs,
requestedAttributes = listOf(
Expand All @@ -293,7 +294,7 @@ class CupsClient(
job.getOriginatingUserNameOrAppleJobOwnerOrNull()?.let { jobOwners.add(it) }
}
.onEach { job -> // keep stats and save documents
if (job.numberOfDocuments == 0) numberOfJobsWithoutDocuments.incrementAndGet()
if (job.numberOfDocumentsOrDocumentCount == 0) numberOfJobsWithoutDocuments.incrementAndGet()
else getAndSaveDocuments(job, optionalCommandToHandleFile = commandToHandleSavedFile)
.apply { numberOfSavedDocuments.addAndGet(size) }
}
Expand Down Expand Up @@ -334,35 +335,40 @@ class CupsClient(
// Get and save documents for job
// ------------------------------

internal fun getAndSaveDocuments(
private fun getAndSaveDocuments(
job: IppJob,
onSuccessUpdateJobAttributes: Boolean = false,
optionalCommandToHandleFile: String? = null
): Collection<File> {
var documents: Collection<IppDocument> = emptyList()
fun getDocuments() = try {
documents = job.cupsGetDocuments()
if (documents.isNotEmpty() && onSuccessUpdateJobAttributes) job.updateAttributes()
true
} catch (ippExchangeException: IppExchangeException) {
logger.info { "Get documents for job #${job.id} failed: ${ippExchangeException.message}" }
ippExchangeException.httpStatus!! != 401
}

if (!getDocuments()) {
val documents: MutableCollection<IppDocument> = mutableListOf()
if (!job.tryToGetDocuments(documents, onSuccessUpdateJobAttributes)) {
val configuredUserName = config.userName
jobOwners.forEach {
config.userName = it
logger.fine { "set userName '${config.userName}'" }
if (getDocuments()) return@forEach
if (it != job.getOriginatingUserNameOrAppleJobOwnerOrNull()) {
config.userName = it
logger.fine { "set userName '${config.userName}'" }
if (job.tryToGetDocuments(documents, onSuccessUpdateJobAttributes)) return@forEach
}
}
config.userName = configuredUserName
}

documents.onEach { document ->
document.save(job.printerDirectory(), overwrite = true)
optionalCommandToHandleFile?.let { document.runCommand(it) }
}
return documents.map { it.file!! }
}

private fun IppJob.tryToGetDocuments(
documents: MutableCollection<IppDocument>,
onSuccessUpdateJobAttributes: Boolean = false
) = try { // returns "getting document was authorized"
documents.addAll(cupsGetDocuments())
if (documents.isNotEmpty() && onSuccessUpdateJobAttributes) updateAttributes()
true
} catch (ippExchangeException: IppExchangeException) {
logger.info { "Get documents for job #$id failed: ${ippExchangeException.message}" }
ippExchangeException !is HttpPostException || ippExchangeException.httpStatus != 401
}

}
30 changes: 12 additions & 18 deletions src/main/kotlin/de/gmuth/ipp/client/IppClient.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package de.gmuth.ipp.client

/**
* Copyright (c) 2020-2023 Gerhard Muth
* Copyright (c) 2020-2024 Gerhard Muth
*/

import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException
import de.gmuth.ipp.client.IppOperationException.ClientErrorNotFoundException
import de.gmuth.ipp.core.IppOperation
import de.gmuth.ipp.core.IppRequest
import de.gmuth.ipp.core.IppResponse
Expand Down Expand Up @@ -113,7 +113,7 @@ open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
validateHttpResponse(request, errorStream, ioException)
errorStream
}
return decodeContentStream(request, responseCode, responseContentStream)
return decodeContentStream(request, responseContentStream)
.apply { httpServer = headerFields["Server"]?.first() }
} finally {
if (disconnectAfterHttpPost) disconnect()
Expand All @@ -131,7 +131,7 @@ open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
IppRegistrationsSection2.validate(request)
if (throwWhenNotSuccessful) {
throw if (status == ClientErrorNotFound) ClientErrorNotFoundException(request, response)
else IppExchangeException(request, response, 200)
else IppOperationException(request, response)
}
}
}
Expand Down Expand Up @@ -172,31 +172,25 @@ open class IppClient(val config: IppConfig = IppConfig()) : IppExchange {
exception != null -> exception.message
else -> null // no issues found
}?.let {
throw IppExchangeException(
throw HttpPostException(
request,
response = null,
responseCode, // HTTP
httpStatus = responseCode,
httpHeaderFields = headerFields,
httpStream = contentStream,
message = it,
cause = exception
)
}

private fun decodeContentStream(
request: IppRequest,
httpStatus: Int,
contentStream: InputStream
) = IppResponse().apply {
private fun decodeContentStream(request: IppRequest, contentStream: InputStream) = 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")
}
throw IppOperationException(request, this, message = "Failed to decode ipp response", cause = throwable)
.also {
if (onExceptionSaveMessages)
it.saveMessages("decoding_ipp_response_${request.requestId}_failed")
}
}
}
}
49 changes: 6 additions & 43 deletions src/main/kotlin/de/gmuth/ipp/client/IppExchangeException.kt
Original file line number Diff line number Diff line change
@@ -1,71 +1,34 @@
package de.gmuth.ipp.client

/**
* Copyright (c) 2020-2023 Gerhard Muth
* Copyright (c) 2020-2024 Gerhard Muth
*/

import de.gmuth.ipp.core.IppException
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 java.io.File
import java.io.InputStream
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
import kotlin.io.path.pathString

open class IppExchangeException(
val request: IppRequest,
val response: IppResponse? = null,
val httpStatus: Int? = null,
val httpHeaderFields: Map<String?, List<String>>? = null,
val httpStream: InputStream? = null,
message: String = defaultMessage(request, response),
cause: Throwable? = null,
message: String = "Exchanging request '${request.operation}' failed",
cause: Throwable? = null
) : IppException(message, cause) {

class ClientErrorNotFoundException(request: IppRequest, response: IppResponse) :
IppExchangeException(request, response) {
init {
require(response.status == ClientErrorNotFound)
{ "IPP response status is not ClientErrorNotFound: ${response.status}" }
}
}

private val logger = getLogger(javaClass.name)

companion object {
fun defaultMessage(request: IppRequest, response: IppResponse?) = StringBuilder().run {
append("${request.operation} failed")
response?.run {
append(": '$status'")
if (operationGroup.containsKey("status-message")) append(", $statusMessage")
}
toString()
}
}

fun statusIs(status: IppStatus) = response?.status == status

fun log(logger: Logger, level: Level = INFO) = logger.run {
open fun log(logger: Logger, level: Level = INFO): Unit = with(logger) {
log(level) { message }
request.log(this, level, prefix = "REQUEST: ")
response?.log(this, 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(level) { "HTTP-Content:\n" + it.bufferedReader().use { it.readText() } } }
}

fun saveMessages(
fileNameWithoutSuffix: String = "ipp_exchange_exception_$httpStatus",
open fun saveMessages(
fileNameWithoutSuffix: String = "ipp_exchange_exception",
directory: String = createTempDirectory("ipp-client-").pathString
) {
request.saveBytes(File(directory, "$fileNameWithoutSuffix.request"))
response?.saveBytes(File(directory, "$fileNameWithoutSuffix.response"))
}

}
6 changes: 3 additions & 3 deletions src/main/kotlin/de/gmuth/ipp/client/IppInspector.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ object IppInspector {
ColorMode.Color,
Media.ISO_A3
)
} catch (ippExchangeException: IppExchangeException) {
ippExchangeException.response
} catch (ippOperationException: IppOperationException) {
ippOperationException.response
}
logger.info { response.toString() }

logger.info { "> Print job $pdfResource" }
printJob(
IppInspector::class.java.getResourceAsStream("/$pdfResource"),
IppInspector::class.java.getResourceAsStream("/$pdfResource")!!,
jobName(pdfResource),

).run {
Expand Down
13 changes: 7 additions & 6 deletions src/main/kotlin/de/gmuth/ipp/client/IppPrinter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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.client.IppOperationException.ClientErrorNotFoundException
import de.gmuth.ipp.core.*
import de.gmuth.ipp.core.IppOperation.*
import de.gmuth.ipp.core.IppStatus.ClientErrorNotFound
Expand Down Expand Up @@ -72,17 +73,17 @@ class IppPrinter(
alert?.let { logger.info { "alert: $it" } }
alertDescription?.let { logger.info { "alert-description: $it" } }
}
} catch (ippExchangeException: IppExchangeException) {
if (ippExchangeException.statusIs(ClientErrorNotFound))
logger.severe { ippExchangeException.message }
} catch (ippOperationException: IppOperationException) {
if (ippOperationException.statusIs(ClientErrorNotFound))
logger.severe { ippOperationException.message }
else {
logger.severe { "Failed to get printer attributes on init. Workaround: getPrinterAttributesOnInit=false" }
ippExchangeException.response?.let {
ippOperationException.response.let {
if (it.containsGroup(Printer)) logger.info { "${it.printerGroup.size} attributes parsed" }
else logger.warning { "RESPONSE: $it" }
}
}
throw ippExchangeException
throw ippOperationException
}
if (isCups()) workDirectory = File("cups-${printerUri.host}")
}
Expand Down Expand Up @@ -513,7 +514,7 @@ class IppPrinter(
exchange(request)
.getAttributesGroups(Subscription)
.map { IppSubscription(this, it, startLease = false) }
} catch (notFoundException: IppExchangeException.ClientErrorNotFoundException) {
} catch (notFoundException: ClientErrorNotFoundException) {
emptyList()
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/de/gmuth/ipp/client/IppSubscription.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package de.gmuth.ipp.client

/**
* Copyright (c) 2021-2023 Gerhard Muth
* Copyright (c) 2021-2024 Gerhard Muth
*/

import de.gmuth.ipp.client.IppExchangeException.ClientErrorNotFoundException
import de.gmuth.ipp.client.IppOperationException.ClientErrorNotFoundException
import de.gmuth.ipp.core.IppAttributesGroup
import de.gmuth.ipp.core.IppOperation
import de.gmuth.ipp.core.IppOperation.*
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/de/gmuth/ipp/client/CupsClientTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class CupsClientTests {

@Test
fun getDefaultFails() {
val exception = assertFailsWith<IppExchangeException> {
val exception = assertFailsWith<IppOperationException> {
ippClientMock.mockResponse("Cups-Get-Default-Error.ipp")
cupsClient.getDefault()
}
Expand Down
35 changes: 30 additions & 5 deletions src/test/kotlin/de/gmuth/ipp/client/IppExchangeExceptionTests.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,51 @@ package de.gmuth.ipp.client

import de.gmuth.ipp.core.IppOperation
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.net.URI
import java.util.logging.Logger.getLogger
import kotlin.test.Test
import kotlin.test.assertEquals

class IppExchangeExceptionTests {

init {
Logging.configure()
}

private val logger = getLogger(javaClass.name)

@Test
fun constructor() {
fun operationException() {
with(
IppExchangeException(
IppOperationException(
IppRequest(IppOperation.GetPrinterAttributes).apply { encode() },
null, 400
)
IppResponse(status = IppStatus.ClientErrorBadRequest)
)
) {
log(logger)
assertEquals(11, request.code)
assertEquals("Get-Printer-Attributes failed: 'client-error-bad-request'", message)
}
}

@Test
fun httpPostException() {
with(
HttpPostException(
IppRequest(
IppOperation.GetPrinterAttributes,
printerUri = URI.create("ipp://foo")
).apply { encode() },
400
)
) {
log(logger)
assertEquals(11, request.code)
assertEquals(400, httpStatus)
assertEquals(message, "Get-Printer-Attributes failed")
assertEquals( "http post for request Get-Printer-Attributes to ipp://foo failed", message)
}
}

Expand Down

0 comments on commit dc82a08

Please sign in to comment.