From a746db11065de52d29c3af994d3d9bbaf9273a22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miko=C5=82aj=20Wawrowski?= Date: Mon, 22 Jun 2020 19:36:31 +0200 Subject: [PATCH 1/8] Accessibility Report generation module - initial commit Ref: #557 --- core/accessibility-report/LICENSE | 201 ++++++++++++++++++ core/accessibility-report/README.md | 5 + core/accessibility-report/pom.xml | 145 +++++++++++++ .../report/models/AccessibilityCode.kt | 48 +++++ .../report/models/AccessibilityIssue.kt | 76 +++++++ .../report/models/AccessibilityReport.kt | 25 +++ .../accessibility/report/models/FileType.kt | 27 +++ .../accessibility/report/models/ReportRow.kt | 41 ++++ .../report/models/XslxColumnModel.kt | 36 ++++ .../service/AccessibilityReportService.kt | 162 ++++++++++++++ .../report/writers/BaseFileWriter.kt | 37 ++++ .../report/writers/PlainFileWriter.kt | 76 +++++++ .../report/writers/ResultWriter.kt | 94 ++++++++ .../report/writers/XslxFileWriter.kt | 91 ++++++++ core/pom.xml | 1 + osgi-dependencies/aet-features.xml | 16 ++ pom.xml | 77 ++++++- rest-endpoint/pom.xml | 9 +- ...ccessibilityReportAvailabilityServlet.java | 138 ++++++++++++ .../aet/rest/AccessibilityReportServlet.java | 131 ++++++++++++ .../java/com/cognifide/aet/rest/Helper.java | 13 +- zip/pom.xml | 14 ++ 22 files changed, 1454 insertions(+), 9 deletions(-) create mode 100644 core/accessibility-report/LICENSE create mode 100644 core/accessibility-report/README.md create mode 100644 core/accessibility-report/pom.xml create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityCode.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityIssue.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityReport.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/FileType.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/ReportRow.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/XslxColumnModel.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/service/AccessibilityReportService.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/BaseFileWriter.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/PlainFileWriter.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/ResultWriter.kt create mode 100644 core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/XslxFileWriter.kt create mode 100644 rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportAvailabilityServlet.java create mode 100644 rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportServlet.java diff --git a/core/accessibility-report/LICENSE b/core/accessibility-report/LICENSE new file mode 100644 index 000000000..6ed9f8d74 --- /dev/null +++ b/core/accessibility-report/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2013 Cognifide Limited + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/core/accessibility-report/README.md b/core/accessibility-report/README.md new file mode 100644 index 000000000..20bbe006b --- /dev/null +++ b/core/accessibility-report/README.md @@ -0,0 +1,5 @@ +## About +This module will aggregate all accessibility errors/warnings/notices into txt and xlsx files. +txt - formatted data, ready to paste into jira task (sometimes You will need to split it for comments because message limit will be exceeded) +xlsx - file with possibility to filter tasks + diff --git a/core/accessibility-report/pom.xml b/core/accessibility-report/pom.xml new file mode 100644 index 000000000..a7fcf8359 --- /dev/null +++ b/core/accessibility-report/pom.xml @@ -0,0 +1,145 @@ + + + + 4.0.0 + + + core + com.cognifide.aet + 3.3.1-SNAPSHOT + + + accessibility-report + bundle + + AET :: Core :: Accessibility Report + Accessibility report generator module + + + + + com.cognifide.aet + communication-api + + + com.cognifide.aet + datastorage-api + + + + org.osgi + org.osgi.service.component.annotations + + + org.osgi + org.osgi.annotation + + + org.osgi + org.osgi.service.metatype.annotations + + + + org.slf4j + slf4j-api + + + + org.mongodb + mongo-java-driver + + + com.google.code.gson + gson + + + commons-io + commons-io + + + org.apache.commons + commons-text + + + org.apache.commons + commons-collections4 + + + + org.apache.poi + poi-ooxml + + + + org.jetbrains.kotlin + kotlin-osgi-bundle + + + + + org.jetbrains + annotations + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + + + + org.apache.felix + maven-bundle-plugin + + + default-bundle + + + Cognifide Ltd. + javax.annotation;resolution:=optional,* + com.cognifide.aet.accessibility.report.service.* + + + + + + + + + + + + upload + + + + org.codehaus.mojo + wagon-maven-plugin + + + + + + diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityCode.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityCode.kt new file mode 100644 index 000000000..49a2cb40e --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityCode.kt @@ -0,0 +1,48 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.models + +import java.util.Objects + +class AccessibilityCode(accessibilityIssueCode: String) { + val code: String + val solutions: Iterable + + init { + val splitIssueCode = accessibilityIssueCode.split(".") + val issueCode = splitIssueCode[3] + val splitCode = issueCode.split("_") + this.code = "${splitCode[0]}_${splitCode[1]}_${splitCode[2]}\n" + + this.solutions = splitIssueCode[4].split(",").asIterable() + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + + val that = other as AccessibilityCode + return code == that.code + } + + override fun hashCode(): Int { + return Objects.hash(31, code.hashCode()) + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityIssue.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityIssue.kt new file mode 100644 index 000000000..cec6b524a --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityIssue.kt @@ -0,0 +1,76 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.models + +import java.io.Serializable +import java.util.Objects + +class AccessibilityIssue( + val type: IssueType, + val message: String, + val code: String, + val elementString: String, + val elementStringAbbreviated: String) : Serializable { + + var lineNumber = 0 + var columnNumber = 0 + var isExcluded = false + private set + var url: String = "" + + fun getAccessibilityCode(): AccessibilityCode = AccessibilityCode(code) + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null || javaClass != other.javaClass) { + return false + } + + val that = other as AccessibilityIssue + return (lineNumber == that.lineNumber + && columnNumber == that.columnNumber + && isExcluded == that.isExcluded + && type == that.type + && message == that.message + && code == that.code + && elementString == that.elementString + && elementStringAbbreviated == that.elementStringAbbreviated + && url == that.url) + } + + override fun hashCode(): Int { + return Objects.hash( + type, + message, + code, + elementString, + elementStringAbbreviated, + lineNumber, + columnNumber, + isExcluded, + url) + } + + enum class IssueType { + ERROR, WARN, NOTICE, UNKNOWN; + } + + companion object { + private const val serialVersionUID = -53665467524179701L + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityReport.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityReport.kt new file mode 100644 index 000000000..48922f1ff --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/AccessibilityReport.kt @@ -0,0 +1,25 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.models + +import java.io.Serializable + +class AccessibilityReport(val nonExcludedIssues: List) : Serializable { + + companion object { + private const val serialVersionUID = -3950927262685618465L + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/FileType.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/FileType.kt new file mode 100644 index 000000000..9bfce3f70 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/FileType.kt @@ -0,0 +1,27 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.models + +enum class FileType(val extension: String, val mimeType: String) { + TEXT("txt", "text/plain"), + XSLX("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + + companion object { + fun fromExtension(extension: String): FileType = + values().find { it.extension == extension.toLowerCase() } + ?: throw IllegalArgumentException("File type not supported, extension provided was: $extension") + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/ReportRow.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/ReportRow.kt new file mode 100644 index 000000000..be83afa08 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/ReportRow.kt @@ -0,0 +1,41 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.models + +import org.apache.commons.text.StringEscapeUtils +import java.net.MalformedURLException +import java.net.URL + +class ReportRow( + code: String, message: String, issue: AccessibilityIssue, solutions: String) { + + val code = code.trim { it <= ' ' } + val message = message.trim { it <= ' ' } + val path = tryGetUrlsPath(issue) + val url = issue.url + val lineNumber = issue.lineNumber + val snippet = StringEscapeUtils.unescapeHtml4(issue.elementStringAbbreviated).trim { it <= ' ' } + val solutions = solutions + + private fun tryGetUrlsPath(issue: AccessibilityIssue): String { + return try { + URL(issue.url).path + } catch (e: MalformedURLException) { + e.printStackTrace() + issue.url + } + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/XslxColumnModel.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/XslxColumnModel.kt new file mode 100644 index 000000000..d6f8a0ed7 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/models/XslxColumnModel.kt @@ -0,0 +1,36 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.models + +enum class XslxColumnModel(val columnName: String) { + ERROR_CODE("Error Code"), + MESSAGE("Message"), + PATH("Path"), + URL("Url"), + LINE_NUMBER("Line"), + SNIPPET("Snippet"), + SOLUTIONS("Solutions"); + + companion object { + private val items = listOf(*values()) + + val itemsTotal = items.size + + fun forEach(consumer: (XslxColumnModel) -> Unit) { + items.forEach(consumer) + } + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/service/AccessibilityReportService.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/service/AccessibilityReportService.kt new file mode 100644 index 000000000..b3a4aab5a --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/service/AccessibilityReportService.kt @@ -0,0 +1,162 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.service + +import com.cognifide.aet.accessibility.report.models.AccessibilityIssue +import com.cognifide.aet.accessibility.report.models.AccessibilityIssue.IssueType +import com.cognifide.aet.accessibility.report.models.AccessibilityReport +import com.cognifide.aet.accessibility.report.models.FileType +import com.cognifide.aet.accessibility.report.writers.ResultWriter +import com.cognifide.aet.vs.ArtifactsDAO +import com.cognifide.aet.vs.DBKey +import com.cognifide.aet.vs.MetadataDAO +import com.cognifide.aet.vs.StorageException +import org.osgi.service.component.annotations.Component +import org.osgi.service.component.annotations.Reference +import org.slf4j.LoggerFactory +import java.io.IOException +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.function.Consumer +import java.util.stream.Collectors + + +@Component(service = [AccessibilityReportService::class], immediate = true) +class AccessibilityReportService { + + @Reference + private lateinit var metadataDAO: MetadataDAO + + @Reference + private lateinit var artifactsDAO: ArtifactsDAO + + fun newTask() = ServiceTask() + + private fun isAvailable(task: ServiceTask): Boolean = + retrieveToMap(task.dbKey, task.correlationId) + .isNotEmpty() + + private fun getReport(task: ServiceTask): ByteArray { + val issuesForTask = issuesForTask(task) + val resultWriter = ResultWriter.forTask(task) + + return resultWriter.write(task, issuesForTask) + } + + private fun issuesForTask(task: ServiceTask): List = + retrieveToMap(task.dbKey, task.correlationId) + .entries + .flatMap { entry -> + entry.value + .mapNotNull { objectId -> + retrieve(task.dbKey, objectId) + } + .flatMap { report -> + report.nonExcludedIssues + .filter { it.type == task.verbosityLevel } + .onEach { it.url = entry.key } + } + } + .sortedBy { it.lineNumber } + + private fun retrieveToMap(dbKey: DBKey, correlationId: String): Map> = + metadataDAO + .getSuite(dbKey, correlationId) + .tests + .stream() //operate on stream as we need the Java .collect() method + .flatMap { test -> test.urls.stream() } + .map { url -> + Pair( + url.domain + url.url, + url.steps + .asSequence() + .filter { it.name == "accessibility" } + .map { it.comparators } + .flatMap { it.asSequence() } + .filter { it.type == "accessibility" } + .map { it.stepResult.artifactId } + .toMutableList() + ) + } + .filter { pair -> pair.second.isNotEmpty() } + .collect( + Collectors.toMap( + { it.first }, + { it.second }, + { left, right -> left.apply { this.addAll(right) } })) + + private fun retrieve(dbKey: DBKey, objectId: String): AccessibilityReport? = + try { + artifactsDAO.getJsonFormatArtifact(dbKey, objectId, AccessibilityReport::class.java) + } catch (e: IOException) { + LOG.error("Exception while retrieving report", e) + null + } + + inner class ServiceTask { + lateinit var dbKey: DBKey + lateinit var correlationId: String + lateinit var verbosityLevel: IssueType + lateinit var fileType: FileType + lateinit var setter: Consumer + val filename: String by lazy { + correlationId + + "-" + + verbosityLevel.toString().toLowerCase() + + "-" + + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HHmmss")) + + "." + + fileType.extension + } + + fun withDbKey(dbKey: DBKey): ServiceTask { + this.dbKey = dbKey + return this + } + + fun withObjectId(objectId: String): ServiceTask { + this.correlationId = objectId + return this + } + + @Throws(IllegalArgumentException::class) + fun withVerbosity(verbosityLevel: String): ServiceTask { + this.verbosityLevel = IssueType.valueOf(verbosityLevel.toUpperCase()) + return this + } + + @Throws(IllegalArgumentException::class) + fun withExtension(extension: String): ServiceTask { + this.fileType = FileType.fromExtension(extension) + return this + } + + fun withMimetypeSetter(setter: Consumer): ServiceTask { + this.setter = setter + return this + } + + @Throws(IOException::class, StorageException::class) + fun invokeReport(): ByteArray = getReport(this) + + @Throws(IOException::class, StorageException::class) + fun invokeIsAvailable(): Boolean = isAvailable(this) + } + + companion object { + private val LOG by lazy { LoggerFactory.getLogger(AccessibilityReportService::class.java) } + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/BaseFileWriter.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/BaseFileWriter.kt new file mode 100644 index 000000000..f4beed567 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/BaseFileWriter.kt @@ -0,0 +1,37 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.writers + +import com.cognifide.aet.accessibility.report.models.ReportRow +import java.io.IOException + +interface BaseFileWriter { + + fun writeHeader() + + fun writeCodeHeader(code: String) + + fun writeMessage(message: String) + + fun writeRow(reportRow: ReportRow) + + fun writeIssueSeparator() + + fun writeSolutions(solutions: String) + + @Throws(IOException::class) + fun toByteArray(): ByteArray +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/PlainFileWriter.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/PlainFileWriter.kt new file mode 100644 index 000000000..a5404ac77 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/PlainFileWriter.kt @@ -0,0 +1,76 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.writers + +import com.cognifide.aet.accessibility.report.models.ReportRow +import com.cognifide.aet.accessibility.report.service.AccessibilityReportService +import java.io.ByteArrayOutputStream +import java.io.OutputStreamWriter + +class PlainFileWriter(private val task: AccessibilityReportService.ServiceTask) : BaseFileWriter { + private val stream = ByteArrayOutputStream() + private val writer = OutputStreamWriter(stream) + + override fun writeHeader() { + writer.write( + """ + Company: ${task.dbKey.company} + Project: ${task.dbKey.project} + Suite: ${task.correlationId} + + """.trimIndent()) + } + + override fun writeCodeHeader(code: String) { + writer.write("************************************************************************\n$code") + } + + override fun writeMessage(message: String) { + writer.write("- *$message*\n\n") + } + + override fun writeRow(reportRow: ReportRow) { + writer.write( + """ + Path: [${reportRow.path}|${reportRow.url}] + Line number: ${reportRow.lineNumber} + {code:html} + ${reportRow.snippet} + {code} + Suggested fix technique: ${reportRow.solutions} + + + """.trimIndent()) + } + + override fun writeSolutions(solutions: String) { + writer.write( + """ + More details at: https://www.w3.org/TR/WCAG20/#meaning-doc-lang-id + Suggested fix technique: (list of techniques - https://www.w3.org/TR/WCAG20-TECHS/) + $solutions + + + + """.trimIndent()) + } + + override fun writeIssueSeparator() { + writer.write("\n") + } + + override fun toByteArray(): ByteArray = stream.toByteArray() +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/ResultWriter.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/ResultWriter.kt new file mode 100644 index 000000000..8b98d4be7 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/ResultWriter.kt @@ -0,0 +1,94 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.writers + +import com.cognifide.aet.accessibility.report.models.AccessibilityIssue +import com.cognifide.aet.accessibility.report.models.FileType +import com.cognifide.aet.accessibility.report.models.ReportRow +import com.cognifide.aet.accessibility.report.service.AccessibilityReportService.ServiceTask +import org.apache.commons.collections4.CollectionUtils +import java.io.IOException + +class ResultWriter(private val writer: BaseFileWriter) { + + @Throws(IOException::class) + fun write(task: ServiceTask, issueList: List): ByteArray { + if (CollectionUtils.isEmpty(issueList)) return ByteArray(0) + + // change mime type only on success + return writeToFile(issueList) + .also { task.setter.accept(task.fileType.mimeType) } + } + + @Throws(IOException::class) + private fun writeToFile(issueList: List): ByteArray { + writer.writeHeader() + + issueList + .groupBy { it.getAccessibilityCode().code } + .forEach { (code, codeIssues) -> + val joinedSolutions = getSolutionsForIssue(codeIssues) + + writer.writeCodeHeader(code) + + codeIssues + .groupBy { it.message } + .forEach { (message, messageIssues) -> + writer.writeMessage(message) + + messageIssues + .groupBy { it.url } + .entries + .sortedBy { it.key } + .forEach { entry -> + entry.value + .sortedBy { it.lineNumber } + .forEach { issue -> + ReportRow(code, message, issue, joinedSolutions) + .let { writer.writeRow(it) } + } + + writer.writeIssueSeparator() + } + } + + writer.writeSolutions(joinedSolutions) + } + + return writer.toByteArray() + } + + private fun getSolutionsForIssue(issues: List): String { + return issues + .flatMap { it.getAccessibilityCode().solutions } + .distinct() + .joinToString(", ") + } + + companion object Factory { + fun forTask(task: ServiceTask): ResultWriter { + val filename = task.filename //fixme should this be filename or sth else? + + val writer: BaseFileWriter = + when (task.fileType) { + FileType.TEXT -> PlainFileWriter(task) + FileType.XSLX -> XslxFileWriter(task) + } + + return ResultWriter(writer) + } + } +} diff --git a/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/XslxFileWriter.kt b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/XslxFileWriter.kt new file mode 100644 index 000000000..3fd14aa98 --- /dev/null +++ b/core/accessibility-report/src/main/kotlin/com/cognifide/aet/accessibility/report/writers/XslxFileWriter.kt @@ -0,0 +1,91 @@ +/* + * AET + * + * Copyright (C) 2020 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.accessibility.report.writers + +import com.cognifide.aet.accessibility.report.models.ReportRow +import com.cognifide.aet.accessibility.report.models.XslxColumnModel +import com.cognifide.aet.accessibility.report.service.AccessibilityReportService +import org.apache.poi.common.usermodel.HyperlinkType +import org.apache.poi.ss.usermodel.Sheet +import org.apache.poi.ss.usermodel.Workbook +import org.apache.poi.ss.util.CellRangeAddress +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.util.concurrent.atomic.AtomicInteger + +class XslxFileWriter(task: AccessibilityReportService.ServiceTask) : BaseFileWriter { + private val workbook: Workbook = XSSFWorkbook() + private val sheet: Sheet = workbook.createSheet("Accessibility Issues - ${task.filename}") + private val rowsCounter: AtomicInteger = AtomicInteger() + + + override fun writeHeader() { + val headerRow = sheet.createRow(0) + rowsCounter.incrementAndGet() + + XslxColumnModel.forEach { column -> + val cell = headerRow.createCell(column.ordinal) + cell.setCellValue(column.columnName) + } + } + + override fun writeCodeHeader(code: String) { + // noop + } + + override fun writeMessage(message: String) { + // noop + } + + override fun writeRow(reportRow: ReportRow) { + val row = sheet.createRow(rowsCounter.getAndIncrement()) + row.createCell(XslxColumnModel.ERROR_CODE.ordinal).setCellValue(reportRow.code) + row.createCell(XslxColumnModel.MESSAGE.ordinal).setCellValue(reportRow.message) + row.createCell(XslxColumnModel.PATH.ordinal).setCellValue(reportRow.path) + + val link = workbook.creationHelper.createHyperlink(HyperlinkType.URL) + link.address = reportRow.url + + val linkCell = row.createCell(XslxColumnModel.URL.ordinal) + linkCell.hyperlink = link + linkCell.setCellValue(reportRow.url) + + row.createCell(XslxColumnModel.LINE_NUMBER.ordinal).setCellValue(reportRow.lineNumber.toDouble()) + row.createCell(XslxColumnModel.SNIPPET.ordinal).setCellValue(reportRow.snippet) + row.createCell(XslxColumnModel.SOLUTIONS.ordinal).setCellValue(reportRow.solutions) + } + + override fun writeIssueSeparator() { + // noop + } + + override fun writeSolutions(solutions: String) { + // noop + } + + @Throws(IOException::class) + override fun toByteArray(): ByteArray { + XslxColumnModel.forEach { column -> sheet.autoSizeColumn(column.ordinal) } + + sheet.setAutoFilter( + CellRangeAddress(0, rowsCounter.get(), 0, XslxColumnModel.itemsTotal)) + + return ByteArrayOutputStream() + .also { workbook.write(it) } + .toByteArray() + } +} diff --git a/core/pom.xml b/core/pom.xml index 60e8bf0a7..921ad8ec5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -34,6 +34,7 @@ Root module for AET core functionalities + accessibility-report cleaner communication datastorage diff --git a/osgi-dependencies/aet-features.xml b/osgi-dependencies/aet-features.xml index cc3e6f0f4..2c4a4bd9e 100644 --- a/osgi-dependencies/aet-features.xml +++ b/osgi-dependencies/aet-features.xml @@ -55,6 +55,8 @@ mvn:org.apache.commons/commons-lang3/3.7 mvn:org.apache.commons/commons-exec/1.1 + mvn:org.apache.commons/commons-collections4/4.4 + mvn:org.apache.commons/commons-text/1.8 mvn:org.apache.felix/org.apache.felix.http.servlet-api/1.1.2 @@ -121,4 +123,18 @@ mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.xmlbeans/2.6.0_2 + + mvn:org.jetbrains.kotlin/kotlin-osgi-bundle/1.3.72 + + + + wrap:mvn:org.apache.poi/poi/4.1.2 + wrap:mvn:org.apache.poi/poi-ooxml/4.1.2 + wrap:mvn:org.apache.poi/poi-ooxml-schemas/4.1.2 + wrap:mvn:org.apache.poi/ooxml-schemas/1.4 + mvn:org.apache.commons/commons-math3/3.6.1 + mvn:commons-codec/commons-codec/1.11 + mvn:com.github.luben/zstd-jni/1.4.5-3 + + diff --git a/pom.xml b/pom.xml index 238564a3e..c4cd16063 100644 --- a/pom.xml +++ b/pom.xml @@ -65,10 +65,6 @@ - - ${min.maven.version} - - api client @@ -116,7 +112,7 @@ 1.8 9.2.10.v20150310 - 3.2.2 + 4.11 @@ -151,6 +147,8 @@ Add this file or override maven property for it's location. --> maven.properties + + 1.3.72 @@ -278,6 +276,29 @@ 2.21.1 provided + + org.apache.poi + poi-ooxml + 4.1.2 + provided + + + org.apache.poi + ooxml-schemas + 1.4 + + + org.apache.commons + commons-text + 1.8 + provided + + + org.apache.commons + commons-collections4 + 4.4 + provided + commons-codec commons-codec @@ -321,6 +342,18 @@ provided + + org.jetbrains.kotlin + kotlin-osgi-bundle + ${kotlin.version} + provided + + + org.jetbrains + annotations + 19.0.0 + + org.apache.httpcomponents @@ -405,6 +438,12 @@ ${project.version} provided + + com.cognifide.aet + accessibility-report + ${project.version} + provided + com.cognifide.aet datastorage @@ -712,6 +751,11 @@ truezip-maven-plugin 1.2 + + com.samaxes.maven + minify-maven-plugin + 1.7.6 + org.codehaus.mojo @@ -795,6 +839,29 @@ 3.6.0 + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + ${project.basedir}/src/main/kotlin + + + + + + 1.8 + + + org.eclipse.jetty jetty-maven-plugin diff --git a/rest-endpoint/pom.xml b/rest-endpoint/pom.xml index 9256f86aa..ac0928ad4 100644 --- a/rest-endpoint/pom.xml +++ b/rest-endpoint/pom.xml @@ -42,6 +42,10 @@ com.cognifide.aet datastorage-api + + com.cognifide.aet + accessibility-report + org.osgi @@ -133,8 +137,7 @@ Cognifide Ltd. - com.cognifide.aet.rest.*;version="${project.version}" - + com.cognifide.aet.rest.* @@ -156,4 +159,4 @@ - \ No newline at end of file + diff --git a/rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportAvailabilityServlet.java b/rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportAvailabilityServlet.java new file mode 100644 index 000000000..c71c4b766 --- /dev/null +++ b/rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportAvailabilityServlet.java @@ -0,0 +1,138 @@ +/* + * AET + *

