From ab43238420a0bbeb521fa8d2b21c215be583d6fd Mon Sep 17 00:00:00 2001 From: James Higgs <45565019+JamesHR3@users.noreply.github.com> Date: Tue, 28 Apr 2020 14:07:50 +0100 Subject: [PATCH 1/4] [EG-438] Error Reporting Framework (#6125) * [EG-438] First commit of error code interface * [EG-438] Implement error reporter and a few error codes * [EG-438] Add unit tests and default properties files * [EG-438] Add the error table builder * [EG-438] Update initial properties files * [EG-438] Add some Irish tests and the build.gradle * [EG-438] Fall back for aliases and use different resource strategy * [EG-438] Define the URL using a project-specific context * [EG-438] Tidy up initialization code * [EG-438] Add testing to generator and tidy up * [EG-438] Remove direct dependency on core and add own logging config * [EG-438] Fix compiler warnings and tidy up logging * [EG-438] Fix detekt warnings * [EG-438] Improve error messages * [EG-438] Address first set of review comments * [EG-438] Use enums and a builder for the reporter * [EG-438] Add kdocs for error resource static methods * [EG-438] Handle enums defined with underscores * [EG-438] Slight refactoring of startup code * [EG-438] Port changes to error reporting code from future branch * [EG-438] Also port test changes * [EG-438] Suppress a deliberately unused parameter --- common/logging/build.gradle | 2 + .../CordaErrorContextProvider.kt | 31 +++++ .../logging/errorReporting/ErrorCode.kt | 28 ++++ .../logging/errorReporting/ErrorCodes.kt | 16 +++ .../errorReporting/ErrorContextProvider.kt | 24 ++++ .../logging/errorReporting/ErrorReporter.kt | 16 +++ .../errorReporting/ErrorReporterImpl.kt | 36 ++++++ .../logging/errorReporting/ErrorReporting.kt | 66 ++++++++++ .../ErrorReportingExceptions.kt | 27 ++++ .../errorReporting/ErrorReportingUtils.kt | 18 +++ .../logging/errorReporting/ErrorResource.kt | 60 +++++++++ .../common/logging/errorReporting/Errors.kt | 20 +++ .../ResourceBundleProperties.kt | 11 ++ .../error-codes/ErrorInfo.properties | 2 + .../error-codes/ErrorInfo_en_US.properties | 2 + .../database-could-not-connect.properties | 4 + ...atabase-could-not-connect_en_US.properties | 3 + .../database-failed-startup.properties | 4 + .../database-failed-startup_en_US.properties | 3 + .../database-missing-driver.properties | 4 + .../database-missing-driver_en_US.properties | 3 + ...tabase-password-required-for-h2.properties | 4 + ...-password-required-for-h2_en_US.properties | 3 + .../CordaErrorContextProviderTest.kt | 20 +++ .../errorReporting/ErrorReporterImplTest.kt | 122 ++++++++++++++++++ .../errorReporting/ErrorInfo.properties | 2 + .../errorReporting/ErrorInfo_en_US.properties | 2 + .../errorReporting/test-case-3.properties | 4 + .../test-case-3_en_US.properties | 4 + .../errorReporting/test-case1.properties | 4 + .../test-case1_en_US.properties | 3 + .../test-case1_es_ES.properties | 1 + .../test-case1_ga_IE.properties | 3 + .../errorReporting/test-case2.properties | 4 + .../test-case2_en_US.properties | 4 + .../test-case2_ga_IE.properties | 4 + node-api/build.gradle | 1 + .../internal/persistence/CordaPersistence.kt | 8 +- .../net/corda/node/internal/AbstractNode.kt | 16 ++- .../kotlin/net/corda/node/internal/Node.kt | 6 +- .../net/corda/node/internal/NodeStartup.kt | 10 ++ settings.gradle | 1 + tools/error-page-builder/build.gradle | 32 +++++ .../errorPageBuilder/ErrorPageBuilder.kt | 88 +++++++++++++ .../errorPageBuilder/ErrorTableGenerator.kt | 66 ++++++++++ .../resources/ErrorPageHeadings.properties | 4 + .../src/main/resources/log4j2.xml | 18 +++ .../test/kotlin/ErrorTableGeneratorTest.kt | 45 +++++++ .../resources/ErrorPageHeadings.properties | 4 + .../ErrorPageHeadings_ga_IE.properties | 4 + .../test-errors/test-error.properties | 4 + .../test-errors/test-error_ga_IE.properties | 3 + 52 files changed, 869 insertions(+), 5 deletions(-) create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/CordaErrorContextProvider.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCode.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCodes.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorContextProvider.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporter.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporterImpl.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingExceptions.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingUtils.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorResource.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt create mode 100644 common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ResourceBundleProperties.kt create mode 100644 common/logging/src/main/resources/error-codes/ErrorInfo.properties create mode 100644 common/logging/src/main/resources/error-codes/ErrorInfo_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/database-could-not-connect.properties create mode 100644 common/logging/src/main/resources/error-codes/database-could-not-connect_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/database-failed-startup.properties create mode 100644 common/logging/src/main/resources/error-codes/database-failed-startup_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/database-missing-driver.properties create mode 100644 common/logging/src/main/resources/error-codes/database-missing-driver_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/database-password-required-for-h2.properties create mode 100644 common/logging/src/main/resources/error-codes/database-password-required-for-h2_en_US.properties create mode 100644 common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordaErrorContextProviderTest.kt create mode 100644 common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt create mode 100644 common/logging/src/test/resources/errorReporting/ErrorInfo.properties create mode 100644 common/logging/src/test/resources/errorReporting/ErrorInfo_en_US.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case-3.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case-3_en_US.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case1.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case1_en_US.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case1_es_ES.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case1_ga_IE.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case2.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case2_en_US.properties create mode 100644 common/logging/src/test/resources/errorReporting/test-case2_ga_IE.properties create mode 100644 tools/error-page-builder/build.gradle create mode 100644 tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt create mode 100644 tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt create mode 100644 tools/error-page-builder/src/main/resources/ErrorPageHeadings.properties create mode 100644 tools/error-page-builder/src/main/resources/log4j2.xml create mode 100644 tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt create mode 100644 tools/error-page-builder/src/test/resources/ErrorPageHeadings.properties create mode 100644 tools/error-page-builder/src/test/resources/ErrorPageHeadings_ga_IE.properties create mode 100644 tools/error-page-builder/src/test/resources/test-errors/test-error.properties create mode 100644 tools/error-page-builder/src/test/resources/test-errors/test-error_ga_IE.properties diff --git a/common/logging/build.gradle b/common/logging/build.gradle index 91770cbabe..1d614efbc4 100644 --- a/common/logging/build.gradle +++ b/common/logging/build.gradle @@ -16,6 +16,8 @@ dependencies { compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version" testCompile project(":test-utils") + testCompile "com.nhaarman:mockito-kotlin:$mockito_kotlin_version" + testCompile "org.mockito:mockito-core:$mockito_version" } diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/CordaErrorContextProvider.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/CordaErrorContextProvider.kt new file mode 100644 index 0000000000..17c9cdba82 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/CordaErrorContextProvider.kt @@ -0,0 +1,31 @@ +package net.corda.common.logging.errorReporting + +import net.corda.common.logging.CordaVersion +import java.util.* + +/** + * Provides information specific to Corda to the error reporting library. + * + * The primary use of this is to provide the URL to the docs site where the error information is hosted. + */ +class CordaErrorContextProvider : ErrorContextProvider { + + companion object { + private const val BASE_URL = "https://docs.corda.net/docs" + private const val OS_PAGES = "corda-os" + private const val ENTERPRISE_PAGES = "corda-enterprise" + private const val ERROR_CODE_PAGE = "error-codes.html" + } + + override fun getURL(locale: Locale): String { + val versionNumber = CordaVersion.releaseVersion + + // This slightly strange block here allows the code to be merged across to Enterprise with no changes. + val productVersion = if (CordaVersion.platformEditionCode == "OS") { + OS_PAGES + } else { + ENTERPRISE_PAGES + } + return "$BASE_URL/$productVersion/$versionNumber/$ERROR_CODE_PAGE" + } +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCode.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCode.kt new file mode 100644 index 0000000000..9dfbd1a58c --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCode.kt @@ -0,0 +1,28 @@ +package net.corda.common.logging.errorReporting + +/** + * A type representing an error condition. + * + * Error codes should be used in situations where an error is expected and information can be provided back to the user about what they've + * done wrong. Each error code should have a resource bundle defined for it, which contains set of properties that define the error string + * in different languages. See the resource bundles in common/logging/src/main/resources/errorReporting for more details. + */ +interface ErrorCode where CODES: ErrorCodes, CODES: Enum { + + /** + * The error code. + * + * Error codes are used to indicate what sort of error occurred. A unique code should be returned for each possible + * error condition that could be reported within the defined namespace. The code should very briefly describe what has gone wrong, e.g. + * "failed-to-store" or "connection-unavailable". + */ + val code: CODES + + /** + * Parameters to pass to the string template when reporting this error. The corresponding template that defines the error string in the + * resource bundle must be expecting this list of parameters. Parameters should be in the order required by the message template - for + * example, if the message template is "This error has argument {0} and argument {1}", the first element of this list will be placed + * into {0}. + */ + val parameters: List +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCodes.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCodes.kt new file mode 100644 index 0000000000..216f041e12 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorCodes.kt @@ -0,0 +1,16 @@ +package net.corda.common.logging.errorReporting + +/** + * A collection of error codes. + * + * Types implementing this are required to be enum classes to be used in an error. + */ +interface ErrorCodes { + /** + * The namespace of this collection of errors. + * + * These are used to partition errors into categories, e.g. "database" or "cordapp". Namespaces should be unique, which can be enforced + * by using enum elements. + */ + val namespace: String +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorContextProvider.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorContextProvider.kt new file mode 100644 index 0000000000..dbd73aa5ca --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorContextProvider.kt @@ -0,0 +1,24 @@ +package net.corda.common.logging.errorReporting + +import java.util.* + +/** + * Provide context around reported errors by supplying product specific information. + */ +interface ErrorContextProvider { + + /** + * Get the URL to the docs site where the error codes are hosted. + * + * Note that the correct docs site link is likely to depend on the following: + * - The locale of the error message + * - The product the error was reported from + * - The version of the product the error was reported from + * + * The returned URL must be the link the to the error code table in the documentation. + * + * @param locale The locale of the link + * @return The URL of the docs site, to be printed in the logs + */ + fun getURL(locale: Locale) : String +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporter.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporter.kt new file mode 100644 index 0000000000..f28b31fb73 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporter.kt @@ -0,0 +1,16 @@ +package net.corda.common.logging.errorReporting + +import org.slf4j.Logger + +/** + * Reports error conditions to the logs, using localised error messages. + */ +internal interface ErrorReporter { + /** + * Report a particular error condition + * + * @param error The error to report + * @param logger The logger to use when reporting this error + */ + fun report(error: ErrorCode<*>, logger: Logger) +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporterImpl.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporterImpl.kt new file mode 100644 index 0000000000..ae6c8b9266 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporterImpl.kt @@ -0,0 +1,36 @@ +package net.corda.common.logging.errorReporting + +import org.slf4j.Logger +import java.text.MessageFormat +import java.util.* + +internal const val ERROR_INFO_RESOURCE = "ErrorInfo" +internal const val ERROR_CODE_MESSAGE = "errorCodeMessage" +internal const val ERROR_CODE_URL = "errorCodeUrl" + +internal class ErrorReporterImpl(private val resourceLocation: String, + private val locale: Locale, + private val errorContextProvider: ErrorContextProvider) : ErrorReporter { + + private fun fetchAndFormat(resource: String, property: String, params: Array) : String { + val bundle = ResourceBundle.getBundle(resource, locale) + val template = bundle.getString(property) + val formatter = MessageFormat(template, locale) + return formatter.format(params) + } + + // Returns the string appended to all reported errors, indicating the error code and the URL to go to. + // e.g. [Code: my-error-code, For further information, please go to https://docs.corda.net/corda-os/4.5/error-codes.html] + private fun getErrorInfo(error: ErrorCode<*>) : String { + val resource = "$resourceLocation/$ERROR_INFO_RESOURCE" + val codeMessage = fetchAndFormat(resource, ERROR_CODE_MESSAGE, arrayOf(error.formatCode())) + val urlMessage = fetchAndFormat(resource, ERROR_CODE_URL, arrayOf(errorContextProvider.getURL(locale))) + return "[$codeMessage, $urlMessage]" + } + + override fun report(error: ErrorCode<*>, logger: Logger) { + val errorResource = ErrorResource.fromErrorCode(error, resourceLocation, locale) + val message = "${errorResource.getErrorMessage(error.parameters.toTypedArray())} ${getErrorInfo(error)}" + logger.error(message) + } +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt new file mode 100644 index 0000000000..b58557d67e --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt @@ -0,0 +1,66 @@ +package net.corda.common.logging.errorReporting + +import java.lang.UnsupportedOperationException +import java.util.* + +/** + * Entry point into the Error Reporting framework. + * + * This creates the error reporter used to report errors. The `initialiseReporting` method should be called to build a reporter before any + * errors are reported. + */ +class ErrorReporting private constructor(private val localeString: String, + private val resourceLocation: String, + private val contextProvider: ErrorContextProvider?) { + + constructor() : this(DEFAULT_LOCALE, DEFAULT_LOCATION, null) + + private companion object { + private const val DEFAULT_LOCALE = "en-US" + private const val DEFAULT_LOCATION = "." + + private var errorReporter: ErrorReporter? = null + } + + /** + * Set the locale to use when reporting errors + * + * @param locale The locale tag to use when reporting errors, e.g. en-US + */ + @Suppress("UNUSED_PARAMETER") + fun withLocale(locale: String) : ErrorReporting { + // Currently, if anything other than the default is used this is entirely untested. As a result, an exception is thrown here to + // indicate that this functionality is not ready at this point in time. + throw LocaleSettingUnsupportedException() + } + + /** + * Set the location of the resource bundles containing the error codes. + * + * @param location The location within the JAR of the resource bundle + */ + fun usingResourcesAt(location: String) : ErrorReporting { + return ErrorReporting(localeString, location, contextProvider) + } + + fun withContextProvider(contextProvider: ErrorContextProvider) : ErrorReporting { + return ErrorReporting(localeString, resourceLocation, contextProvider) + } + + /** + * Set up the reporting of errors. + */ + fun initialiseReporting() { + if (contextProvider == null) { + throw NoContextProviderSuppliedException() + } + if (errorReporter != null) { + throw DoubleInitializationException() + } + errorReporter = ErrorReporterImpl(resourceLocation, Locale.forLanguageTag(localeString), contextProvider) + } + + internal fun getReporter() : ErrorReporter { + return errorReporter ?: throw ReportingUninitializedException() + } +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingExceptions.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingExceptions.kt new file mode 100644 index 0000000000..3cbbaed720 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingExceptions.kt @@ -0,0 +1,27 @@ +package net.corda.common.logging.errorReporting + +abstract class ErrorReportingException(message: String, cause: Throwable? = null) : Exception(message, cause) + +/** + * Occurs when reporting is requested before the error reporting code has been initialized + */ +class ReportingUninitializedException : ErrorReportingException("Error reporting is uninitialized") + +/** + * Occurs when no error context provider is supplied while initializing error reporting + */ +class NoContextProviderSuppliedException + : ErrorReportingException("No error context provider was supplied when initializing error reporting") + +/** + * Occurs if the error reporting framework has been initialized twice + */ +class DoubleInitializationException : ErrorReportingException("Error reporting has previously been initialized") + +/** + * Occurs if a locale is set while initializing the error reporting framework. + * + * This is done as locale support has not yet been properly designed, and so using anything other than the default is untested. + */ +class LocaleSettingUnsupportedException : + ErrorReportingException("Setting a locale other than the default is not supported in the first release") \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingUtils.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingUtils.kt new file mode 100644 index 0000000000..827a78c450 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReportingUtils.kt @@ -0,0 +1,18 @@ +package net.corda.common.logging.errorReporting + +import org.slf4j.Logger + +/** + * Report errors that have occurred. + * + * Doing this allows the error reporting framework to find the corresponding resources for the error and pick the correct locale. + * + * @param error The error that has occurred. + */ +fun Logger.report(error: ErrorCode<*>) = ErrorReporting().getReporter().report(error, this) + +internal fun ErrorCode<*>.formatCode() : String { + val namespaceString = this.code.namespace.toLowerCase().replace("_", "-") + val codeString = this.code.toString().toLowerCase().replace("_", "-") + return "$namespaceString-$codeString" +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorResource.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorResource.kt new file mode 100644 index 0000000000..b42643bc3a --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorResource.kt @@ -0,0 +1,60 @@ +package net.corda.common.logging.errorReporting + +import net.corda.common.logging.errorReporting.ResourceBundleProperties.ACTIONS_TO_FIX +import net.corda.common.logging.errorReporting.ResourceBundleProperties.ALIASES +import net.corda.common.logging.errorReporting.ResourceBundleProperties.MESSAGE_TEMPLATE +import net.corda.common.logging.errorReporting.ResourceBundleProperties.SHORT_DESCRIPTION +import java.text.MessageFormat +import java.util.* + +/** + * A representation of a single error resource file. + * + * This handles selecting the right properties from the resource bundle and formatting the error message. + */ +class ErrorResource private constructor(private val bundle: ResourceBundle, + private val locale: Locale) { + + companion object { + /** + * Construct an error resource from a provided code. + * + * @param errorCode The code to get the resource bundle for + * @param resourceLocation The location in the JAR of the error code resource bundles + * @param locale The locale to use for this resource + */ + fun fromErrorCode(errorCode: ErrorCode<*>, resourceLocation: String, locale: Locale) : ErrorResource { + val resource = "$resourceLocation/${errorCode.formatCode()}" + val bundle = ResourceBundle.getBundle(resource, locale) + return ErrorResource(bundle, locale) + } + + /** + * Construct an error resource using resources loaded in a given classloader + * + * @param resource The resource bundle to load + * @param classLoader The classloader used to load the resource bundles + * @param locale The locale to use for this resource + */ + fun fromLoader(resource: String, classLoader: ClassLoader, locale: Locale) : ErrorResource { + val bundle = ResourceBundle.getBundle(resource, locale, classLoader) + return ErrorResource(bundle, locale) + } + } + + private fun getProperty(propertyName: String) : String = bundle.getString(propertyName) + + private fun formatTemplate(template: String, args: Array) : String { + val formatter = MessageFormat(template, locale) + return formatter.format(args) + } + + fun getErrorMessage(args: Array): String { + val template = getProperty(MESSAGE_TEMPLATE) + return formatTemplate(template, args) + } + + val shortDescription: String = getProperty(SHORT_DESCRIPTION) + val actionsToFix: String = getProperty(ACTIONS_TO_FIX) + val aliases: String = getProperty(ALIASES) +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt new file mode 100644 index 0000000000..83e324b893 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt @@ -0,0 +1,20 @@ +package net.corda.common.logging.errorReporting + +/** + * Namespaces for errors within the node. + */ +enum class NodeNamespaces { + DATABASE +} + +/** + * Errors related to database connectivity + */ +enum class NodeDatabaseErrors : ErrorCodes { + COULD_NOT_CONNECT, + MISSING_DRIVER, + FAILED_STARTUP, + PASSWORD_REQUIRED_FOR_H2; + + override val namespace = NodeNamespaces.DATABASE.toString() +} \ No newline at end of file diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ResourceBundleProperties.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ResourceBundleProperties.kt new file mode 100644 index 0000000000..7e53dc62f4 --- /dev/null +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ResourceBundleProperties.kt @@ -0,0 +1,11 @@ +package net.corda.common.logging.errorReporting + +/** + * Constants defining the properties available in the resource bundle files. + */ +object ResourceBundleProperties { + const val MESSAGE_TEMPLATE = "errorTemplate" + const val SHORT_DESCRIPTION = "shortDescription" + const val ACTIONS_TO_FIX = "actionsToFix" + const val ALIASES = "aliases" +} \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/ErrorInfo.properties b/common/logging/src/main/resources/error-codes/ErrorInfo.properties new file mode 100644 index 0000000000..2ce5490052 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/ErrorInfo.properties @@ -0,0 +1,2 @@ +errorCodeMessage = Error Code: {0} +errorCodeUrl = For further information, please go to {0} \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/ErrorInfo_en_US.properties b/common/logging/src/main/resources/error-codes/ErrorInfo_en_US.properties new file mode 100644 index 0000000000..2ce5490052 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/ErrorInfo_en_US.properties @@ -0,0 +1,2 @@ +errorCodeMessage = Error Code: {0} +errorCodeUrl = For further information, please go to {0} \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-could-not-connect.properties b/common/logging/src/main/resources/error-codes/database-could-not-connect.properties new file mode 100644 index 0000000000..d2c349f8ca --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-could-not-connect.properties @@ -0,0 +1,4 @@ +errorTemplate = Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database. +shortDescription = The node failed to connect to the database on node startup, preventing the node from starting correctly. +actionsToFix = This happens either because the database connection has been misconfigured or the database is unreachable. Check that the JDBC URL is configured correctly in your node.conf. If this is correctly configured, then check your database connection. +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-could-not-connect_en_US.properties b/common/logging/src/main/resources/error-codes/database-could-not-connect_en_US.properties new file mode 100644 index 0000000000..bfd5973209 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-could-not-connect_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database. +shortDescription = The node failed to connect to the database on node startup, preventing the node from starting correctly. +actionsToFix = This happens either because the database connection has been misconfigured or the database is unreachable. Check that the JDBC URL is configured correctly in your node.conf. If this is correctly configured, then check your database connection. \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-failed-startup.properties b/common/logging/src/main/resources/error-codes/database-failed-startup.properties new file mode 100644 index 0000000000..996de3ba76 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-failed-startup.properties @@ -0,0 +1,4 @@ +errorTemplate = Failed to create the datasource. See the logs for further information and the cause. +shortDescription = The datasource could not be created for unknown reasons. +actionsToFix = The logs in the logs directory should contain more information on what went wrong. +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-failed-startup_en_US.properties b/common/logging/src/main/resources/error-codes/database-failed-startup_en_US.properties new file mode 100644 index 0000000000..1abe8840bb --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-failed-startup_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = Failed to create the datasource. See the logs for further information and the cause. +shortDescription = The datasource could not be created for unknown reasons. +actionsToFix = The logs in the logs directory should contain more information on what went wrong. \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-missing-driver.properties b/common/logging/src/main/resources/error-codes/database-missing-driver.properties new file mode 100644 index 0000000000..dfb6b460db --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-missing-driver.properties @@ -0,0 +1,4 @@ +errorTemplate = Could not find the database driver class. Please add it to the 'drivers' folder. +shortDescription = The node could not find the driver in the 'drivers' directory. +actionsToFix = Please ensure that the correct database driver has been placed in the 'drivers' folder. The driver must contain the driver main class specified in 'node.conf'. +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-missing-driver_en_US.properties b/common/logging/src/main/resources/error-codes/database-missing-driver_en_US.properties new file mode 100644 index 0000000000..c52708a02b --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-missing-driver_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = Could not find the database driver class. Please add it to the 'drivers' folder. +shortDescription = The node could not find the driver in the 'drivers' directory. +actionsToFix = Please ensure that the correct database driver has been placed in the 'drivers' folder. The driver must contain the driver main class specified in 'node.conf'. \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-password-required-for-h2.properties b/common/logging/src/main/resources/error-codes/database-password-required-for-h2.properties new file mode 100644 index 0000000000..7284fa125e --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-password-required-for-h2.properties @@ -0,0 +1,4 @@ +errorTemplate = Database password is required for H2 server listening on {0} +shortDescription = A password is required to access the H2 server the node is trying to access, and this password is missing. +actionsToFix = Add the required password to the 'datasource.password' configuration in 'node.conf'. +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/database-password-required-for-h2_en_US.properties b/common/logging/src/main/resources/error-codes/database-password-required-for-h2_en_US.properties new file mode 100644 index 0000000000..3afefc34d9 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/database-password-required-for-h2_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = Database password is required for H2 server listening on {0} +shortDescription = A password is required to access the H2 server the node is trying to access, and this password is missing. +actionsToFix = Add the required password to the 'datasource.password' configuration in 'node.conf'. \ No newline at end of file diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordaErrorContextProviderTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordaErrorContextProviderTest.kt new file mode 100644 index 0000000000..85dcf4d73b --- /dev/null +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordaErrorContextProviderTest.kt @@ -0,0 +1,20 @@ +package net.corda.commmon.logging.errorReporting + +import net.corda.common.logging.CordaVersion +import net.corda.common.logging.errorReporting.CordaErrorContextProvider +import org.junit.Test +import java.util.* +import kotlin.test.assertEquals + +class CordaErrorContextProviderTest { + + @Test(timeout = 300_000) + fun `check that correct URL is returned from context provider`() { + val context = CordaErrorContextProvider() + val expectedURL = "https://docs.corda.net/docs/corda-os/${CordaVersion.releaseVersion}/error-codes.html" + // In this first release, there is only one localisation and the URL structure for future localisations is currently unknown. As + // a result, the same URL is expected for all locales. + assertEquals(expectedURL, context.getURL(Locale.getDefault())) + assertEquals(expectedURL, context.getURL(Locale.forLanguageTag("es-ES"))) + } +} \ No newline at end of file diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt new file mode 100644 index 0000000000..9971f8c60d --- /dev/null +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt @@ -0,0 +1,122 @@ +package net.corda.commmon.logging.errorReporting + +import junit.framework.TestCase.assertEquals +import net.corda.common.logging.errorReporting.ErrorCode +import net.corda.common.logging.errorReporting.ErrorCodes +import net.corda.common.logging.errorReporting.ErrorContextProvider +import net.corda.common.logging.errorReporting.ErrorReporterImpl +import org.junit.After +import org.junit.Test +import org.junit.rules.TestName +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito +import org.slf4j.Logger +import java.text.DateFormat +import java.time.Instant +import java.util.* + +class ErrorReporterImplTest { + + companion object { + private const val TEST_URL = "test-url" + } + + private val logs: MutableList = mutableListOf() + + private val loggerMock = Mockito.mock(Logger::class.java).also { + Mockito.`when`(it.error(anyString())).then { logs.addAll(it.arguments) } + } + + private val contextProvider: ErrorContextProvider = object : ErrorContextProvider { + override fun getURL(locale: Locale): String { + return "$TEST_URL/${locale.toLanguageTag()}" + } + } + + private enum class TestNamespaces { + TEST + } + + private enum class TestErrors : ErrorCodes { + CASE1, + CASE2, + CASE_3; + + override val namespace = TestNamespaces.TEST.toString() + } + + private val TEST_ERROR_1 = object : ErrorCode { + override val code = TestErrors.CASE1 + override val parameters = listOf() + } + + private class TestError2(currentDate: Date) : ErrorCode { + override val code = TestErrors.CASE2 + override val parameters: List = listOf("foo", 1, currentDate) + } + + private val TEST_ERROR_3 = object : ErrorCode { + override val code = TestErrors.CASE_3 + override val parameters = listOf() + } + + private fun createReporterImpl(localeTag: String?) : ErrorReporterImpl { + val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault() + return ErrorReporterImpl("errorReporting", locale, contextProvider) + } + + @After + fun tearDown() { + logs.clear() + } + + @Test(timeout = 300_000) + fun `error codes logged correctly`() { + val error = TEST_ERROR_1 + val testReporter = createReporterImpl("en-US") + testReporter.report(error, loggerMock) + assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/en-US]"), logs) + } + + @Test(timeout = 300_00) + fun `error code with parameters correctly reported`() { + val currentDate = Date.from(Instant.now()) + val error = TestError2(currentDate) + val testReporter = createReporterImpl("en-US") + testReporter.report(error, loggerMock) + val format = DateFormat.getDateInstance(DateFormat.LONG, Locale.forLanguageTag("en-US")) + assertEquals(listOf("This is the second case with string foo, number 1, date ${format.format(currentDate)} [Code: test-case2, URL: $TEST_URL/en-US]"), logs) + } + + @Test(timeout = 300_000) + fun `locale used with no corresponding resource falls back to default`() { + val error = TEST_ERROR_1 + val testReporter = createReporterImpl("fr-FR") + testReporter.report(error, loggerMock) + assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/fr-FR]"), logs) + } + + @Test(timeout = 300_000) + fun `locale with corresponding resource causes correct error to be printed`() { + val error = TEST_ERROR_1 + val testReporter = createReporterImpl("ga-IE") + testReporter.report(error, loggerMock) + assertEquals(listOf("Is teachtaireacht earráide é seo [Code: test-case1, URL: $TEST_URL/ga-IE]"), logs) + } + + @Test(timeout = 300_000) + fun `locale with missing properties falls back to default properties`() { + val error = TEST_ERROR_1 + val testReporter = createReporterImpl("es-ES") + testReporter.report(error, loggerMock) + assertEquals(listOf("This is a test message [Code: test-case1, URL: $TEST_URL/es-ES]"), logs) + } + + @Test(timeout = 300_000) + fun `error code with underscore in translated to resource file successfully`() { + val error = TEST_ERROR_3 + val testReporter = createReporterImpl("en-US") + testReporter.report(error, loggerMock) + assertEquals(listOf("This is the third test message [Code: test-case-3, URL: $TEST_URL/en-US]"), logs) + } +} \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/ErrorInfo.properties b/common/logging/src/test/resources/errorReporting/ErrorInfo.properties new file mode 100644 index 0000000000..79bb3660a3 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/ErrorInfo.properties @@ -0,0 +1,2 @@ +errorCodeMessage = Code: {0} +errorCodeUrl = URL: {0} \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/ErrorInfo_en_US.properties b/common/logging/src/test/resources/errorReporting/ErrorInfo_en_US.properties new file mode 100644 index 0000000000..79bb3660a3 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/ErrorInfo_en_US.properties @@ -0,0 +1,2 @@ +errorCodeMessage = Code: {0} +errorCodeUrl = URL: {0} \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case-3.properties b/common/logging/src/test/resources/errorReporting/test-case-3.properties new file mode 100644 index 0000000000..f85dfb98fd --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case-3.properties @@ -0,0 +1,4 @@ +errorTemplate = This is the third test message +shortDescription = Test description +actionsToFix = Actions +aliases = \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case-3_en_US.properties b/common/logging/src/test/resources/errorReporting/test-case-3_en_US.properties new file mode 100644 index 0000000000..f85dfb98fd --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case-3_en_US.properties @@ -0,0 +1,4 @@ +errorTemplate = This is the third test message +shortDescription = Test description +actionsToFix = Actions +aliases = \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case1.properties b/common/logging/src/test/resources/errorReporting/test-case1.properties new file mode 100644 index 0000000000..c0b3c34aa0 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case1.properties @@ -0,0 +1,4 @@ +errorTemplate = This is a test message +shortDescription = Test description +actionsToFix = Actions +aliases = foo, bar \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case1_en_US.properties b/common/logging/src/test/resources/errorReporting/test-case1_en_US.properties new file mode 100644 index 0000000000..69bbaa2176 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case1_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = This is a test message +shortDescription = Test description +actionsToFix = Actions \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case1_es_ES.properties b/common/logging/src/test/resources/errorReporting/test-case1_es_ES.properties new file mode 100644 index 0000000000..a0432b4fcb --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case1_es_ES.properties @@ -0,0 +1 @@ +shortDescription = Descripción de la prueba \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case1_ga_IE.properties b/common/logging/src/test/resources/errorReporting/test-case1_ga_IE.properties new file mode 100644 index 0000000000..59c5f19ae0 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case1_ga_IE.properties @@ -0,0 +1,3 @@ +errorTemplate = Is teachtaireacht earráide é seo +shortDescription = Teachtaireacht tástála +actionsToFix = Roinnt gníomhartha \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case2.properties b/common/logging/src/test/resources/errorReporting/test-case2.properties new file mode 100644 index 0000000000..3b5fb55744 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case2.properties @@ -0,0 +1,4 @@ +errorTemplate = This is the second case with string {0}, number {1, number, integer}, date {2, date, long} +shortDescription = Test description +actionsToFix = Actions +aliases = "" \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case2_en_US.properties b/common/logging/src/test/resources/errorReporting/test-case2_en_US.properties new file mode 100644 index 0000000000..b5faea14dc --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case2_en_US.properties @@ -0,0 +1,4 @@ +errorTemplate = This is the second case with string {0}, number {1, number, integer}, date {2, date, long} +shortDescription = Test description +actionsToFix = Actions +aliases = \ No newline at end of file diff --git a/common/logging/src/test/resources/errorReporting/test-case2_ga_IE.properties b/common/logging/src/test/resources/errorReporting/test-case2_ga_IE.properties new file mode 100644 index 0000000000..bef917eb18 --- /dev/null +++ b/common/logging/src/test/resources/errorReporting/test-case2_ga_IE.properties @@ -0,0 +1,4 @@ +errorTemplate = Seo an dara cás le sreang {0}, uimhir {1, uimhir, slánuimhir}, dáta {2, dáta, fada} +shortDescription = An dara tuairisc +actionsToFix = Roinnt gníomhartha eile +aliases = \ No newline at end of file diff --git a/node-api/build.gradle b/node-api/build.gradle index 2f5f774b9a..cdf79e772c 100644 --- a/node-api/build.gradle +++ b/node-api/build.gradle @@ -9,6 +9,7 @@ dependencies { compile project(":core") compile project(":serialization") // TODO Remove this once the NetworkBootstrapper class is moved into the tools:bootstrapper module compile project(':common-configuration-parsing') // TODO Remove this dependency once NetworkBootsrapper is moved into tools:bootstrapper + compile project(':common-logging') compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 4efaff19b3..2671468856 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -4,10 +4,13 @@ import co.paralleluniverse.strands.Strand import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.pool.HikariPool import com.zaxxer.hikari.util.ConcurrentBag +import net.corda.common.logging.errorReporting.ErrorCode import net.corda.core.flows.HospitalizeFlowException import net.corda.core.internal.NamedCacheFactory import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger +import net.corda.common.logging.errorReporting.NodeDatabaseErrors +import net.corda.common.logging.errorReporting.NodeNamespaces import org.hibernate.tool.schema.spi.SchemaManagementException import rx.Observable import rx.Subscriber @@ -405,7 +408,10 @@ private fun Throwable.hasSQLExceptionCause(): Boolean = else -> cause?.hasSQLExceptionCause() ?: false } -class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception() +class CouldNotCreateDataSourceException(override val message: String?, + override val code: NodeDatabaseErrors, + override val parameters: List = listOf(), + override val cause: Throwable? = null) : ErrorCode, Exception() class HibernateSchemaChangeException(override val message: String?, override val cause: Throwable? = null): Exception() diff --git a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt index 3141b71bbc..680685a53c 100644 --- a/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt +++ b/node/src/main/kotlin/net/corda/node/internal/AbstractNode.kt @@ -152,6 +152,7 @@ import net.corda.nodeapi.internal.crypto.X509Utilities.DISTRIBUTED_NOTARY_KEY_AL import net.corda.nodeapi.internal.crypto.X509Utilities.NODE_IDENTITY_KEY_ALIAS import net.corda.node.utilities.cryptoservice.CryptoServiceFactory import net.corda.node.utilities.cryptoservice.SupportedCryptoServices +import net.corda.common.logging.errorReporting.NodeDatabaseErrors import net.corda.nodeapi.internal.cryptoservice.bouncycastle.BCCryptoService import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEvent import net.corda.nodeapi.internal.lifecycle.NodeLifecycleEventsDistributor @@ -1302,10 +1303,19 @@ fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfi start(dataSource) } catch (ex: Exception) { when { - ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException("Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.", ex) - ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException("Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html") + ex is HikariPool.PoolInitializationException -> throw CouldNotCreateDataSourceException( + "Could not connect to the database. Please check your JDBC connection URL, or the connectivity to the database.", + NodeDatabaseErrors.COULD_NOT_CONNECT, + cause = ex) + ex.cause is ClassNotFoundException -> throw CouldNotCreateDataSourceException( + "Could not find the database driver class. Please add it to the 'drivers' folder. See: https://docs.corda.net/corda-configuration-file.html", + NodeDatabaseErrors.MISSING_DRIVER) ex is OutstandingDatabaseChangesException -> throw (DatabaseIncompatibleException(ex.message)) - else -> throw CouldNotCreateDataSourceException("Could not create the DataSource: ${ex.message}", ex) + else -> + throw CouldNotCreateDataSourceException( + "Could not create the DataSource: ${ex.message}", + NodeDatabaseErrors.FAILED_STARTUP, + cause = ex) } } } diff --git a/node/src/main/kotlin/net/corda/node/internal/Node.kt b/node/src/main/kotlin/net/corda/node/internal/Node.kt index eee9e063f0..1ed600f1ed 100644 --- a/node/src/main/kotlin/net/corda/node/internal/Node.kt +++ b/node/src/main/kotlin/net/corda/node/internal/Node.kt @@ -73,6 +73,7 @@ import net.corda.node.utilities.DefaultNamedCacheFactory import net.corda.node.utilities.DemoClock import net.corda.node.utilities.errorAndTerminate import net.corda.nodeapi.internal.ArtemisMessagingClient +import net.corda.common.logging.errorReporting.NodeDatabaseErrors import net.corda.nodeapi.internal.ShutdownHook import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.bridging.BridgeControlListener @@ -503,7 +504,10 @@ open class Node(configuration: NodeConfiguration, if (effectiveH2Settings?.address != null) { if (!InetAddress.getByName(effectiveH2Settings.address.host).isLoopbackAddress && configuration.dataSourceProperties.getProperty("dataSource.password").isBlank()) { - throw CouldNotCreateDataSourceException("Database password is required for H2 server listening on ${InetAddress.getByName(effectiveH2Settings.address.host)}.") + throw CouldNotCreateDataSourceException( + "Database password is required for H2 server listening on ${InetAddress.getByName(effectiveH2Settings.address.host)}.", + NodeDatabaseErrors.PASSWORD_REQUIRED_FOR_H2, + listOf(InetAddress.getByName(effectiveH2Settings.address.host).toString())) } val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';') val baseDir = Paths.get(databaseName).parent.toString() diff --git a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt index 01b62d0a1b..5c5e13e5ce 100644 --- a/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt +++ b/node/src/main/kotlin/net/corda/node/internal/NodeStartup.kt @@ -6,6 +6,8 @@ import net.corda.cliutils.CordaCliWrapper import net.corda.cliutils.ExitCodes import net.corda.cliutils.printError import net.corda.common.logging.CordaVersion +import net.corda.common.logging.errorReporting.CordaErrorContextProvider +import net.corda.common.logging.errorReporting.ErrorCode import net.corda.core.contracts.HashAttachmentConstraint import net.corda.core.crypto.Crypto import net.corda.core.internal.* @@ -16,6 +18,8 @@ import net.corda.core.utilities.Try import net.corda.core.utilities.contextLogger import net.corda.core.utilities.loggerFor import net.corda.node.* +import net.corda.common.logging.errorReporting.ErrorReporting +import net.corda.common.logging.errorReporting.report import net.corda.node.internal.Node.Companion.isInvalidJavaVersion import net.corda.node.internal.cordapp.MultipleCordappsForFlowException import net.corda.node.internal.subcommands.* @@ -140,6 +144,7 @@ open class NodeStartup : NodeStartupLogging { private val logger by lazy { loggerFor() } // I guess this is lazy to allow for logging init, but why Node? const val LOGS_DIRECTORY_NAME = "logs" const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in" + const val ERROR_CODE_RESOURCE_LOCATION = "error-codes" } lateinit var cmdLineOptions: SharedNodeCmdLineOptions @@ -169,6 +174,10 @@ open class NodeStartup : NodeStartupLogging { ?: return ExitCodes.FAILURE val configuration = cmdLineOptions.parseConfiguration(rawConfig).doIfValid { logRawConfig(rawConfig) }.doOnErrors(::logConfigurationErrors).optional ?: return ExitCodes.FAILURE + ErrorReporting() + .usingResourcesAt(ERROR_CODE_RESOURCE_LOCATION) + .withContextProvider(CordaErrorContextProvider()) + .initialiseReporting() // Step 6. Check if we can access the certificates directory if (requireCertificates && !canReadCertificatesDirectory(configuration.certificatesDirectory, configuration.devMode)) return ExitCodes.FAILURE @@ -482,6 +491,7 @@ interface NodeStartupLogging { fun handleStartError(error: Throwable) { when { + error is ErrorCode<*> -> logger.report(error) error.isExpectedWhenStartingNode() -> error.logAsExpected() error is CouldNotCreateDataSourceException -> error.logAsUnexpected() error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.") diff --git a/settings.gradle b/settings.gradle index 2fbb64d38e..f3ec7e75d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -108,4 +108,5 @@ include 'serialization-deterministic' include 'tools:checkpoint-agent' include 'detekt-plugins' +include 'tools:error-page-builder' diff --git a/tools/error-page-builder/build.gradle b/tools/error-page-builder/build.gradle new file mode 100644 index 0000000000..ed16c572b3 --- /dev/null +++ b/tools/error-page-builder/build.gradle @@ -0,0 +1,32 @@ +group 'net.corda' +version '4.5-SNAPSHOT' + +apply plugin: 'kotlin' +apply plugin: 'com.github.johnrengelman.shadow' + +repositories { + mavenCentral() +} + +dependencies { + implementation project(":common-logging") + implementation project(":tools:cliutils") + implementation "info.picocli:picocli:$picocli_version" + testCompile group: 'junit', name: 'junit', version: '4.12' + + implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" +} + +jar { + enabled = false + classifier = 'ignore' +} + +shadowJar { + baseName = "corda-tools-error-page-builder" + manifest { + attributes( + 'Main-Class': "net.corda.errorPageBuilder.ErrorPageBuilderKt" + ) + } +} \ No newline at end of file diff --git a/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt b/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt new file mode 100644 index 0000000000..abe122310d --- /dev/null +++ b/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt @@ -0,0 +1,88 @@ +package net.corda.errorPageBuilder + +import net.corda.cliutils.CordaCliWrapper +import net.corda.cliutils.ExitCodes +import net.corda.cliutils.start +import org.slf4j.LoggerFactory +import picocli.CommandLine +import java.io.File +import java.lang.IllegalArgumentException +import java.nio.file.Files +import java.nio.file.Path +import java.util.* + +fun main(args: Array) { + val builder = ErrorPageBuilder() + builder.start(args) +} + +class ErrorPageBuilder : CordaCliWrapper("error-page-builder", "Builds the error table for the error codes page") { + + @CommandLine.Parameters( + index = "0", + paramLabel = "OUTPUT_LOCATION", + arity = "1", + description = ["The file system location to output the error codes documentation table"] + ) + var outputDir: Path? = null + + @CommandLine.Parameters( + index = "1", + paramLabel = "RESOURCE_LOCATION", + arity = "1", + description = ["The file system location of the resource files to process"] + ) + var resourceLocation: Path? = null + + @CommandLine.Option( + names = ["--locale-tag"], + description = ["The locale tag of the locale to use when localising the error codes table. For example, en-US"], + arity = "1" + ) + var localeTag: String? = null + + companion object { + private val logger = LoggerFactory.getLogger(ErrorPageBuilder::class.java) + private const val ERROR_CODES_FILE = "error-codes.md" + } + + private fun getOutputFile() : File { + return outputDir?.let { + require(Files.exists(it)) { + "Directory $it does not exist. Please specify a valid directory to write output to." + } + val outputPath = it.resolve(ERROR_CODES_FILE) + require(Files.notExists(outputPath)) { + "Output file $outputPath exists, please remove it and run again." + } + outputPath.toFile() + } ?: throw IllegalArgumentException("Directory not specified. Please specify a valid directory to write output to.") + } + + private fun getResourceDir() : Path { + return resourceLocation?.also { + require(Files.exists(it)) { + "Resource location $it does not exist. Please specify a valid location for error code resources" + } + } ?: throw IllegalArgumentException("Resource location not specified. Please specify a resource location.") + } + + override fun runProgram(): Int { + val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault() + val (outputFile, resources) = try { + Pair(getOutputFile(), getResourceDir()) + } catch (e: IllegalArgumentException) { + logger.error(e.message, e) + return ExitCodes.FAILURE + } + val tableGenerator = ErrorTableGenerator(resources.toFile(), locale) + try { + val table = tableGenerator.generateMarkdown() + outputFile.writeText(table) + } catch (e: IllegalArgumentException) { + logger.error(e.message, e) + return ExitCodes.FAILURE + } + return ExitCodes.SUCCESS + } +} \ No newline at end of file diff --git a/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt b/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt new file mode 100644 index 0000000000..6cf13436a2 --- /dev/null +++ b/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt @@ -0,0 +1,66 @@ +package net.corda.errorPageBuilder + +import net.corda.common.logging.errorReporting.ErrorResource +import java.io.File +import java.lang.IllegalArgumentException +import java.net.URLClassLoader +import java.util.* + +class ErrorTableGenerator(private val resourceLocation: File, + private val locale: Locale) { + + companion object { + private const val ERROR_CODE_HEADING = "codeHeading" + private const val ALIASES_HEADING = "aliasesHeading" + private const val DESCRIPTION_HEADING = "descriptionHeading" + private const val TO_FIX_HEADING = "toFixHeading" + private const val ERROR_HEADINGS_BUNDLE = "ErrorPageHeadings" + private const val ERROR_INFO_RESOURCE = "ErrorInfo.properties" + } + + private fun getHeading(heading: String) : String { + val resource = ResourceBundle.getBundle(ERROR_HEADINGS_BUNDLE, locale) + return resource.getString(heading) + } + + private fun listResources() : Iterator { + return resourceLocation.walkTopDown().filter { + it.name.matches("[^_]*\\.properties".toRegex()) && !it.name.matches(ERROR_INFO_RESOURCE.toRegex()) + }.map { + it.nameWithoutExtension + }.iterator() + } + + private fun createLoader() : ClassLoader { + val urls = resourceLocation.walkTopDown().map { it.toURI().toURL() }.asIterable().toList().toTypedArray() + return URLClassLoader(urls) + } + + private fun generateTable() : List> { + val table = mutableListOf>() + val loader = createLoader() + for (resource in listResources()) { + val errorResource = ErrorResource.fromLoader(resource, loader, locale) + table.add(listOf(resource, errorResource.aliases, errorResource.shortDescription, errorResource.actionsToFix)) + } + return table + } + + private fun formatTable(tableData: List>) : String { + val headings = listOf( + getHeading(ERROR_CODE_HEADING), + getHeading(ALIASES_HEADING), + getHeading(DESCRIPTION_HEADING), + getHeading(TO_FIX_HEADING) + ) + val underlines = headings.map { "-".repeat(it.length) } + val fullTable = listOf(headings, underlines) + tableData + return fullTable.joinToString(System.lineSeparator()) { it.joinToString(prefix = "| ", postfix = " |", separator = " | ") } + } + + fun generateMarkdown() : String { + if (!resourceLocation.exists()) throw IllegalArgumentException("Directory $resourceLocation does not exist.") + val tableData = generateTable() + return formatTable(tableData) + } +} \ No newline at end of file diff --git a/tools/error-page-builder/src/main/resources/ErrorPageHeadings.properties b/tools/error-page-builder/src/main/resources/ErrorPageHeadings.properties new file mode 100644 index 0000000000..5741987c8a --- /dev/null +++ b/tools/error-page-builder/src/main/resources/ErrorPageHeadings.properties @@ -0,0 +1,4 @@ +codeHeading = Error Code +aliasesHeading = Aliases +descriptionHeading = Description +toFixHeading = Actions to Fix \ No newline at end of file diff --git a/tools/error-page-builder/src/main/resources/log4j2.xml b/tools/error-page-builder/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..be842b0fa0 --- /dev/null +++ b/tools/error-page-builder/src/main/resources/log4j2.xml @@ -0,0 +1,18 @@ + + + + ${sys:defaultLogLevel:-info} + ${sys:consoleLogLevel:-error} + + + + + + + + + + + + + diff --git a/tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt b/tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt new file mode 100644 index 0000000000..211576dda1 --- /dev/null +++ b/tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt @@ -0,0 +1,45 @@ +import junit.framework.TestCase.assertEquals +import net.corda.errorPageBuilder.ErrorTableGenerator +import org.junit.Test +import java.io.File +import java.lang.IllegalArgumentException +import java.nio.file.Paths +import java.util.* + +class ErrorTableGeneratorTest { + + companion object { + private val RESOURCE_LOCATION = Paths.get("src/test/resources/test-errors").toAbsolutePath().toFile() + } + + private val englishTable = """| Error Code | Aliases | Description | Actions to Fix | + /| ---------- | ------- | ----------- | -------------- | + /| test-error | foo, bar | Test description | Actions | + """.trimMargin("/") + + private val irishTable = """| Cód Earráide | Ailiasanna | Cur síos | Caingne le Deisiú | + /| ------------ | ---------- | -------- | ----------------- | + /| test-error | foo, bar | Teachtaireacht tástála | Roinnt gníomhartha | + """.trimMargin("/") + + @Test(timeout = 300_000) + fun `check error table is produced as expected`() { + val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("en-US")) + val table = generator.generateMarkdown() + // Raw strings in Kotlin always use Unix line endings, so this is required to keep the test passing on Windows + assertEquals(englishTable.split("\n").joinToString(System.lineSeparator()), table) + } + + @Test(timeout = 300_000) + fun `check table in other locales is produced as expected`() { + val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("ga-IE")) + val table = generator.generateMarkdown() + assertEquals(irishTable.split("\n").joinToString(System.lineSeparator()), table) + } + + @Test(expected = IllegalArgumentException::class, timeout = 300_000) + fun `error thrown if unknown directory passed to generator`() { + val generator = ErrorTableGenerator(File("not/a/directory"), Locale.getDefault()) + generator.generateMarkdown() + } +} \ No newline at end of file diff --git a/tools/error-page-builder/src/test/resources/ErrorPageHeadings.properties b/tools/error-page-builder/src/test/resources/ErrorPageHeadings.properties new file mode 100644 index 0000000000..5741987c8a --- /dev/null +++ b/tools/error-page-builder/src/test/resources/ErrorPageHeadings.properties @@ -0,0 +1,4 @@ +codeHeading = Error Code +aliasesHeading = Aliases +descriptionHeading = Description +toFixHeading = Actions to Fix \ No newline at end of file diff --git a/tools/error-page-builder/src/test/resources/ErrorPageHeadings_ga_IE.properties b/tools/error-page-builder/src/test/resources/ErrorPageHeadings_ga_IE.properties new file mode 100644 index 0000000000..fc69cd3d55 --- /dev/null +++ b/tools/error-page-builder/src/test/resources/ErrorPageHeadings_ga_IE.properties @@ -0,0 +1,4 @@ +codeHeading = Cód Earráide +aliasesHeading = Ailiasanna +descriptionHeading = Cur síos +toFixHeading = Caingne le Deisiú \ No newline at end of file diff --git a/tools/error-page-builder/src/test/resources/test-errors/test-error.properties b/tools/error-page-builder/src/test/resources/test-errors/test-error.properties new file mode 100644 index 0000000000..c0b3c34aa0 --- /dev/null +++ b/tools/error-page-builder/src/test/resources/test-errors/test-error.properties @@ -0,0 +1,4 @@ +errorTemplate = This is a test message +shortDescription = Test description +actionsToFix = Actions +aliases = foo, bar \ No newline at end of file diff --git a/tools/error-page-builder/src/test/resources/test-errors/test-error_ga_IE.properties b/tools/error-page-builder/src/test/resources/test-errors/test-error_ga_IE.properties new file mode 100644 index 0000000000..59c5f19ae0 --- /dev/null +++ b/tools/error-page-builder/src/test/resources/test-errors/test-error_ga_IE.properties @@ -0,0 +1,3 @@ +errorTemplate = Is teachtaireacht earráide é seo +shortDescription = Teachtaireacht tástála +actionsToFix = Roinnt gníomhartha \ No newline at end of file From ab95aa57a2071dc6edfb8a4cc8db76fcdb70a3c3 Mon Sep 17 00:00:00 2001 From: James Higgs <45565019+JamesHR3@users.noreply.github.com> Date: Wed, 29 Apr 2020 11:21:50 +0100 Subject: [PATCH 2/4] [EG-440] Add some error codes and the error resource generation tool (#6192) * [EG-438] First commit of error code interface * [EG-438] Implement error reporter and a few error codes * [EG-438] Add unit tests and default properties files * [EG-438] Add the error table builder * [EG-438] Update initial properties files * [EG-438] Add some Irish tests and the build.gradle * [EG-438] Fall back for aliases and use different resource strategy * [EG-438] Define the URL using a project-specific context * [EG-438] Tidy up initialization code * [EG-438] Add testing to generator and tidy up * [EG-438] Remove direct dependency on core and add own logging config * [EG-438] Fix compiler warnings and tidy up logging * [EG-438] Fix detekt warnings * [EG-438] Improve error messages * [EG-438] Address first set of review comments * [EG-438] Use enums and a builder for the reporter * [EG-438] Address first set of review comments * [EG-438] Use enums and a builder for the reporter * [EG-438] Add kdocs for error resource static methods * [EG-440] Add error code for duplicate CorDapp loading * [EG-438] Handle enums defined with underscores * [EG-440] Add errors for some CorDapp loading scenarios * [EG-440] Finish adding errors for CorDapp loading * [EG-440] Fix up errors in properties files * [EG-440] Start change to error code definition * [EG-440] Update error code definition and add resource generation tool * [EG-440] Tidy up error resource generation tool frontend * [EG-440] Small refactorings and add kdocs * [EG-440] Generate all missing resources * [EG-440] Some refactoring and start writing a test * [EG-440] Update unit test for resource generator * [EG-440] Renaming of various parts of the error tool * [EG-440] Add testing for errors and fix an issue in resource generation * [EG-440] Add a kdoc for context provider API * [EG-440] Remove old code from repository * [EG-440] Address some review comments --- .../logging/errorReporting/ErrorReporting.kt | 5 ++ .../common/logging/errorReporting/Errors.kt | 15 +++- ...pp-duplicate-cordapps-installed.properties | 4 + ...licate-cordapps-installed_en_US.properties | 3 + ...dapp-invalid-version-identifier.properties | 4 + ...nvalid-version-identifier_en_US.properties | 4 + ...rdapp-missing-version-attribute.properties | 4 + ...missing-version-attribute_en_US.properties | 3 + ...dapp-multiple-cordapps-for-flow.properties | 4 + ...ultiple-cordapps-for-flow_en_US.properties | 4 + .../errorReporting/CordappErrorsTest.kt | 12 +++ .../errorReporting/DatabaseErrorsTest.kt | 13 +++ .../logging/errorReporting/ErrorCodeTest.kt | 61 ++++++++++++++ .../cordapp/JarScanningCordappLoader.kt | 53 ++++++++++--- .../cordapp/JarScanningCordappLoaderTest.kt | 1 + settings.gradle | 2 +- .../build.gradle | 15 ++-- .../errorUtilities/ErrorResourceUtilities.kt | 45 +++++++++++ .../net/corda/errorUtilities/ErrorTool.kt | 29 +++++++ .../errorUtilities/ErrorToolCLIUtilities.kt | 23 ++++++ .../errorUtilities/ErrorToolExceptions.kt | 7 ++ .../errorUtilities/docsTable/DocsTableCLI.kt} | 54 +++++-------- .../docsTable/DocsTableGenerator.kt} | 33 +++----- .../resourceGenerator/ResourceGenerator.kt | 79 +++++++++++++++++++ .../resourceGenerator/ResourceGeneratorCLI.kt | 75 ++++++++++++++++++ .../resources/ErrorPageHeadings.properties | 0 .../src/main/resources/log4j2.xml | 0 .../docsTable/DocsTableGeneratorTest.kt} | 20 ++--- .../ResourceGeneratorTest.kt | 56 +++++++++++++ .../resourceGenerator/TestErrorCodes.kt | 23 ++++++ .../resources/ErrorPageHeadings.properties | 0 .../ErrorPageHeadings_ga_IE.properties | 0 .../test-errors/test-error.properties | 0 .../test-errors/test-error_ga_IE.properties | 0 34 files changed, 566 insertions(+), 85 deletions(-) create mode 100644 common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute_en_US.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow.properties create mode 100644 common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow_en_US.properties create mode 100644 common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordappErrorsTest.kt create mode 100644 common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/DatabaseErrorsTest.kt create mode 100644 common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorCodeTest.kt rename tools/{error-page-builder => error-tool}/build.gradle (67%) create mode 100644 tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorResourceUtilities.kt create mode 100644 tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorTool.kt create mode 100644 tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolCLIUtilities.kt create mode 100644 tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolExceptions.kt rename tools/{error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt => error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableCLI.kt} (57%) rename tools/{error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt => error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableGenerator.kt} (63%) create mode 100644 tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGenerator.kt create mode 100644 tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorCLI.kt rename tools/{error-page-builder => error-tool}/src/main/resources/ErrorPageHeadings.properties (100%) rename tools/{error-page-builder => error-tool}/src/main/resources/log4j2.xml (100%) rename tools/{error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt => error-tool/src/test/kotlin/net/corda/errorUtilities/docsTable/DocsTableGeneratorTest.kt} (74%) create mode 100644 tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt create mode 100644 tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/TestErrorCodes.kt rename tools/{error-page-builder => error-tool}/src/test/resources/ErrorPageHeadings.properties (100%) rename tools/{error-page-builder => error-tool}/src/test/resources/ErrorPageHeadings_ga_IE.properties (100%) rename tools/{error-page-builder => error-tool}/src/test/resources/test-errors/test-error.properties (100%) rename tools/{error-page-builder => error-tool}/src/test/resources/test-errors/test-error_ga_IE.properties (100%) diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt index b58557d67e..20cdb77cf6 100644 --- a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt @@ -43,6 +43,11 @@ class ErrorReporting private constructor(private val localeString: String, return ErrorReporting(localeString, location, contextProvider) } + /** + * Set the context provider to supply project-specific information about the errors. + * + * @param contextProvider The context provider to use with error reporting + */ fun withContextProvider(contextProvider: ErrorContextProvider) : ErrorReporting { return ErrorReporting(localeString, resourceLocation, contextProvider) } diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt index 83e324b893..95779d23d6 100644 --- a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/Errors.kt @@ -4,7 +4,8 @@ package net.corda.common.logging.errorReporting * Namespaces for errors within the node. */ enum class NodeNamespaces { - DATABASE + DATABASE, + CORDAPP } /** @@ -17,4 +18,16 @@ enum class NodeDatabaseErrors : ErrorCodes { PASSWORD_REQUIRED_FOR_H2; override val namespace = NodeNamespaces.DATABASE.toString() +} + +/** + * Errors related to loading of Cordapps + */ +enum class CordappErrors : ErrorCodes { + DUPLICATE_CORDAPPS_INSTALLED, + MULTIPLE_CORDAPPS_FOR_FLOW, + MISSING_VERSION_ATTRIBUTE, + INVALID_VERSION_IDENTIFIER; + + override val namespace = NodeNamespaces.CORDAPP.toString() } \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed.properties b/common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed.properties new file mode 100644 index 0000000000..0fd778435c --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed.properties @@ -0,0 +1,4 @@ +errorTemplate = The CorDapp (name: {0}, file: {1}) is installed multiple times on the node. The following files correspond to the exact same content: {2} +shortDescription = A CorDapp has been installed multiple times on the same node. +actionsToFix = Investigate the logs to determine the files with duplicate content, and remove one of them from the cordapps directory. +aliases = iw8d4e \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed_en_US.properties b/common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed_en_US.properties new file mode 100644 index 0000000000..2354427df3 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-duplicate-cordapps-installed_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = The CorDapp (name: {0}, file: {1}) is installed multiple times on the node. The following files correspond to the exact same content: {2} +shortDescription = A CorDapp has been installed multiple times on the same node. +actionsToFix = Investigate the logs to determine the files with duplicate content, and remove one of them from the cordapps directory. \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier.properties b/common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier.properties new file mode 100644 index 0000000000..0922f80bb4 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier.properties @@ -0,0 +1,4 @@ +errorTemplate = Version identifier ({0}) for attribute {1} must be a whole number starting from 1. +shortDescription = A version attribute was specified in the CorDapp manifest with an invalid value. The value must be a whole number, and it must be greater than or equal to 1. +actionsToFix = Investigate the logs to find the invalid attribute, and change the attribute value to be valid (a whole number greater than or equal to 1). +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier_en_US.properties b/common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier_en_US.properties new file mode 100644 index 0000000000..0922f80bb4 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-invalid-version-identifier_en_US.properties @@ -0,0 +1,4 @@ +errorTemplate = Version identifier ({0}) for attribute {1} must be a whole number starting from 1. +shortDescription = A version attribute was specified in the CorDapp manifest with an invalid value. The value must be a whole number, and it must be greater than or equal to 1. +actionsToFix = Investigate the logs to find the invalid attribute, and change the attribute value to be valid (a whole number greater than or equal to 1). +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute.properties b/common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute.properties new file mode 100644 index 0000000000..772b459195 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute.properties @@ -0,0 +1,4 @@ +errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1. +shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR. +actionsToFix = Investigate the logs to find out which version attribute has not been specified, and add that version attribute to the CorDapp manifest. +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute_en_US.properties b/common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute_en_US.properties new file mode 100644 index 0000000000..d8a7ab050a --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-missing-version-attribute_en_US.properties @@ -0,0 +1,3 @@ +errorTemplate = Target versionId attribute {0} not specified. Please specify a whole number starting from 1. +shortDescription = A required version attribute was not specified in the manifest of the CorDapp JAR. +actionsToFix = Investigate the logs to find out which version attribute has not been specified, and add that version attribute to the CorDapp manifest. \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow.properties b/common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow.properties new file mode 100644 index 0000000000..b1ebfb6bb4 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow.properties @@ -0,0 +1,4 @@ +errorTemplate = There are multiple CorDapp JARs on the classpath for the flow {0}: [{1}] +shortDescription = Multiple CorDapp JARs on the classpath define the same flow class. As a result, the platform will not know which version of the flow to start when the flow is invoked. +actionsToFix = Investigate the logs to find out which CorDapp JARs define the same flow classes. The developers of these apps will need to resolve the clash. +aliases = \ No newline at end of file diff --git a/common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow_en_US.properties b/common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow_en_US.properties new file mode 100644 index 0000000000..b1ebfb6bb4 --- /dev/null +++ b/common/logging/src/main/resources/error-codes/cordapp-multiple-cordapps-for-flow_en_US.properties @@ -0,0 +1,4 @@ +errorTemplate = There are multiple CorDapp JARs on the classpath for the flow {0}: [{1}] +shortDescription = Multiple CorDapp JARs on the classpath define the same flow class. As a result, the platform will not know which version of the flow to start when the flow is invoked. +actionsToFix = Investigate the logs to find out which CorDapp JARs define the same flow classes. The developers of these apps will need to resolve the clash. +aliases = \ No newline at end of file diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordappErrorsTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordappErrorsTest.kt new file mode 100644 index 0000000000..4e28410c78 --- /dev/null +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/CordappErrorsTest.kt @@ -0,0 +1,12 @@ +package net.corda.commmon.logging.errorReporting + +import net.corda.common.logging.errorReporting.CordappErrors + +class CordappErrorsTest : ErrorCodeTest(CordappErrors::class.java, true) { + override val dataForCodes = mapOf( + CordappErrors.MISSING_VERSION_ATTRIBUTE to listOf("test-attribute"), + CordappErrors.INVALID_VERSION_IDENTIFIER to listOf(-1, "test-attribute"), + CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW to listOf("MyTestFlow", "Jar 1, Jar 2"), + CordappErrors.DUPLICATE_CORDAPPS_INSTALLED to listOf("TestCordapp", "testapp.jar", "testapp2.jar") + ) +} \ No newline at end of file diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/DatabaseErrorsTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/DatabaseErrorsTest.kt new file mode 100644 index 0000000000..d8697e9415 --- /dev/null +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/DatabaseErrorsTest.kt @@ -0,0 +1,13 @@ +package net.corda.commmon.logging.errorReporting + +import net.corda.common.logging.errorReporting.NodeDatabaseErrors +import java.net.InetAddress + +class DatabaseErrorsTest : ErrorCodeTest(NodeDatabaseErrors::class.java) { + override val dataForCodes = mapOf( + NodeDatabaseErrors.COULD_NOT_CONNECT to listOf(), + NodeDatabaseErrors.FAILED_STARTUP to listOf(), + NodeDatabaseErrors.MISSING_DRIVER to listOf(), + NodeDatabaseErrors.PASSWORD_REQUIRED_FOR_H2 to listOf(InetAddress.getLocalHost()) + ) +} \ No newline at end of file diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorCodeTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorCodeTest.kt new file mode 100644 index 0000000000..1faf5deaef --- /dev/null +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorCodeTest.kt @@ -0,0 +1,61 @@ +package net.corda.commmon.logging.errorReporting + +import junit.framework.TestCase.assertFalse +import net.corda.common.logging.errorReporting.ErrorCode +import net.corda.common.logging.errorReporting.ErrorCodes +import net.corda.common.logging.errorReporting.ErrorResource +import net.corda.common.logging.errorReporting.ResourceBundleProperties +import org.junit.Test +import java.util.* +import kotlin.test.assertTrue + +/** + * Utility for testing that error code resource files behave as expected. + * + * This allows for testing that error messages are printed correctly if they are provided the correct parameters. The test will fail if any + * of the parameters of the template are not filled in. + * + * To use, override the `dataForCodes` with a map from an error code enum value to a list of parameters the message template takes. If any + * are missed, the test will fail. + * + * `printProperties`, if set to true, will print the properties out the resource files, with the error message filled in. This allows the + * message to be inspected. + */ +abstract class ErrorCodeTest(private val clazz: Class, + private val printProperties: Boolean = false) where T: Enum, T: ErrorCodes { + + abstract val dataForCodes: Map> + + private class TestError(override val code: T, + override val parameters: List) : ErrorCode where T: Enum, T: ErrorCodes + + @Test(timeout = 300_000) + fun `test error codes`() { + for ((code, params) in dataForCodes) { + val error = TestError(code, params) + val resource = ErrorResource.fromErrorCode(error, "error-codes", Locale.forLanguageTag("en-US")) + val message = resource.getErrorMessage(error.parameters.toTypedArray()) + assertFalse( + "The error message reported for code $code contains missing parameters", + message.contains("\\{.*}".toRegex()) + ) + val otherProperties = Triple(resource.shortDescription, resource.actionsToFix, resource.aliases) + if (printProperties) { + println("Data for $code") + println("Error Message = $message") + println("${ResourceBundleProperties.SHORT_DESCRIPTION} = ${otherProperties.first}") + println("${ResourceBundleProperties.ACTIONS_TO_FIX} = ${otherProperties.second}") + println("${ResourceBundleProperties.ALIASES} = ${otherProperties.third}") + println("") + } + } + } + + @Test(timeout = 300_000) + fun `ensure all error codes tested`() { + val expected = clazz.enumConstants.toSet() + val actual = dataForCodes.keys.toSet() + val missing = expected - actual + assertTrue(missing.isEmpty(), "The following codes have not been tested: $missing") + } +} \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index a8fef21fc4..b134c69815 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -2,6 +2,10 @@ package net.corda.node.internal.cordapp import io.github.classgraph.ClassGraph import io.github.classgraph.ScanResult +import net.corda.common.logging.errorReporting.CordappErrors +import net.corda.common.logging.errorReporting.ErrorCode +import net.corda.common.logging.errorReporting.NodeDatabaseErrors +import net.corda.common.logging.errorReporting.NodeNamespaces import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 @@ -144,9 +148,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: val duplicateCordapps = registeredCordapps.filter { it.jarHash == cordapp.jarHash }.toSet() if (duplicateCordapps.isNotEmpty()) { - throw IllegalStateException("The CorDapp (name: ${cordapp.info.shortName}, file: ${cordapp.name}) " + - "is installed multiple times on the node. The following files correspond to the exact same content: " + - "${duplicateCordapps.map { it.name }}") + throw DuplicateCordappsInstalledException(cordapp, duplicateCordapps) } if (registeredClassName in contractClasses) { throw IllegalStateException("More than one CorDapp installed on the node for contract $registeredClassName. " + @@ -227,12 +229,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: private fun parseVersion(versionStr: String?, attributeName: String): Int { if (versionStr == null) { - throw CordappInvalidVersionException("Target versionId attribute $attributeName not specified. Please specify a whole number starting from 1.") + throw CordappInvalidVersionException( + "Target versionId attribute $attributeName not specified. Please specify a whole number starting from 1.", + CordappErrors.MISSING_VERSION_ATTRIBUTE, + listOf(attributeName)) } val version = versionStr.toIntOrNull() - ?: throw CordappInvalidVersionException("Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.") + ?: throw CordappInvalidVersionException( + "Version identifier ($versionStr) for attribute $attributeName must be a whole number starting from 1.", + CordappErrors.INVALID_VERSION_IDENTIFIER, + listOf(versionStr, attributeName)) if (version < 1) { - throw CordappInvalidVersionException("Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.") + throw CordappInvalidVersionException( + "Target versionId ($versionStr) for attribute $attributeName must not be smaller than 1.", + CordappErrors.INVALID_VERSION_IDENTIFIER, + listOf(versionStr, attributeName)) } return version } @@ -403,12 +414,34 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths: /** * Thrown when scanning CorDapps. */ -class MultipleCordappsForFlowException(message: String) : Exception(message) +class MultipleCordappsForFlowException( + message: String, + flowName: String, + jars: String +) : Exception(message), ErrorCode { + override val code = CordappErrors.MULTIPLE_CORDAPPS_FOR_FLOW + override val parameters = listOf(flowName, jars) +} /** * Thrown if an exception occurs whilst parsing version identifiers within cordapp configuration */ -class CordappInvalidVersionException(msg: String) : Exception(msg) +class CordappInvalidVersionException( + msg: String, + override val code: CordappErrors, + override val parameters: List = listOf() +) : Exception(msg), ErrorCode + +/** + * Thrown if duplicate CorDapps are installed on the node + */ +class DuplicateCordappsInstalledException(app: Cordapp, duplicates: Set) + : IllegalStateException("The CorDapp (name: ${app.info.shortName}, file: ${app.name}) " + + "is installed multiple times on the node. The following files correspond to the exact same content: " + + "${duplicates.map { it.name }}"), ErrorCode { + override val code = CordappErrors.DUPLICATE_CORDAPPS_INSTALLED + override val parameters = listOf(app.info.shortName, app.name, duplicates.map { it.name }) +} abstract class CordappLoaderTemplate : CordappLoader { @@ -436,7 +469,9 @@ abstract class CordappLoaderTemplate : CordappLoader { } } throw MultipleCordappsForFlowException("There are multiple CorDapp JARs on the classpath for flow " + - "${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.jarPath.toString() }} ].") + "${entry.value.first().first.name}: [ ${entry.value.joinToString { it.second.jarPath.toString() }} ].", + entry.value.first().first.name, + entry.value.joinToString { it.second.jarPath.toString() }) } entry.value.single().second } diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index fa98ff7e59..0d3f8d8f54 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -1,6 +1,7 @@ package net.corda.node.internal.cordapp import co.paralleluniverse.fibers.Suspendable +import net.corda.common.logging.errorReporting.CordappErrors import net.corda.core.flows.* import net.corda.node.VersionInfo import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES diff --git a/settings.gradle b/settings.gradle index f3ec7e75d8..d582cee14e 100644 --- a/settings.gradle +++ b/settings.gradle @@ -108,5 +108,5 @@ include 'serialization-deterministic' include 'tools:checkpoint-agent' include 'detekt-plugins' -include 'tools:error-page-builder' +include 'tools:error-tool' diff --git a/tools/error-page-builder/build.gradle b/tools/error-tool/build.gradle similarity index 67% rename from tools/error-page-builder/build.gradle rename to tools/error-tool/build.gradle index ed16c572b3..d1e11ec376 100644 --- a/tools/error-page-builder/build.gradle +++ b/tools/error-tool/build.gradle @@ -1,6 +1,3 @@ -group 'net.corda' -version '4.5-SNAPSHOT' - apply plugin: 'kotlin' apply plugin: 'com.github.johnrengelman.shadow' @@ -12,9 +9,9 @@ dependencies { implementation project(":common-logging") implementation project(":tools:cliutils") implementation "info.picocli:picocli:$picocli_version" - testCompile group: 'junit', name: 'junit', version: '4.12' - implementation "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version" + + testCompile "junit:junit:4.12" } jar { @@ -23,10 +20,12 @@ jar { } shadowJar { - baseName = "corda-tools-error-page-builder" + baseName = "corda-tools-error-utils" manifest { attributes( - 'Main-Class': "net.corda.errorPageBuilder.ErrorPageBuilderKt" + 'Main-Class': "net.corda.errorUtilities.ErrorToolKt" ) } -} \ No newline at end of file +} + +assemble.dependsOn shadowJar \ No newline at end of file diff --git a/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorResourceUtilities.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorResourceUtilities.kt new file mode 100644 index 0000000000..bcb88f6180 --- /dev/null +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorResourceUtilities.kt @@ -0,0 +1,45 @@ +package net.corda.errorUtilities + +import java.net.URLClassLoader +import java.nio.file.Path + +/** + * A class for reading and processing error code resource bundles from a given directory. + */ +class ErrorResourceUtilities { + + companion object { + private val ERROR_INFO_RESOURCE_REGEX= ".*ErrorInfo.*".toRegex() + private val DEFAULT_RESOURCE_FILE_REGEX = "[^_]*\\.properties".toRegex() + private val PROPERTIES_FILE_REGEX = ".*\\.properties".toRegex() + + /** + * List all resource bundle names in a given directory + */ + fun listResourceNames(location: Path) : List { + return location.toFile().walkTopDown().filter { + it.name.matches(DEFAULT_RESOURCE_FILE_REGEX) && !it.name.matches(ERROR_INFO_RESOURCE_REGEX) + }.map { + it.nameWithoutExtension + }.toList() + } + + /** + * List all resource files in a given directory + */ + fun listResourceFiles(location: Path) : List { + return location.toFile().walkTopDown().filter { + it.name.matches(PROPERTIES_FILE_REGEX) + }.map { it.name }.toList() + } + + /** + * Create a classloader with all URLs in a given directory + */ + fun loaderFromDirectory(location: Path) : URLClassLoader { + val urls = arrayOf(location.toUri().toURL()) + val sysLoader = ClassLoader.getSystemClassLoader() + return URLClassLoader(urls, sysLoader) + } + } +} \ No newline at end of file diff --git a/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorTool.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorTool.kt new file mode 100644 index 0000000000..1c2f620c76 --- /dev/null +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorTool.kt @@ -0,0 +1,29 @@ +package net.corda.errorUtilities + +import net.corda.cliutils.CordaCliWrapper +import net.corda.cliutils.ExitCodes +import net.corda.cliutils.start +import net.corda.errorUtilities.docsTable.DocsTableCLI +import net.corda.errorUtilities.resourceGenerator.ResourceGeneratorCLI + +fun main(args: Array) = ErrorTool().start(args) + + +/** + * Entry point for the error utilities. + * + * By itself, this doesn't do anything - instead one of the subcommands should be invoked. + */ +class ErrorTool : CordaCliWrapper("error-utils", "Utilities for working with error codes and error reporting") { + + private val errorPageBuilder = DocsTableCLI() + private val errorResourceGenerator = ResourceGeneratorCLI() + + override fun additionalSubCommands() = setOf(errorPageBuilder, errorResourceGenerator) + + override fun runProgram(): Int { + println("No subcommand specified - please invoke one of the subcommands.") + printHelp() + return ExitCodes.FAILURE + } +} \ No newline at end of file diff --git a/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolCLIUtilities.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolCLIUtilities.kt new file mode 100644 index 0000000000..b76c655b68 --- /dev/null +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolCLIUtilities.kt @@ -0,0 +1,23 @@ +package net.corda.errorUtilities + +import java.lang.IllegalArgumentException +import java.nio.file.Files +import java.nio.file.Path + +/** + * Common functions to use among multiple of the error code subcommands + */ +class ErrorToolCLIUtilities { + companion object { + /** + * Checks that a directory provided through Picocli exists. + */ + fun checkDirectory(dir: Path?, expectedContents: String) : Path { + return dir?.also { + require(Files.exists(it)) { + "Directory $it does not exist. Please specify a valid direction for $expectedContents" + } + } ?: throw IllegalArgumentException("No location specified for $expectedContents. Please specify a directory for $expectedContents.") + } + } +} \ No newline at end of file diff --git a/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolExceptions.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolExceptions.kt new file mode 100644 index 0000000000..070316493b --- /dev/null +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/ErrorToolExceptions.kt @@ -0,0 +1,7 @@ +package net.corda.errorUtilities + +abstract class ErrorToolException(msg: String, cause: Exception? = null) : Exception(msg, cause) + +class ClassDoesNotExistException(classname: String) + : ErrorToolException("The class $classname could not be found in the provided JAR. " + + "Check that the correct fully qualified name has been provided and the JAR file is the correct one for this class.") \ No newline at end of file diff --git a/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableCLI.kt similarity index 57% rename from tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt rename to tools/error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableCLI.kt index abe122310d..7773be6ff7 100644 --- a/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorPageBuilder.kt +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableCLI.kt @@ -1,22 +1,24 @@ -package net.corda.errorPageBuilder +package net.corda.errorUtilities.docsTable import net.corda.cliutils.CordaCliWrapper import net.corda.cliutils.ExitCodes -import net.corda.cliutils.start +import net.corda.errorUtilities.ErrorToolCLIUtilities import org.slf4j.LoggerFactory import picocli.CommandLine -import java.io.File import java.lang.IllegalArgumentException import java.nio.file.Files import java.nio.file.Path import java.util.* -fun main(args: Array) { - val builder = ErrorPageBuilder() - builder.start(args) -} - -class ErrorPageBuilder : CordaCliWrapper("error-page-builder", "Builds the error table for the error codes page") { +/** + * Error tool sub-command for generating the documentation for error codes. + * + * The command needs a location to output the documentation to and a directory containing the resource files. From this, it generates a + * Markdown table with all defined error codes. + * + * In the event that the file already exists, the tool will report an error and exit. + */ +class DocsTableCLI : CordaCliWrapper("build-docs", "Builds the error table for the error codes page") { @CommandLine.Parameters( index = "0", @@ -42,43 +44,27 @@ class ErrorPageBuilder : CordaCliWrapper("error-page-builder", "Builds the error var localeTag: String? = null companion object { - private val logger = LoggerFactory.getLogger(ErrorPageBuilder::class.java) + private val logger = LoggerFactory.getLogger(DocsTableCLI::class.java) private const val ERROR_CODES_FILE = "error-codes.md" } - private fun getOutputFile() : File { - return outputDir?.let { - require(Files.exists(it)) { - "Directory $it does not exist. Please specify a valid directory to write output to." - } - val outputPath = it.resolve(ERROR_CODES_FILE) - require(Files.notExists(outputPath)) { - "Output file $outputPath exists, please remove it and run again." - } - outputPath.toFile() - } ?: throw IllegalArgumentException("Directory not specified. Please specify a valid directory to write output to.") - } - - private fun getResourceDir() : Path { - return resourceLocation?.also { - require(Files.exists(it)) { - "Resource location $it does not exist. Please specify a valid location for error code resources" - } - } ?: throw IllegalArgumentException("Resource location not specified. Please specify a resource location.") - } - override fun runProgram(): Int { val locale = if (localeTag != null) Locale.forLanguageTag(localeTag) else Locale.getDefault() val (outputFile, resources) = try { - Pair(getOutputFile(), getResourceDir()) + val output = ErrorToolCLIUtilities.checkDirectory(outputDir, "output file") + val outputPath = output.resolve(ERROR_CODES_FILE) + require(Files.notExists(outputPath)) { + "Output file $outputPath exists, please remove it and run again." + } + Pair(outputPath, ErrorToolCLIUtilities.checkDirectory(resourceLocation, "resource bundle files")) } catch (e: IllegalArgumentException) { logger.error(e.message, e) return ExitCodes.FAILURE } - val tableGenerator = ErrorTableGenerator(resources.toFile(), locale) + val tableGenerator = DocsTableGenerator(resources, locale) try { val table = tableGenerator.generateMarkdown() - outputFile.writeText(table) + outputFile.toFile().writeText(table) } catch (e: IllegalArgumentException) { logger.error(e.message, e) return ExitCodes.FAILURE diff --git a/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableGenerator.kt similarity index 63% rename from tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt rename to tools/error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableGenerator.kt index 6cf13436a2..d16675bf55 100644 --- a/tools/error-page-builder/src/main/kotlin/net/corda/errorPageBuilder/ErrorTableGenerator.kt +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/docsTable/DocsTableGenerator.kt @@ -1,13 +1,16 @@ -package net.corda.errorPageBuilder +package net.corda.errorUtilities.docsTable import net.corda.common.logging.errorReporting.ErrorResource -import java.io.File +import net.corda.errorUtilities.ErrorResourceUtilities import java.lang.IllegalArgumentException -import java.net.URLClassLoader +import java.nio.file.Path import java.util.* -class ErrorTableGenerator(private val resourceLocation: File, - private val locale: Locale) { +/** + * Generate the documentation table given a resource file location set. + */ +class DocsTableGenerator(private val resourceLocation: Path, + private val locale: Locale) { companion object { private const val ERROR_CODE_HEADING = "codeHeading" @@ -15,7 +18,6 @@ class ErrorTableGenerator(private val resourceLocation: File, private const val DESCRIPTION_HEADING = "descriptionHeading" private const val TO_FIX_HEADING = "toFixHeading" private const val ERROR_HEADINGS_BUNDLE = "ErrorPageHeadings" - private const val ERROR_INFO_RESOURCE = "ErrorInfo.properties" } private fun getHeading(heading: String) : String { @@ -23,23 +25,10 @@ class ErrorTableGenerator(private val resourceLocation: File, return resource.getString(heading) } - private fun listResources() : Iterator { - return resourceLocation.walkTopDown().filter { - it.name.matches("[^_]*\\.properties".toRegex()) && !it.name.matches(ERROR_INFO_RESOURCE.toRegex()) - }.map { - it.nameWithoutExtension - }.iterator() - } - - private fun createLoader() : ClassLoader { - val urls = resourceLocation.walkTopDown().map { it.toURI().toURL() }.asIterable().toList().toTypedArray() - return URLClassLoader(urls) - } - private fun generateTable() : List> { val table = mutableListOf>() - val loader = createLoader() - for (resource in listResources()) { + val loader = ErrorResourceUtilities.loaderFromDirectory(resourceLocation) + for (resource in ErrorResourceUtilities.listResourceNames(resourceLocation)) { val errorResource = ErrorResource.fromLoader(resource, loader, locale) table.add(listOf(resource, errorResource.aliases, errorResource.shortDescription, errorResource.actionsToFix)) } @@ -59,7 +48,7 @@ class ErrorTableGenerator(private val resourceLocation: File, } fun generateMarkdown() : String { - if (!resourceLocation.exists()) throw IllegalArgumentException("Directory $resourceLocation does not exist.") + if (!resourceLocation.toFile().exists()) throw IllegalArgumentException("Directory $resourceLocation does not exist.") val tableData = generateTable() return formatTable(tableData) } diff --git a/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGenerator.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGenerator.kt new file mode 100644 index 0000000000..2cb34684dd --- /dev/null +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGenerator.kt @@ -0,0 +1,79 @@ +package net.corda.errorUtilities.resourceGenerator + +import net.corda.common.logging.errorReporting.ErrorCodes +import net.corda.common.logging.errorReporting.ResourceBundleProperties +import net.corda.errorUtilities.ClassDoesNotExistException +import java.nio.file.Path +import java.util.* + +/** + * Generate a set of resource files from an enumeration of error codes. + */ +class ResourceGenerator(private val locales: List) { + + companion object { + internal const val MESSAGE_TEMPLATE_DEFAULT = "" + internal const val SHORT_DESCRIPTION_DEFAULT = "" + internal const val ACTIONS_TO_FIX_DEFAULT = "" + internal const val ALIASES_DEFAULT = "" + } + + private fun createResourceFile(name: String, location: Path) { + val file = location.resolve(name) + val text = """ + |${ResourceBundleProperties.MESSAGE_TEMPLATE} = $MESSAGE_TEMPLATE_DEFAULT + |${ResourceBundleProperties.SHORT_DESCRIPTION} = $SHORT_DESCRIPTION_DEFAULT + |${ResourceBundleProperties.ACTIONS_TO_FIX} = $ACTIONS_TO_FIX_DEFAULT + |${ResourceBundleProperties.ALIASES} = $ALIASES_DEFAULT + """.trimMargin() + file.toFile().writeText(text) + } + + /** + * Create a set of resource files in the given location. + * + * @param resources The resource file names to create + * @param resourceLocation The location to create the resource files + */ + fun createResources(resources: List, resourceLocation: Path) { + for (resource in resources) { + createResourceFile(resource, resourceLocation) + } + } + + private fun definedCodes(classes: List, loader: ClassLoader) : List { + return classes.flatMap { + val clazz = try { + loader.loadClass(it) + } catch (e: ClassNotFoundException) { + throw ClassDoesNotExistException(it) + } + if (ErrorCodes::class.java.isAssignableFrom(clazz) && clazz != ErrorCodes::class.java) { + val namespace = (clazz.enumConstants.first() as ErrorCodes).namespace.toLowerCase() + clazz.enumConstants.map { code -> "${namespace}-${code.toString().toLowerCase().replace("_", "-")}"} + } else { + listOf() + } + } + } + + private fun getExpectedResources(codes: List) : List { + return codes.flatMap { + val localeResources = locales.map { locale -> "${it}_${locale.toLanguageTag().replace("-", "_")}.properties"} + localeResources + "$it.properties" + } + } + + /** + * Calculate what resource files are missing from a set of resource files, given a set of error codes. + * + * @param classes The classes to generate resource files for + * @param resourceFiles The list of resource files + */ + fun calculateMissingResources(classes: List, resourceFiles: List, loader: ClassLoader) : List { + val codes = definedCodes(classes, loader) + val expected = getExpectedResources(codes) + val missing = expected - resourceFiles.toSet() + return missing.toList() + } +} \ No newline at end of file diff --git a/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorCLI.kt b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorCLI.kt new file mode 100644 index 0000000000..49b28f52ba --- /dev/null +++ b/tools/error-tool/src/main/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorCLI.kt @@ -0,0 +1,75 @@ +package net.corda.errorUtilities.resourceGenerator + +import net.corda.cliutils.CordaCliWrapper +import net.corda.cliutils.ExitCodes +import net.corda.errorUtilities.ErrorResourceUtilities +import net.corda.errorUtilities.ErrorToolCLIUtilities +import org.slf4j.LoggerFactory +import picocli.CommandLine +import java.lang.IllegalArgumentException +import java.nio.file.Path +import java.util.* + +/** + * Subcommand for generating resource bundles from error codes. + * + * This subcommand takes a directory containing built class files that define the enumerations of codes, and a directory containing any + * existing resource bundles. From this, it generates any missing resource files with the properties specified. The data under these + * properties should then be filled in by hand. + */ +class ResourceGeneratorCLI : CordaCliWrapper( + "generate-resources", + "Generate any missing resource files for a set of error codes" +) { + + @CommandLine.Parameters( + index = "0", + paramLabel = "JAR_FILE", + arity = "1", + description = ["JAR file containing class files of the error code definitions"] + ) + var jarFile: Path? = null + + @CommandLine.Parameters( + index = "1", + paramLabel = "RESOURCE_DIR", + arity = "1", + description = ["Directory containing resource bundles for the error codes"] + ) + var resourceDir: Path? = null + + @CommandLine.Parameters( + index="2..*", + paramLabel = "ERROR_CODE_CLASSES", + description = ["Fully qualified class names of the error code classes to generate resources for"] + ) + var classes: List = mutableListOf() + + @CommandLine.Option( + names = ["--locales"], + description = ["The set of locales to generate resource files for. Specified as locale tags, for example en-US"], + arity = "1" + ) + var locales: List = listOf("en-US") + + companion object { + private val logger = LoggerFactory.getLogger(ResourceGeneratorCLI::class.java) + } + + override fun runProgram(): Int { + val jarFileLocation = ErrorToolCLIUtilities.checkDirectory(jarFile, "error code definition class files") + val resourceLocation = ErrorToolCLIUtilities.checkDirectory(resourceDir, "resource bundle files") + val resourceGenerator = ResourceGenerator(locales.map { Locale.forLanguageTag(it) }) + try { + val resources = ErrorResourceUtilities.listResourceFiles(resourceLocation) + val loader = ErrorResourceUtilities.loaderFromDirectory(jarFileLocation) + val missingResources = resourceGenerator.calculateMissingResources(classes, resources, loader) + resourceGenerator.createResources(missingResources, resourceLocation) + loader.close() + } catch (e: IllegalArgumentException) { + logger.error(e.message, e) + return ExitCodes.FAILURE + } + return ExitCodes.SUCCESS + } +} \ No newline at end of file diff --git a/tools/error-page-builder/src/main/resources/ErrorPageHeadings.properties b/tools/error-tool/src/main/resources/ErrorPageHeadings.properties similarity index 100% rename from tools/error-page-builder/src/main/resources/ErrorPageHeadings.properties rename to tools/error-tool/src/main/resources/ErrorPageHeadings.properties diff --git a/tools/error-page-builder/src/main/resources/log4j2.xml b/tools/error-tool/src/main/resources/log4j2.xml similarity index 100% rename from tools/error-page-builder/src/main/resources/log4j2.xml rename to tools/error-tool/src/main/resources/log4j2.xml diff --git a/tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/docsTable/DocsTableGeneratorTest.kt similarity index 74% rename from tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt rename to tools/error-tool/src/test/kotlin/net/corda/errorUtilities/docsTable/DocsTableGeneratorTest.kt index 211576dda1..59ca2ed17f 100644 --- a/tools/error-page-builder/src/test/kotlin/ErrorTableGeneratorTest.kt +++ b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/docsTable/DocsTableGeneratorTest.kt @@ -1,15 +1,15 @@ +package net.corda.errorUtilities.docsTable + import junit.framework.TestCase.assertEquals -import net.corda.errorPageBuilder.ErrorTableGenerator import org.junit.Test -import java.io.File import java.lang.IllegalArgumentException import java.nio.file.Paths import java.util.* -class ErrorTableGeneratorTest { +class DocsTableGeneratorTest { companion object { - private val RESOURCE_LOCATION = Paths.get("src/test/resources/test-errors").toAbsolutePath().toFile() + private val RESOURCE_LOCATION = Paths.get("src/test/resources/test-errors").toAbsolutePath() } private val englishTable = """| Error Code | Aliases | Description | Actions to Fix | @@ -22,24 +22,24 @@ class ErrorTableGeneratorTest { /| test-error | foo, bar | Teachtaireacht tástála | Roinnt gníomhartha | """.trimMargin("/") - @Test(timeout = 300_000) + @Test(timeout = 1000) fun `check error table is produced as expected`() { - val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("en-US")) + val generator = DocsTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("en-US")) val table = generator.generateMarkdown() // Raw strings in Kotlin always use Unix line endings, so this is required to keep the test passing on Windows assertEquals(englishTable.split("\n").joinToString(System.lineSeparator()), table) } - @Test(timeout = 300_000) + @Test(timeout = 1000) fun `check table in other locales is produced as expected`() { - val generator = ErrorTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("ga-IE")) + val generator = DocsTableGenerator(RESOURCE_LOCATION, Locale.forLanguageTag("ga-IE")) val table = generator.generateMarkdown() assertEquals(irishTable.split("\n").joinToString(System.lineSeparator()), table) } - @Test(expected = IllegalArgumentException::class, timeout = 300_000) + @Test(expected = IllegalArgumentException::class, timeout = 1000) fun `error thrown if unknown directory passed to generator`() { - val generator = ErrorTableGenerator(File("not/a/directory"), Locale.getDefault()) + val generator = DocsTableGenerator(Paths.get("not/a/directory"), Locale.getDefault()) generator.generateMarkdown() } } \ No newline at end of file diff --git a/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt new file mode 100644 index 0000000000..0e8b2be854 --- /dev/null +++ b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt @@ -0,0 +1,56 @@ +package net.corda.errorUtilities.resourceGenerator + +import junit.framework.TestCase.assertEquals +import net.corda.common.logging.errorReporting.ResourceBundleProperties +import org.junit.Test +import java.util.* + +class ResourceGeneratorTest { + + private val classes = listOf(TestCodes1::class.qualifiedName!!, TestCodes2::class.qualifiedName!!) + + private fun expectedCodes() : List { + val codes1 = TestCodes1.values().map { "${it.namespace.toLowerCase()}-${it.name.replace("_", "-").toLowerCase()}" } + val codes2 = TestCodes2.values().map { "${it.namespace.toLowerCase()}-${it.name.replace("_", "-").toLowerCase()}" } + return codes1 + codes2 + } + + @Test(timeout = 1000) + fun `no codes marked as missing if all resources are present`() { + val resourceGenerator = ResourceGenerator(listOf()) + val currentFiles = expectedCodes().map { "$it.properties" } + val missing = resourceGenerator.calculateMissingResources(classes, currentFiles, TestCodes1::class.java.classLoader) + assertEquals(setOf(), missing.toSet()) + } + + @Test(timeout = 1000) + fun `missing locales are marked as missing when other locales are present`() { + val resourceGenerator = ResourceGenerator(listOf("en-US", "ga-IE").map { Locale.forLanguageTag(it) }) + val currentFiles = expectedCodes().flatMap { listOf("$it.properties", "${it}_en_US.properties") } + val missing = resourceGenerator.calculateMissingResources(classes, currentFiles, TestCodes1::class.java.classLoader) + assertEquals(expectedCodes().map { "${it}_ga_IE.properties" }.toSet(), missing.toSet()) + } + + @Test(timeout = 1000) + fun `test writing out files works correctly`() { + // First test that if all files are missing then the resource generator detects this + val resourceGenerator = ResourceGenerator(listOf()) + val currentFiles = listOf() + val missing = resourceGenerator.calculateMissingResources(classes, currentFiles, TestCodes1::class.java.classLoader) + assertEquals(expectedCodes().map { "$it.properties" }.toSet(), missing.toSet()) + + // Now check that all resource files that should be created are + val tempDir = createTempDir() + resourceGenerator.createResources(missing, tempDir.toPath()) + val createdFiles = tempDir.walkTopDown().filter { it.isFile && it.extension == "properties" }.map { it.name }.toList() + assertEquals(missing, createdFiles) + + // Now check that a created file has the expected properties and values + val properties = Properties() + properties.load(tempDir.walk().filter { it.isFile && it.extension == "properties"}.first().inputStream()) + assertEquals(ResourceGenerator.SHORT_DESCRIPTION_DEFAULT, properties.getProperty(ResourceBundleProperties.SHORT_DESCRIPTION)) + assertEquals(ResourceGenerator.ACTIONS_TO_FIX_DEFAULT, properties.getProperty(ResourceBundleProperties.ACTIONS_TO_FIX)) + assertEquals(ResourceGenerator.MESSAGE_TEMPLATE_DEFAULT, properties.getProperty(ResourceBundleProperties.MESSAGE_TEMPLATE)) + assertEquals(ResourceGenerator.ALIASES_DEFAULT, properties.getProperty(ResourceBundleProperties.ALIASES)) + } +} \ No newline at end of file diff --git a/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/TestErrorCodes.kt b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/TestErrorCodes.kt new file mode 100644 index 0000000000..0e33357797 --- /dev/null +++ b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/TestErrorCodes.kt @@ -0,0 +1,23 @@ +package net.corda.errorUtilities.resourceGenerator + +import net.corda.common.logging.errorReporting.ErrorCodes + +// These test errors are not used directly, but their compiled class files are used to verify the resource generator functionality. +enum class TestNamespaces { + TN1, + TN2 +} + +enum class TestCodes1 : ErrorCodes { + CASE1, + CASE2; + + override val namespace = TestNamespaces.TN1.toString() +} + +enum class TestCodes2 : ErrorCodes { + CASE1, + CASE3; + + override val namespace = TestNamespaces.TN2.toString() +} \ No newline at end of file diff --git a/tools/error-page-builder/src/test/resources/ErrorPageHeadings.properties b/tools/error-tool/src/test/resources/ErrorPageHeadings.properties similarity index 100% rename from tools/error-page-builder/src/test/resources/ErrorPageHeadings.properties rename to tools/error-tool/src/test/resources/ErrorPageHeadings.properties diff --git a/tools/error-page-builder/src/test/resources/ErrorPageHeadings_ga_IE.properties b/tools/error-tool/src/test/resources/ErrorPageHeadings_ga_IE.properties similarity index 100% rename from tools/error-page-builder/src/test/resources/ErrorPageHeadings_ga_IE.properties rename to tools/error-tool/src/test/resources/ErrorPageHeadings_ga_IE.properties diff --git a/tools/error-page-builder/src/test/resources/test-errors/test-error.properties b/tools/error-tool/src/test/resources/test-errors/test-error.properties similarity index 100% rename from tools/error-page-builder/src/test/resources/test-errors/test-error.properties rename to tools/error-tool/src/test/resources/test-errors/test-error.properties diff --git a/tools/error-page-builder/src/test/resources/test-errors/test-error_ga_IE.properties b/tools/error-tool/src/test/resources/test-errors/test-error_ga_IE.properties similarity index 100% rename from tools/error-page-builder/src/test/resources/test-errors/test-error_ga_IE.properties rename to tools/error-tool/src/test/resources/test-errors/test-error_ga_IE.properties From 1956bdb5d4ac2ae02041f2562868d188ec4dfa00 Mon Sep 17 00:00:00 2001 From: James Higgs Date: Wed, 29 Apr 2020 11:57:37 +0100 Subject: [PATCH 3/4] [MERGE] Fix detekt issues --- .../net/corda/common/logging/errorReporting/ErrorReporting.kt | 1 - .../commmon/logging/errorReporting/ErrorReporterImplTest.kt | 1 - .../net/corda/nodeapi/internal/persistence/CordaPersistence.kt | 1 - .../net/corda/node/internal/cordapp/JarScanningCordappLoader.kt | 2 -- .../corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt | 1 - 5 files changed, 6 deletions(-) diff --git a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt index 20cdb77cf6..6ee60f8c49 100644 --- a/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt +++ b/common/logging/src/main/kotlin/net/corda/common/logging/errorReporting/ErrorReporting.kt @@ -1,6 +1,5 @@ package net.corda.common.logging.errorReporting -import java.lang.UnsupportedOperationException import java.util.* /** diff --git a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt index 9971f8c60d..38192f81ad 100644 --- a/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt +++ b/common/logging/src/test/kotlin/net/corda/commmon/logging/errorReporting/ErrorReporterImplTest.kt @@ -7,7 +7,6 @@ import net.corda.common.logging.errorReporting.ErrorContextProvider import net.corda.common.logging.errorReporting.ErrorReporterImpl import org.junit.After import org.junit.Test -import org.junit.rules.TestName import org.mockito.ArgumentMatchers.anyString import org.mockito.Mockito import org.slf4j.Logger diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt index 5e044f65e7..d54575102b 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/persistence/CordaPersistence.kt @@ -10,7 +10,6 @@ import net.corda.core.internal.NamedCacheFactory import net.corda.core.schemas.MappedSchema import net.corda.core.utilities.contextLogger import net.corda.common.logging.errorReporting.NodeDatabaseErrors -import net.corda.common.logging.errorReporting.NodeNamespaces import org.hibernate.tool.schema.spi.SchemaManagementException import rx.Observable import rx.Subscriber diff --git a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt index b134c69815..efa1cd32d6 100644 --- a/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt +++ b/node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt @@ -4,8 +4,6 @@ import io.github.classgraph.ClassGraph import io.github.classgraph.ScanResult import net.corda.common.logging.errorReporting.CordappErrors import net.corda.common.logging.errorReporting.ErrorCode -import net.corda.common.logging.errorReporting.NodeDatabaseErrors -import net.corda.common.logging.errorReporting.NodeNamespaces import net.corda.core.cordapp.Cordapp import net.corda.core.crypto.SecureHash import net.corda.core.crypto.sha256 diff --git a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt index 0d3f8d8f54..fa98ff7e59 100644 --- a/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt +++ b/node/src/test/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoaderTest.kt @@ -1,7 +1,6 @@ package net.corda.node.internal.cordapp import co.paralleluniverse.fibers.Suspendable -import net.corda.common.logging.errorReporting.CordappErrors import net.corda.core.flows.* import net.corda.node.VersionInfo import net.corda.nodeapi.internal.DEV_PUB_KEY_HASHES From 7db25b89864de7a1bbf77eb4527285380d1941ce Mon Sep 17 00:00:00 2001 From: James Higgs <45565019+JamesHR3@users.noreply.github.com> Date: Thu, 30 Apr 2020 10:57:47 +0100 Subject: [PATCH 4/4] [NOTICK] Fix a test error to allow tests to pass in Jenkins (#6202) --- .../errorUtilities/resourceGenerator/ResourceGeneratorTest.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt index 0e8b2be854..8f20d5988c 100644 --- a/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt +++ b/tools/error-tool/src/test/kotlin/net/corda/errorUtilities/resourceGenerator/ResourceGeneratorTest.kt @@ -42,8 +42,8 @@ class ResourceGeneratorTest { // Now check that all resource files that should be created are val tempDir = createTempDir() resourceGenerator.createResources(missing, tempDir.toPath()) - val createdFiles = tempDir.walkTopDown().filter { it.isFile && it.extension == "properties" }.map { it.name }.toList() - assertEquals(missing, createdFiles) + val createdFiles = tempDir.walkTopDown().filter { it.isFile && it.extension == "properties" }.map { it.name }.toSet() + assertEquals(missing.toSet(), createdFiles) // Now check that a created file has the expected properties and values val properties = Properties()