+ * Copyright (C) 2013 Cognifide Limited + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.rest; + +import static com.cognifide.aet.rest.Helper.isValidCorrelationId; +import static com.cognifide.aet.rest.Helper.responseAsJson; + +import com.cognifide.aet.accessibility.report.service.AccessibilityReportService; +import com.cognifide.aet.rest.helpers.BundleVersionProvider; +import com.cognifide.aet.vs.ArtifactsDAO; +import com.cognifide.aet.vs.DBKey; +import com.cognifide.aet.vs.MetadataDAO; +import com.cognifide.aet.vs.StorageException; +import com.google.gson.reflect.TypeToken; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.HttpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(immediate = true) +public class AccessibilityReportAvailabilityServlet extends BasicDataServlet { + + private static final long serialVersionUID = 7801533499484174272L; + + private static final Logger LOGGER = LoggerFactory.getLogger(AccessibilityReportAvailabilityServlet.class); + + private static final String APP_VERSION_HEADER = "X-Application-Version"; + + @Reference + private MetadataDAO metadataDAO; + + @Reference + private ArtifactsDAO artifactsDAO; + + @Reference + private BundleVersionProvider bundleVersionProvider; + + @Reference + private AccessibilityReportService accessibilityReportService; + + @Reference + protected transient HttpService httpService; + + + @Override + protected void process(DBKey dbKey, HttpServletRequest request, HttpServletResponse response) + throws IOException { + String correlationId = request.getParameter(Helper.CORRELATION_ID_PARAM); + String verbosityType = request.getParameter("type"); + String extension = request.getParameter("extension"); + + response.setHeader(APP_VERSION_HEADER, bundleVersionProvider.getBundleVersion()); + response.setCharacterEncoding("UTF-8"); + + try { + if (isValidCorrelationId(correlationId)) { + AccessibilityReportService.ServiceTask serviceTask = + accessibilityReportService + .newTask() + .withDbKey(dbKey) + .withObjectId(correlationId); + + boolean isAvailable = serviceTask.invokeIsAvailable(); + Map json = new HashMap<>(); + + response.setContentType("application/json"); + json.put("isAvailable", isAvailable); + + if (!isAvailable) { + response.setStatus(HttpURLConnection.HTTP_NOT_FOUND); + } + + response.getWriter().write(GSON.toJson(json, new TypeToken>() {}.getType())); + } else { + createNotFoundResponse(response, dbKey, correlationId); + } + } catch (StorageException | IllegalArgumentException e) { + LOGGER.error("Failed during retrieval", e); + response.setStatus(HttpURLConnection.HTTP_BAD_REQUEST); + response.getWriter().write(responseAsJson(GSON, "Exception while processing: %s", e.getMessage())); + } + } + + @Override + protected HttpService getHttpService() { + return this.httpService; + } + + @Override + protected void setHttpService(HttpService httpService) { + this.httpService = httpService; + } + + private void createNotFoundResponse(HttpServletResponse response, DBKey dbKey, String correlationId) throws IOException { + response.setStatus(HttpURLConnection.HTTP_NOT_FOUND); + String paramsValuesMessage = ""; + + if (correlationId != null) { + paramsValuesMessage = String.format("correlationId: %s", correlationId); + } + + response.getWriter() + .write(responseAsJson(GSON, "Unable to get accessibility report with %s for %s", paramsValuesMessage, dbKey.toString())); + } + + + @Activate + public void start() { + register(Helper.getAccessibilityReportAvailabilityPath()); + } + + @Deactivate + public void stop() { + unregister(Helper.getAccessibilityReportAvailabilityPath()); + } +} diff --git a/rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportServlet.java b/rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportServlet.java new file mode 100644 index 000000000..275d94c50 --- /dev/null +++ b/rest-endpoint/src/main/java/com/cognifide/aet/rest/AccessibilityReportServlet.java @@ -0,0 +1,131 @@ +/* + * AET + *

+ * Copyright (C) 2013 Cognifide Limited + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package com.cognifide.aet.rest; + +import static com.cognifide.aet.rest.Helper.isValidCorrelationId; +import static com.cognifide.aet.rest.Helper.responseAsJson; + +import com.cognifide.aet.accessibility.report.service.AccessibilityReportService; +import com.cognifide.aet.rest.helpers.BundleVersionProvider; +import com.cognifide.aet.vs.ArtifactsDAO; +import com.cognifide.aet.vs.DBKey; +import com.cognifide.aet.vs.MetadataDAO; +import com.cognifide.aet.vs.StorageException; +import java.io.IOException; +import java.net.HttpURLConnection; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.HttpService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component(immediate = true) +public class AccessibilityReportServlet extends BasicDataServlet { + + private static final long serialVersionUID = 7233205495217724069L; + + private static final Logger LOGGER = LoggerFactory.getLogger(AccessibilityReportServlet.class); + + private static final String APP_VERSION_HEADER = "X-Application-Version"; + + @Reference + protected MetadataDAO metadataDAO; + + @Reference + protected ArtifactsDAO artifactsDAO; + + @Reference + private BundleVersionProvider bundleVersionProvider; + + @Reference + private AccessibilityReportService accessibilityReportService; + + @Reference + protected transient HttpService httpService; + + + @Override + protected void process(DBKey dbKey, HttpServletRequest request, HttpServletResponse response) + throws IOException { + String correlationId = request.getParameter(Helper.CORRELATION_ID_PARAM); + String verbosityType = request.getParameter(Helper.TYPE_PARAM); + String extension = request.getParameter(Helper.EXTENSION_PARAM); + + response.setHeader(APP_VERSION_HEADER, bundleVersionProvider.getBundleVersion()); + response.setCharacterEncoding("UTF-8"); + + try { + if (isValidCorrelationId(correlationId)) { + AccessibilityReportService.ServiceTask serviceTask = + accessibilityReportService + .newTask() + .withDbKey(dbKey) + .withObjectId(correlationId) + .withVerbosity(verbosityType) + .withExtension(extension) + .withMimetypeSetter(response::setContentType); + + byte[] byteArray = serviceTask.invokeReport(); + + response.setHeader("Content-disposition", "attachment; filename=" + serviceTask.getFilename()); + + response.getOutputStream().write(byteArray); + } else { + createNotFoundResponse(response, dbKey, correlationId); + } + } catch (StorageException | IllegalArgumentException e) { + LOGGER.error("Failed during retrieval", e); + response.setStatus(HttpURLConnection.HTTP_BAD_REQUEST); + response.getWriter().write(responseAsJson(GSON, "Exception while processing: %s", e.getMessage())); + } + } + + @Override + protected HttpService getHttpService() { + return this.httpService; + } + + @Override + protected void setHttpService(HttpService httpService) { + this.httpService = httpService; + } + + private void createNotFoundResponse(HttpServletResponse response, DBKey dbKey, String correlationId) throws IOException { + response.setStatus(HttpURLConnection.HTTP_NOT_FOUND); + String paramsValuesMessage = ""; + + if (correlationId != null) { + paramsValuesMessage = String.format("correlationId: %s", correlationId); + } + + response.getWriter() + .write(responseAsJson(GSON, "Unable to get accessibility report with %s for %s", paramsValuesMessage, dbKey.toString())); + } + + @Activate + public void start() { + register(Helper.getAccessibilityReportPath()); + } + + @Deactivate + public void stop() { + unregister(Helper.getAccessibilityReportPath()); + } +} diff --git a/rest-endpoint/src/main/java/com/cognifide/aet/rest/Helper.java b/rest-endpoint/src/main/java/com/cognifide/aet/rest/Helper.java index d16ea2a72..4b65d5544 100644 --- a/rest-endpoint/src/main/java/com/cognifide/aet/rest/Helper.java +++ b/rest-endpoint/src/main/java/com/cognifide/aet/rest/Helper.java @@ -30,6 +30,8 @@ public final class Helper { static final String METADATA_PART_PATH = "metadata"; static final String RERUN_PART_PATH = "suite-rerun"; static final String HISTORY_PART_PATH = "history"; + static final String ACCESSIBILITY_PART_PATH = "/accessibility"; + static final String AVAILABLE_PART_PATH = "/available"; static final String REPORT_PART_PATH = "/report"; static final String CONFIGS_PART_PATH = "/configs"; static final String LOCK_PART_PATH = "/lock"; @@ -43,6 +45,7 @@ public final class Helper { public static final String VERSION_PARAM = "version"; public static final String ID_PARAM = "id"; public static final String TYPE_PARAM = "type"; + public static final String EXTENSION_PARAM = "extension"; public static final String REPORT_PART_PATH_DEFAULT_PAGE = "index.html"; public static final String TEST_RERUN_PARAM = "testName"; public static final String URL_RERUN_PARAM = "testUrl"; @@ -79,11 +82,19 @@ public static String getXUnitPath() { return XUNIT_PART_PATH; } + public static String getAccessibilityReportAvailabilityPath() { + return REST_PREFIX + ACCESSIBILITY_PART_PATH + REPORT_PART_PATH + AVAILABLE_PART_PATH; + } + + public static String getAccessibilityReportPath() { + return REST_PREFIX + ACCESSIBILITY_PART_PATH + REPORT_PART_PATH; + } + public static String getReportPathDefaultPage() { return REPORT_PART_PATH + PATH_SEPARATOR + REPORT_PART_PATH_DEFAULT_PAGE; } - public static String getRerunPath(){ + public static String getRerunPath() { return REST_PREFIX + PATH_SEPARATOR + RERUN_PART_PATH; } diff --git a/zip/pom.xml b/zip/pom.xml index 97ced7879..ec91aaaae 100644 --- a/zip/pom.xml +++ b/zip/pom.xml @@ -52,6 +52,11 @@ core bundles dependencies configured here as they are needed only for release --> + + ${project.groupId} + accessibility-report + ${project.version} + ${project.groupId} cleaner @@ -133,6 +138,10 @@ + + ${project.groupId} + accessibility-report + ${project.groupId} cleaner @@ -261,6 +270,10 @@ validation-api + + ${project.groupId} + accessibility-report + ${project.groupId} cleaner @@ -372,6 +385,7 @@ ${project.groupId}.jobs-api.jar ${project.groupId}.validation-api.jar + ${project.groupId}.accessibility-report.jar ${project.groupId}.cleaner.jar ${project.groupId}.communication.jar ${project.groupId}.proxy.jar From 3f9411163dd093cfd6795bd8d8f7b6480fa480ea Mon Sep 17 00:00:00 2001 From: Wiktor Jurczyszyn Date: Tue, 23 Jun 2020 13:46:48 +0200 Subject: [PATCH 2/8] button and modal for accessibility report --- report/src/main/webapp/app/app.config.js | 1 + report/src/main/webapp/app/app.module.js | 1 + .../accessibilityModal.controller.js | 61 ++++++++++++++ .../accessibilityModal.view.html | 79 +++++++++++++++++++ .../toolbar/toolbarBottom.controller.js | 43 +++++++++- .../layout/toolbar/toolbarBottom.view.html | 18 ++++- .../assets/sass/_accessibilityReport.scss | 16 ++++ .../src/main/webapp/assets/sass/_toolbar.scss | 6 +- report/src/main/webapp/assets/sass/main.scss | 1 + 9 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.controller.js create mode 100644 report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.view.html create mode 100644 report/src/main/webapp/assets/sass/_accessibilityReport.scss diff --git a/report/src/main/webapp/app/app.config.js b/report/src/main/webapp/app/app.config.js index 6c5c9411b..dab73b59d 100644 --- a/report/src/main/webapp/app/app.config.js +++ b/report/src/main/webapp/app/app.config.js @@ -77,6 +77,7 @@ require.config({ //modals 'unsavedChangesModalController': 'layout/modal/unsavedChanges/unsavedChangesModal.controller', 'noteModalController': 'layout/modal/note/noteModal.controller', + 'accessibilityModalController': 'layout/modal/accessibility/accessibilityModal.controller', 'historyModalController': 'layout/modal/history/historyModal.controller' }, shim: { diff --git a/report/src/main/webapp/app/app.module.js b/report/src/main/webapp/app/app.module.js index 9a271df2c..e32058dc3 100644 --- a/report/src/main/webapp/app/app.module.js +++ b/report/src/main/webapp/app/app.module.js @@ -66,6 +66,7 @@ define(['angularAMD', 'filterInformationDirective', // modals 'noteModalController', + 'accessibilityModalController', 'historyModalController', 'unsavedChangesModalController'], function (angularAMD, _) { diff --git a/report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.controller.js b/report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.controller.js new file mode 100644 index 000000000..6bae1154c --- /dev/null +++ b/report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.controller.js @@ -0,0 +1,61 @@ +/* + * AET + * + * Copyright (C) 2013 Cognifide Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +define(['angularAMD', 'endpointConfiguration'], function (angularAMD) { + 'use strict'; + angularAMD.controller('accessibilityModalController', AccessibilityModalController); + + function AccessibilityModalController($scope, $http, $uibModalInstance, model, $stateParams, $window, endpointConfiguration) { + + var vm = this; + init(); + + /*************************************** + *********** Private methods ********* + ***************************************/ + + function init() { + vm.report = { + logLevel: 'ERROR', + format: 'xlsx' + } + vm.generateReport = generateReport; + vm.cancelReport = cancelReport; + vm.model = model; + } + + function generateReport() { + const { company, correlationId, name: suite, project } = model; + const baseUrl = endpointConfiguration.getEndpoint().getUrl; + const downloadUrl = 'accessibility/report?' + + 'company=' + company + + '&project=' + project + + '&suite=' + suite + + '&correlationId=' + correlationId + + '&type=' + vm.report.logLevel + + '&extenstion=' + vm.report.format; + + $window.open(baseUrl + downloadUrl, "_blank"); + + $uibModalInstance.close(); + } + + function cancelReport() { + $uibModalInstance.close(); + } + } +}); diff --git a/report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.view.html b/report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.view.html new file mode 100644 index 000000000..1606dde94 --- /dev/null +++ b/report/src/main/webapp/app/layout/modal/accessibility/accessibilityModal.view.html @@ -0,0 +1,79 @@ + +

+ + \ No newline at end of file diff --git a/report/src/main/webapp/app/layout/toolbar/toolbarBottom.controller.js b/report/src/main/webapp/app/layout/toolbar/toolbarBottom.controller.js index bd0c06f26..2edb76782 100644 --- a/report/src/main/webapp/app/layout/toolbar/toolbarBottom.controller.js +++ b/report/src/main/webapp/app/layout/toolbar/toolbarBottom.controller.js @@ -20,12 +20,13 @@ define([], function () { return ['$scope', '$rootScope', '$uibModal', '$stateParams', 'patternsService', 'metadataAccessService', 'notesService', 'viewModeService', 'suiteInfoService', 'rerunService', 'historyService', + '$http', 'endpointConfiguration', ToolbarBottomController ]; function ToolbarBottomController($scope, $rootScope, $uibModal, $stateParams, patternsService, metadataAccessService, notesService, viewModeService, - suiteInfoService, rerunService, historyService) { + suiteInfoService, rerunService, historyService, $http, endpointConfiguration) { var vm = this; // disables accept button if compared against another suite patterns @@ -35,6 +36,7 @@ define([], function () { vm.showAcceptButton = patternsMayBeUpdated; vm.showRevertButton = patternsMarkedForUpdateMayBeReverted; + vm.displayAccessibilityModal = displayAccessibilityModal; vm.displayCommentModal = displayCommentModal; vm.scrollSidepanel = scrollSidepanel; vm.rerunSuite = rerunSuite; @@ -44,7 +46,7 @@ define([], function () { vm.suiteInfoService = suiteInfoService; vm.checkRerunStatus = rerunService.checkRerunStatus; - historyService.fetchHistory(suiteInfoService.getInfo().version, function() { + historyService.fetchHistory(suiteInfoService.getInfo().version, function () { var currentVersion = suiteInfoService.getInfo().version; vm.isLastSuiteVersion = historyService.getNextVersion(currentVersion) == null; }); @@ -64,10 +66,33 @@ define([], function () { updateToolbar(); + updateAccessibilityInfo(); + /*************************************** *********** Private methods ********* ***************************************/ + function updateAccessibilityInfo() { + const { company, correlationId, name: suite, project } = suiteInfoService.getInfo(); + const url = endpointConfiguration.getEndpoint().getUrl; + + $http.get(url + 'accessibility/report/available', { + params: { + company, + project, + suite, + correlationId + } + }) + .then(function (response) { + vm.canGenerateAccessibilityReport = response.data.isAvailable; + }) + .catch(function (error) { + vm.canGenerateAccessibilityReport = false; + console.error(error); + }); + } + function updateToolbar() { vm.viewMode = viewModeService.get(); switch (vm.viewMode) { @@ -112,6 +137,20 @@ define([], function () { return result; } + function displayAccessibilityModal() { + $uibModal.open({ + animation: true, + templateUrl: 'app/layout/modal/accessibility/accessibilityModal.view.html', + controller: 'accessibilityModalController', + controllerAs: 'accessibilityModal', + resolve: { + model: function () { + return vm.model; + } + } + }); + } + function displayCommentModal() { $uibModal.open({ animation: true, diff --git a/report/src/main/webapp/app/layout/toolbar/toolbarBottom.view.html b/report/src/main/webapp/app/layout/toolbar/toolbarBottom.view.html index 73377e355..f950f3031 100644 --- a/report/src/main/webapp/app/layout/toolbar/toolbarBottom.view.html +++ b/report/src/main/webapp/app/layout/toolbar/toolbarBottom.view.html @@ -28,7 +28,7 @@