├── .github └── workflows │ └── gradle.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── LICENSES ├── Apache-2.0.txt └── CC0-1.0.txt ├── README.md ├── REUSE.toml ├── build-plugin ├── .gitignore ├── build.gradle.kts └── src │ ├── functionalTest │ └── groovy │ │ └── mpern │ │ └── sap │ │ └── commerce │ │ └── build │ │ ├── BootstrapSparseTest.groovy │ │ ├── BootstrapTest.groovy │ │ └── CleanupTest.groovy │ ├── integrationTest │ └── groovy │ │ └── mpern │ │ └── sap │ │ └── commerce │ │ └── build │ │ ├── extensioninfo │ │ └── ExtensionInfoLoaderSpec.groovy │ │ └── util │ │ └── PlatformResolverSpec.groovy │ ├── main │ └── java │ │ └── mpern │ │ └── sap │ │ └── commerce │ │ └── build │ │ ├── HybrisPlugin.java │ │ ├── HybrisPluginExtension.java │ │ ├── extensioninfo │ │ ├── ExtensionInfoException.java │ │ ├── ExtensionInfoLoader.java │ │ └── ExtensionXmlUtil.java │ │ ├── rules │ │ └── HybrisAntRule.java │ │ ├── tasks │ │ ├── GlobClean.java │ │ ├── HybrisAntTask.java │ │ └── UnpackPlatformSparseTask.java │ │ └── util │ │ ├── Extension.java │ │ ├── ExtensionType.java │ │ ├── HybrisPlatform.java │ │ ├── PlatformResolver.java │ │ ├── SparseBootstrap.java │ │ ├── Stopwatch.java │ │ └── Version.java │ └── test │ ├── groovy │ └── mpern │ │ └── sap │ │ └── commerce │ │ └── build │ │ ├── extensioninfo │ │ └── ExtensionXmlUtilTest.groovy │ │ └── util │ │ └── VersionTest.groovy │ └── resources │ ├── test-localextensions.xml │ └── testExtensions │ └── module │ ├── brokenextension │ └── extensioninfo.xml │ └── extension │ └── extensioninfo.xml ├── build.gradle.kts ├── buildSrc ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── kotlin │ ├── mpern.commons.gradle.kts │ └── mpern.plugin.basics.gradle.kts ├── cloud-plugin ├── .gitignore ├── build.gradle.kts └── src │ ├── functionalTest │ └── groovy │ │ └── mpern │ │ └── sap │ │ └── commerce │ │ └── ccv2 │ │ └── CCv2Tests.groovy │ ├── integrationTest │ ├── groovy │ │ └── mpern │ │ │ └── sap │ │ │ └── commerce │ │ │ └── ccv2 │ │ │ ├── ManifestParseSpec.groovy │ │ │ └── validation │ │ │ ├── AddonValidatorSpec.groovy │ │ │ ├── AspectValidatorSpec.groovy │ │ │ ├── AspectWebappValidatorSpec.groovy │ │ │ ├── CloudHotfolderValidatorSpec.groovy │ │ │ ├── ExtensionResolverSpec.groovy │ │ │ ├── IntExtPackValidatorSpec.groovy │ │ │ ├── MediaConversionValidatorSpec.groovy │ │ │ ├── PropertyValidatorSpec.groovy │ │ │ ├── SolrVersionValidatorSpec.groovy │ │ │ ├── TestExtensionResolver.java │ │ │ ├── UseConfigValidatorSpec.groovy │ │ │ └── WebrootValidatorSpec.groovy │ └── resources │ │ ├── integration-test-manifest.json │ │ ├── manifest.2005.json │ │ └── minimal-manifest.json │ └── main │ └── java │ └── mpern │ └── sap │ └── commerce │ └── ccv2 │ ├── CCv2Extension.java │ ├── CloudV2Plugin.java │ ├── model │ ├── Addon.java │ ├── Aspect.java │ ├── ExtensionPack.java │ ├── Manifest.java │ ├── Property.java │ ├── TestConfiguration.java │ ├── UseConfig.java │ ├── Webapp.java │ ├── useconfig │ │ ├── Extensions.java │ │ ├── Languages.java │ │ ├── Properties.java │ │ └── Solr.java │ └── util │ │ └── ParseUtils.java │ ├── tasks │ ├── GenerateLocalextensions.java │ ├── PatchLocalExtensions.java │ └── ValidateManifest.java │ └── validation │ ├── Error.java │ ├── ExtensionValidator.java │ ├── ExtensionsResolver.java │ ├── Level.java │ ├── ValidationUtils.java │ ├── Validator.java │ └── impl │ ├── AddonValidator.java │ ├── AspectValidator.java │ ├── AspectWebappValidator.java │ ├── CloudHotfolderValidator.java │ ├── IntExtPackValidator.java │ ├── ManifestExtensionsResolver.java │ ├── MediaConversionValidator.java │ ├── PropertyValidator.java │ ├── SharedPropertyValidator.java │ ├── SolrVersionValidator.java │ ├── UseConfigValidator.java │ └── WebrootValidator.java ├── docs ├── FAQ.md ├── Plugin-sap.commerce.build.ccv2.md ├── Plugin-sap.commerce.build.md ├── README.md ├── ccv2-validation.md └── images │ ├── launchpad-info.png │ ├── launchpad-object.png │ └── launchpad-search.png ├── gradle.properties ├── gradle ├── greclipse.properties ├── spotless.importorder ├── spotless.xml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── manualTest ├── .gitignore ├── build.gradle.kts ├── manifest.json └── settings.gradle.kts ├── plugin-commons ├── .gitignore ├── build.gradle.kts └── src │ └── main │ └── java │ └── mpern │ └── sap │ └── commerce │ └── commons │ └── Constants.java ├── renovate.json ├── settings.gradle.kts └── test-utils ├── .gitignore ├── build.gradle.kts └── src └── main ├── java └── mpern │ └── sap │ └── commerce │ └── build │ ├── ExtensionsTestUtils.java │ ├── ProjectFolderTestUtils.java │ └── TestUtils.java └── resources ├── ccv2-test-manifest.json ├── cloud-extension-pack-manifest.json ├── dummy-custom-modules └── hybris │ └── bin │ └── custom │ └── module │ ├── myextensionone │ ├── CodeOne.java │ └── extensioninfo.xml │ └── myextensiontwo │ ├── CodeTwo.java │ └── extensioninfo.xml ├── dummy-integration-pack └── hybris │ └── bin │ └── modules │ └── scpi │ └── sapcpiproductexchange │ └── project.properties ├── dummy-platform-new-model ├── build_and_install.sh ├── hybris-Mobile-Apps-SDK │ └── dummy.txt ├── hybris │ └── bin │ │ ├── modules │ │ ├── api-registry │ │ │ └── apiregistryservices │ │ │ │ └── extensioninfo.xml │ │ ├── backoffice-framework │ │ │ ├── backoffice │ │ │ │ └── extensioninfo.xml │ │ │ └── ybackoffice │ │ │ │ └── extensioninfo.xml │ │ ├── base-commerce │ │ │ ├── basecommerce │ │ │ │ └── extensioninfo.xml │ │ │ └── payment │ │ │ │ └── extensioninfo.xml │ │ ├── platform │ │ │ └── yempty │ │ │ │ └── extensioninfo.xml │ │ ├── rule-engine │ │ │ └── ruleengine │ │ │ │ └── extensioninfo.xml │ │ └── search-services │ │ │ └── searchservices │ │ │ └── extensioninfo.xml │ │ └── platform │ │ ├── build.number │ │ └── ext │ │ ├── commons │ │ └── extensioninfo.xml │ │ └── core │ │ └── extensioninfo.xml ├── installer │ └── install.sh ├── test-jar.jar └── test.jar ├── dummy-platform ├── build_and_install.sh ├── hybris-Mobile-Apps-SDK │ └── dummy.txt ├── hybris │ └── bin │ │ ├── ext-template │ │ └── yaccelerator │ │ │ └── src │ │ │ └── dummy.java │ │ └── platform │ │ └── build.number ├── installer │ └── install.sh ├── test-jar.jar └── test.jar ├── localextensions.xml └── manifest.2005.json /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Gradle CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - renovate/** 7 | - main 8 | tags: 9 | - v* 10 | pull_request: 11 | branches: 12 | - main 13 | 14 | jobs: 15 | # Basic Gradle wrapper validation. This step is run first, due to security 16 | # concerns. Running our whole pipeline with Gradle with a corrupt wrapper, 17 | # could expose our secrets, and we definitely want to prevent this. 18 | validation: 19 | runs-on: ubuntu-latest 20 | timeout-minutes: 15 21 | name: Gradle wrapper validation 22 | steps: 23 | - name: Check out repo 24 | uses: actions/checkout@v4 25 | - name: Validate Gradle wrapper 26 | uses: gradle/actions/wrapper-validation@v4 27 | 28 | # Basic Compliance check. Checks if codeStyle is correct and if reuse 29 | # is applied correctly 30 | compliance: 31 | runs-on: ubuntu-latest 32 | name: Compliance check 33 | needs: validation 34 | steps: 35 | - name: Check out repo 36 | uses: actions/checkout@v4 37 | with: 38 | fetch-depth: 0 39 | - name: REUSE Compliance Check 40 | uses: fsfe/reuse-action@v5 41 | - name: Set up Java 42 | uses: actions/setup-java@v4 43 | with: 44 | java-version: 17 45 | distribution: 'sapmachine' 46 | cache: 'gradle' 47 | - name: Setup Gradle 48 | uses: gradle/actions/setup-gradle@v4 49 | - name: Check code style 50 | run: ./gradlew spotlessCheck 51 | 52 | # One Basic check, for all kind of OS - to check if we have some kind of 53 | # issue with any kind of OS 54 | basic: 55 | strategy: 56 | matrix: 57 | os: [ ubuntu, macos, windows ] 58 | runs-on: ${{ matrix.os }}-latest 59 | name: on ${{ matrix.os }} 60 | needs: validation 61 | steps: 62 | - name: Check out repo 63 | uses: actions/checkout@v4 64 | - name: Set up Java 65 | uses: actions/setup-java@v4 66 | with: 67 | java-version: '17' 68 | distribution: 'sapmachine' 69 | cache: 'gradle' 70 | - name: Setup Gradle 71 | uses: gradle/actions/setup-gradle@v4 72 | - name: Build and Test 73 | run: ./gradlew check -x spotlessCheck 74 | - uses: actions/upload-artifact@v4 75 | if: ${{ failure() }} 76 | with: 77 | name: ${{ matrix.os }}-test-result 78 | path: | 79 | build/reports 80 | build/test-results 81 | 82 | # Check supported Gradle releases 83 | # https://gradle.org/releases/ 84 | # https://docs.gradle.org/current/userguide/feature_lifecycle.html#eol_support 85 | compatibility: 86 | strategy: 87 | matrix: 88 | GRADLE_VERSION: 89 | # renovate: gradle-backwards-compatibility 90 | - "7.6.4" 91 | runs-on: ubuntu-latest 92 | needs: [basic, compliance] 93 | name: with Gradle ${{ matrix.GRADLE_VERSION }} 94 | steps: 95 | - name: Check out repo 96 | uses: actions/checkout@v4 97 | - name: Setup Java JDK 98 | uses: actions/setup-java@v4 99 | with: 100 | java-version: 17 101 | distribution: 'sapmachine' 102 | - name: Setup Gradle 103 | uses: gradle/actions/setup-gradle@v4 104 | - name: Build and Test 105 | run: ./gradlew check -x spotlessCheck 106 | env: 107 | GRADLE_VERSION: "${{ matrix.GRADLE_VERSION }}" 108 | - uses: actions/upload-artifact@v4 109 | if: ${{ failure() }} 110 | with: 111 | name: ${{ matrix.GRADLE_VERSION }}-test-result 112 | path: | 113 | */build/reports 114 | */build/test-results 115 | 116 | publish: 117 | runs-on: ubuntu-latest 118 | needs: compatibility 119 | if: startsWith(github.ref, 'refs/tags/v') 120 | steps: 121 | - name: Check out repo 122 | uses: actions/checkout@v4 123 | - name: Setup Java JDK 124 | uses: actions/setup-java@v4 125 | with: 126 | java-version: 17 127 | distribution: 'sapmachine' 128 | cache: 'gradle' 129 | - name: Setup Gradle 130 | uses: gradle/actions/setup-gradle@v4 131 | - name: Publish Plugin 132 | env: 133 | GRADLE_PUBLISH_KEY: ${{secrets.PUBLISH_KEY}} 134 | GRADLE_PUBLISH_SECRET: ${{secrets.PUBLISH_SECRET}} 135 | run: ./gradlew publishPlugins 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | /build 4 | /out 5 | /bin 6 | 7 | /NOTES.md 8 | .settings 9 | .classpath 10 | .project 11 | .idea 12 | *.iml 13 | *.orig 14 | 15 | /manualTest/hybris 16 | /manualTest/gradle.properties 17 | 18 | test-user.properties 19 | 20 | .envrc 21 | .tool-versions -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to commerce-gradle-plugin 2 | 3 | Thank you for taking the time to invest in these plugins! Let's make the life of SAP Commerce developers easier together. 4 | 5 | ## Bug Reports 6 | 7 | Please ensure that you use the [latest version][latest] of the plugin(s) and also check the [issues] if there is a 8 | similar bug already filed. 9 | 10 | If you have found a new bug, please make sure to provide as much detail as you can, or, if possible, provide a link to a 11 | minimal repository where the bug can be reproduced. 12 | 13 | As you probably know, reproducing bugs can be very hard. The faster we can reproduce the bug, the faster it will get 14 | fixed. 15 | 16 | [latest]: https://github.com/SAP/commerce-gradle-plugin/releases 17 | 18 | [issues]: https://github.com/SAP/commerce-gradle-plugin/issues 19 | 20 | ## Feature Requests 21 | 22 | You have an idea for a new feature, or a new automation that you could benefit the SAP Commerce Cloud developer 23 | community? 24 | 25 | Just open a new issue and describe your idea! 26 | 27 | ## Contributing Code Changes 28 | 29 | Here is the short version of how you can contribute changes: 30 | 31 | 1. Fork the repository (see the [github help][fork] for further guidance) 32 | 1. Implement your new feature on a new git branch 33 | 1. Provide unit, functional and/or integration tests 34 | 1. Make sure to apply the project code style using `gradlew spotlessApply` 35 | 1. Open a pull request ([github help][pr]) 36 | 37 | [fork]: https://help.github.com/articles/fork-a-repo 38 | 39 | [pr]: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request 40 | 41 | All major Java IDEs can easily import Gradle projects and therefore also this project.\ 42 | Please use the Gradle Wrapper provided in the project for your build. 43 | 44 | ### Code Style 45 | 46 | We enforce a consistent code style for the whole project using [spotless][spotless] in combination with the Eclipse JDT 47 | formatter. You can find the formatter configuration files in `gradle/spotless.xml` and `gradle/spotless.importorder`. 48 | 49 | The PR build will **fail** if you don't follow the project code style. 50 | 51 | [spotless]: https://github.com/diffplug/spotless/tree/main/plugin-gradle 52 | 53 | ### Manual Integration Testing 54 | 55 | Some features are very hard to test automatically, especially if they depend on the behavior of the SAP Commerce build. 56 | 57 | You can use a Gradle [composite build][manual] to test your local plugin version with a Commerce project. An example of 58 | such a setup is provided in the folder `manualTest`. 59 | 60 | [manual]: https://guides.gradle.org/testing-gradle-plugins/#manual-tests 61 | 62 | ### Developer Certificate of Origin (DCO) 63 | 64 | Due to legal reasons, contributors will be asked to accept a DCO before they submit the first pull request to this 65 | project, this happens in an automated fashion during the submission process. 66 | 67 | SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SAP Commerce Gradle Plugins 4.1.0 2 | 3 | [![Actions Status](https://github.com/SAP/commerce-gradle-plugin/workflows/Gradle%20CI/badge.svg)](https://github.com/SAP/commerce-gradle-plugin/actions) 4 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP/commerce-gradle-plugin)](https://api.reuse.software/info/github.com/SAP/commerce-gradle-plugin) 5 | 6 | Bootstrap, configure and build your SAP Commerce (Hybris) project using Gradle 5+. 7 | 8 | > [!TIP] 9 | > **For the user documentation, please check the [`/docs` folder](/docs)** 10 | 11 | The plugins are published to https://plugins.gradle.org/. 12 | 13 | | Published Plugin | Documentation | Description | 14 | | --------------------------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------- | 15 | | [`sap.commerce.build`][build] | [Documentation][build-doc] | Automates the developer setup and allows you to interact with the platform build using Gradle | 16 | | [`sap.commerce.build.ccv2`][ccv2] | [Documentation][ccv2-doc] | Use `manifest.json` to configure and build your "SAP Commerce Cloud in the Public Cloud" (aka CCv2) project locally | 17 | 18 | This project uses [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) 19 | 20 | ## Getting Started 21 | 22 | Here is how you get started with the plugins for your SAP Commerce project. 23 | 24 | ### Prerequisites 25 | 26 | [Install Gradle](https://gradle.org/install/), in case you haven't yet. 27 | 28 | ### Examples 29 | 30 | **Minimal Setup for CCv2 Manifest Validation** 31 | 32 | 1. ```cd /core-customize``` 33 | 2. (optional, but highly recommended) Initialize the [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) 34 | 35 | ```shell 36 | gradle wrapper 37 | ``` 38 | 3. Add a minimal Gradle build script:\ 39 | `build.gradle.kts` 40 | 41 | ```kotlin 42 | plugins { 43 | id("sap.commerce.build.ccv2") version("4.1.0") 44 | } 45 | ``` 46 | 47 | 4. `./gradlew validateManifest` 48 | 49 | **Development Setup** 50 | 51 | *For a fully automated, best-practice CCv2 project setup, refer to [sap-commerce-tools/ccv2-project-template](https://github.com/sap-commerce-tools/ccv2-project-template)* 52 | 53 | 1. ```cd /core-customize``` 54 | 2. (optional, but highly recommended) Initialize the [Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) 55 | 56 | ```shell 57 | gradle wrapper 58 | ``` 59 | 3. Add Gradle build script \ 60 | ```build.gradle.kts``` 61 | 62 | ```kotlin 63 | plugins { 64 | id("sap.commerce.build") version("4.1.0") 65 | id("sap.commerce.build.ccv2") version("4.1.0") 66 | } 67 | 68 | repositories { 69 | //Please refer to the official Gradle documentation and the plugin documentation for additional 70 | // information about dependency resolution. 71 | 72 | // Option 1: Use a (custom) Maven repository to provide SAP Commerce artifacts for development 73 | maven { 74 | url = uri("https://custom.repo.com/maven") 75 | } 76 | // Option 2: Download all required files manually and put them in `dependencies` folder 77 | // There are ways to automate the downloads from launchpad.support.sap.com, please check the FAQ. 78 | // Make sure to rename the files accordingly (-.zip) 79 | flatDir { dirs("dependencies") } 80 | 81 | mavenCentral() 82 | } 83 | ``` 84 | 4. Enjoy things like: 85 | 86 | - `./gradlew bootstrapPlatform` - download (if you use Maven) and set up the correct SAP Commerce zip, extension packs, cloud extension packs, ..., as defined in `manifest.json` 87 | - `./gradlew installManifestAddons` - install all addons as defined in `manifest.json` 88 | - `./gradlew yclean yall` - run `ant clean all` using Gradle. You can run any Ant target provided by SAP Commerce as `y`. 89 | - `./gradlew validateManifest`- validate your manifest for common errors. Now with additional checks because 90 | the full platform is available. 91 | - `./gradlew cloudTests cloudWebTests`- run the tests defined in `manifest.json` 92 | 93 | 94 | Don't forget to commit the Gradle Wrapper and your build script. 95 | 96 | ## Support 97 | 98 | Please raise an [issue] in this GitHub project, following the guidelines outlined in [CONTRIBUTING.md] 99 | 100 | ## Contributing 101 | 102 | Please refer to [CONTRIBUTING.md] 103 | 104 | [CONTRIBUTING.md]: CONTRIBUTING.md 105 | [issue]: https://github.com/SAP/commerce-gradle-plugin/issues 106 | 107 | [build]: https://plugins.gradle.org/plugin/sap.commerce.build 108 | [build-doc]: docs/Plugin-sap.commerce.build.md 109 | [ccv2]: https://plugins.gradle.org/plugin/sap.commerce.build.ccv2 110 | [ccv2-doc]: docs/Plugin-sap.commerce.build.ccv2.md 111 | [tools]:https://github.com/sap-commerce-tools 112 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "commerce-gradle-plugin" 3 | SPDX-PackageSupplier = "sap-oss-commerce-gradle-plugin@sap.com" 4 | SPDX-PackageDownloadLocation = "https://github.com/SAP/commerce-gradle-plugin" 5 | SPDX-PackageComment = """ 6 | The code in this project may include calls to APIs (“API Calls”) of 7 | SAP or third-party products or services developed outside of this project 8 | (“External Products”). 9 | “APIs” means application programming interfaces, as well as their respective 10 | specifications and implementing code that allows software to communicate with 11 | other software. 12 | API Calls to External Products are not licensed under the open source license 13 | that governs this project. The use of such API Calls and related External 14 | Products are subject to applicable additional agreements with the relevant 15 | provider of the External Products. In no event shall the open source license 16 | that governs this project grant any rights in or to any External Products,or 17 | alter, expand or supersede any terms of the applicable additional agreements. 18 | If you have a valid license agreement with SAP for the use of a particular SAP 19 | External Product, then you may make use of any API Calls included in this 20 | project’s code for that SAP External Product, subject to the terms of such 21 | license agreement. If you do not have a valid license agreement for the use of 22 | a particular SAP External Product, then you may only make use of any API Calls 23 | in this project for that SAP External Product for your internal, non-productive 24 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 25 | you any rights to use or access any SAP External Product, or provide any third 26 | parties the right to use of access any SAP External Product, through API Calls. 27 | """ 28 | 29 | [[annotations]] 30 | path = [ 31 | "gradle.properties", 32 | ".gitignore", 33 | "**/.gitignore", 34 | "README.md", 35 | "CHANGELOG.md", 36 | "CONTRIBUTING.md", 37 | ".java-version", 38 | "**/**.gradle.kts", 39 | "**.gradle.kts", 40 | "gradle/**", 41 | ".github/**", 42 | "docs/**", 43 | "manualTest/**", 44 | "renovate.json", 45 | "REUSE.toml" 46 | ] 47 | precedence = "aggregate" 48 | SPDX-FileCopyrightText = "2020 SAP SE or an SAP affiliate company and commerce-gradle-plugin contributors" 49 | SPDX-License-Identifier = "CC0-1.0" 50 | 51 | [[annotations]] 52 | path = [ 53 | "gradlew", 54 | "gradlew.bat", 55 | "**/gradle/wrapper/**" 56 | ] 57 | precedence = "aggregate" 58 | SPDX-FileCopyrightText = "Gradle Inc. 2020" 59 | SPDX-License-Identifier = "Apache-2.0" 60 | 61 | [[annotations]] 62 | path = "**/src/**" 63 | precedence = "aggregate" 64 | SPDX-FileCopyrightText = "2020 SAP SE or an SAP affiliate company and commerce-gradle-plugin contributors" 65 | SPDX-License-Identifier = "Apache-2.0" 66 | -------------------------------------------------------------------------------- /build-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /build-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("mpern.commons") 3 | id("mpern.plugin.basics") 4 | } 5 | 6 | dependencies { 7 | implementation(project(":plugin-commons")) 8 | } 9 | 10 | gradlePlugin { 11 | 12 | plugins { 13 | create("hybrisPlugin") { 14 | id = "sap.commerce.build" 15 | implementationClass = "mpern.sap.commerce.build.HybrisPlugin" 16 | 17 | displayName = "SAP Commerce Bootstrap & Build Plugin" 18 | description = """Manage the whole development lifecycle of your SAP Commerce Project with Gradle""" 19 | tags = 20 | setOf( 21 | "sap commerce", 22 | "sap hybris commerce", 23 | "hybris", 24 | "sap", 25 | "commerce", 26 | "bootstrap", 27 | "build", 28 | ) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /build-plugin/src/functionalTest/groovy/mpern/sap/commerce/build/CleanupTest.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build 2 | 3 | import static mpern.sap.commerce.build.TestUtils.ensureParents 4 | import static org.gradle.testkit.runner.TaskOutcome.SKIPPED 5 | 6 | import java.nio.file.Files 7 | import java.nio.file.Path 8 | 9 | import org.gradle.testkit.runner.GradleRunner 10 | 11 | import spock.lang.Specification 12 | import spock.lang.TempDir 13 | 14 | class CleanupTest extends Specification { 15 | 16 | @TempDir 17 | Path testProjectDir 18 | 19 | Path buildFile 20 | 21 | String providedVersion = '2020.0' 22 | 23 | GradleRunner runner 24 | 25 | def setup() { 26 | buildFile = testProjectDir.resolve('build.gradle') 27 | 28 | buildFile << """ 29 | plugins { 30 | id 'sap.commerce.build' 31 | } 32 | repositories { 33 | flatDir { 34 | dirs 'dependencies' 35 | } 36 | } 37 | """ 38 | 39 | def deps = testProjectDir.resolve("dependencies") 40 | Files.createDirectory(deps) 41 | TestUtils.generateDummyPlatform(deps, providedVersion) 42 | 43 | runner = GradleRunner.create() 44 | .withProjectDir(testProjectDir.toFile()) 45 | def gradleVersion = System.getenv("GRADLE_VERSION") 46 | if (gradleVersion) { 47 | println "Using Gradle ${gradleVersion}" 48 | runner.withGradleVersion(gradleVersion) 49 | } 50 | runner.withPluginClasspath() 51 | } 52 | 53 | 54 | def "cleanPlatformIfVersionChanged skipped when same platform version"() { 55 | def version = '1811.0' 56 | 57 | given: "correct version exists" 58 | buildFile << """ 59 | hybris { 60 | version = '$version' 61 | } 62 | """ 63 | ensureParents(testProjectDir.resolve("hybris/bin/platform/build.number")) << """ 64 | version=$version 65 | """ 66 | 67 | when: "running cleanPlatformIfVersionChanged task" 68 | def result = runner 69 | .withArguments("--stacktrace", "-i", "cleanPlatformIfVersionChanged") 70 | .build() 71 | 72 | then: "the task is skipped" 73 | result.task(":cleanPlatformIfVersionChanged").outcome == SKIPPED 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /build-plugin/src/integrationTest/groovy/mpern/sap/commerce/build/util/PlatformResolverSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util 2 | 3 | import java.nio.file.Files 4 | import java.nio.file.Path 5 | 6 | import spock.lang.IgnoreIf 7 | import spock.lang.Specification 8 | 9 | class PlatformResolverSpec extends Specification { 10 | 11 | def platformHome = Path.of("../manualTest/hybris/bin/platform"); 12 | PlatformResolver resolver 13 | 14 | def setup() { 15 | resolver = new PlatformResolver(platformHome) 16 | } 17 | 18 | @IgnoreIf({ !Files.exists(instance.platformHome) }) 19 | def "load extensions correctly"() { 20 | when: 21 | def extensions = resolver.getConfiguredExtensions().collect() 22 | println(extensions.size()) 23 | println(extensions.sort{it.name}.collect{"${it.name} - ${it.relativeLocation}"}.join('\n')) 24 | 25 | then: 26 | extensions.size() > 0 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/HybrisPluginExtension.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.gradle.api.Action; 6 | import org.gradle.api.Project; 7 | import org.gradle.api.model.ObjectFactory; 8 | import org.gradle.api.provider.ListProperty; 9 | import org.gradle.api.provider.MapProperty; 10 | import org.gradle.api.provider.Property; 11 | import org.gradle.api.tasks.Nested; 12 | 13 | import mpern.sap.commerce.build.util.HybrisPlatform; 14 | import mpern.sap.commerce.build.util.SparseBootstrap; 15 | 16 | public abstract class HybrisPluginExtension { 17 | 18 | private final HybrisPlatform platform; 19 | 20 | @Inject 21 | public HybrisPluginExtension(Project project, ObjectFactory objectFactory) { 22 | getVersion().convention("2211"); 23 | 24 | getIntExtPackVersion().convention(""); 25 | 26 | platform = objectFactory.newInstance(HybrisPlatform.class, project); 27 | } 28 | 29 | public abstract Property getVersion(); 30 | 31 | public abstract Property getIntExtPackVersion(); 32 | 33 | public HybrisPlatform getPlatform() { 34 | return platform; 35 | } 36 | 37 | public abstract Property getCleanGlob(); 38 | 39 | public abstract ListProperty getBootstrapInclude(); 40 | 41 | public abstract ListProperty getBootstrapExclude(); 42 | 43 | public abstract ListProperty getAntTaskDependencies(); 44 | 45 | public abstract MapProperty getPreviewToPatchLevel(); 46 | 47 | @Nested 48 | public abstract SparseBootstrap getSparseBootstrap(); 49 | 50 | public void sparseBootstrap(Action action) { 51 | action.execute(getSparseBootstrap()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/extensioninfo/ExtensionInfoException.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.extensioninfo; 2 | 3 | public class ExtensionInfoException extends RuntimeException { 4 | 5 | public ExtensionInfoException(String message) { 6 | super(message); 7 | } 8 | 9 | public ExtensionInfoException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/extensioninfo/ExtensionXmlUtil.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.extensioninfo; 2 | 3 | import static org.w3c.dom.Node.ELEMENT_NODE; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.Path; 8 | import java.util.ArrayList; 9 | import java.util.LinkedHashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | import javax.annotation.Nonnull; 14 | import javax.xml.parsers.DocumentBuilder; 15 | import javax.xml.parsers.DocumentBuilderFactory; 16 | import javax.xml.parsers.ParserConfigurationException; 17 | 18 | import org.w3c.dom.Attr; 19 | import org.w3c.dom.Document; 20 | import org.w3c.dom.Element; 21 | import org.w3c.dom.Node; 22 | import org.w3c.dom.NodeList; 23 | import org.xml.sax.SAXException; 24 | 25 | import mpern.sap.commerce.build.util.Extension; 26 | import mpern.sap.commerce.build.util.ExtensionType; 27 | 28 | /** 29 | * Utility functions for loading extension information from Hybris specific XML 30 | * files. 31 | */ 32 | public final class ExtensionXmlUtil { 33 | 34 | /** 35 | * Loads the names of all extensions declared in localextensions.xml. Only 36 | * extensions declared with "extension" elements are supported. Autoloaded paths 37 | * are not supported. 38 | * 39 | * @param file the localextensions.xml to be loaded 40 | * @return the declared extensions names 41 | */ 42 | public static Set loadExtensionNamesFromLocalExtensionsXML(File file) { 43 | try { 44 | DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); 45 | DocumentBuilder builder = domFactory.newDocumentBuilder(); 46 | Document doc = builder.parse(file); 47 | Set extensionNames = new LinkedHashSet<>(); 48 | NodeList extensions = doc.getDocumentElement().getElementsByTagName("extension"); 49 | for (int i = 0; i < extensions.getLength(); i++) { 50 | Node extension = extensions.item(i); 51 | Node name = extension.getAttributes().getNamedItem("name"); 52 | if (name != null && name.getNodeValue() != null && !name.getNodeValue().isEmpty()) { 53 | extensionNames.add(name.getNodeValue()); 54 | } 55 | } 56 | return extensionNames; 57 | } catch (ParserConfigurationException | SAXException | IOException e) { 58 | throw new ExtensionInfoException("Error parsing localextensions.xml file " + file, e); 59 | } 60 | } 61 | 62 | /** 63 | * Extracts an Extension information object from a given extensioninfo.xml file. 64 | * 65 | * @param extensioninfoXmlFile the location of extensioninfo.xml file 66 | * @param rootLocation the root location of the project 67 | * @param extensionType type of extension being extracted 68 | * @return the information object 69 | */ 70 | @Nonnull 71 | public static Extension loadExtensionFromExtensioninfoXml(File extensioninfoXmlFile, String rootLocation, 72 | ExtensionType extensionType) { 73 | try { 74 | DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); 75 | DocumentBuilder builder = domFactory.newDocumentBuilder(); 76 | Document doc = builder.parse(extensioninfoXmlFile); 77 | Node extensionNode = doc.getDocumentElement().getElementsByTagName("extension").item(0); 78 | 79 | Path extensionPath = extensioninfoXmlFile.toPath().getParent(); 80 | return new Extension(extractExtensionNameFromNode(extensionNode, extensioninfoXmlFile), 81 | getRelativeLocation(extensionPath, rootLocation), extensionType, 82 | extractRequiredExtensionsNamesFromNode(extensionNode)); 83 | 84 | } catch (ParserConfigurationException | SAXException | IOException e) { 85 | throw new ExtensionInfoException("Error parsing extensioninfo file " + extensioninfoXmlFile, e); 86 | } 87 | } 88 | 89 | private static String extractExtensionNameFromNode(Node extensionNode, File extensioninfoXmlFile) 90 | throws IOException { 91 | Node nameNode = extensionNode.getAttributes().getNamedItem("name"); 92 | if (nameNode != null && nameNode.getNodeValue() != null && !nameNode.getNodeValue().isEmpty()) { 93 | return nameNode.getNodeValue(); 94 | } else { 95 | throw new ExtensionInfoException("Found extension without name in file " + extensioninfoXmlFile); 96 | } 97 | } 98 | 99 | private static List extractRequiredExtensionsNamesFromNode(Node extensionNode) throws IOException { 100 | NodeList extensionNodeChildNodes = extensionNode.getChildNodes(); 101 | List requiresExtensionElements = new ArrayList<>(); 102 | for (int i = 0; i < extensionNodeChildNodes.getLength(); i++) { 103 | Node node = extensionNodeChildNodes.item(i); 104 | if (node.getNodeType() == ELEMENT_NODE && node.getNodeName() == "requires-extension") { 105 | requiresExtensionElements.add((Element) node); 106 | } 107 | } 108 | 109 | List requiredExtensionsNames = new ArrayList<>(); 110 | for (Element requiresExtensionElement : requiresExtensionElements) { 111 | Attr nameAttr = requiresExtensionElement.getAttributeNode("name"); 112 | requiredExtensionsNames.add(nameAttr.getValue()); 113 | } 114 | 115 | return requiredExtensionsNames; 116 | } 117 | 118 | private static Path getRelativeLocation(Path fullLocation, String rootLocation) { 119 | // normalize to a Unix path 120 | String fullUnixLocation = fullLocation.toString().replace("\\", "/"); 121 | // get everything after last rootLocation 122 | int rootLocationPos = fullUnixLocation.lastIndexOf(rootLocation); 123 | if (rootLocationPos == -1) { 124 | throw new ExtensionInfoException( 125 | "Full location [" + fullUnixLocation + "] does not contain [" + rootLocation + "]"); 126 | } 127 | return Path.of(fullUnixLocation.substring(rootLocationPos + rootLocation.length())); 128 | } 129 | 130 | private ExtensionXmlUtil() { 131 | // no instances for util class 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/rules/HybrisAntRule.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.rules; 2 | 3 | import static mpern.sap.commerce.build.HybrisPlugin.HYBRIS_EXTENSION; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.Callable; 7 | 8 | import org.gradle.api.Project; 9 | import org.gradle.api.Rule; 10 | 11 | import mpern.sap.commerce.build.HybrisPluginExtension; 12 | import mpern.sap.commerce.build.tasks.HybrisAntTask; 13 | 14 | public class HybrisAntRule implements Rule { 15 | 16 | public static final String PREFIX = "y"; 17 | private final Project project; 18 | 19 | public HybrisAntRule(Project project) { 20 | this.project = project; 21 | } 22 | 23 | @Override 24 | public String getDescription() { 25 | return "Pattern: y: Run hybris ant "; 26 | } 27 | 28 | @Override 29 | public void apply(String taskName) { 30 | if (taskName.startsWith(PREFIX)) { 31 | String antTarget = taskName.substring(PREFIX.length()); 32 | project.getTasks().create(taskName, HybrisAntTask.class, t -> { 33 | t.args(antTarget); 34 | t.dependsOn((Callable>) () -> { 35 | HybrisPluginExtension extension = (HybrisPluginExtension) project.getExtensions() 36 | .getByName(HYBRIS_EXTENSION); 37 | return extension.getAntTaskDependencies().getOrNull(); 38 | }); 39 | }); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/tasks/GlobClean.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.tasks; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.FileSystems; 5 | import java.nio.file.FileVisitResult; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.PathMatcher; 9 | import java.nio.file.attribute.BasicFileAttributes; 10 | 11 | import org.gradle.api.DefaultTask; 12 | import org.gradle.api.provider.Property; 13 | import org.gradle.api.tasks.*; 14 | 15 | public abstract class GlobClean extends DefaultTask { 16 | 17 | @TaskAction 18 | public void cleanup() { 19 | PathMatcher matcher = FileSystems.getDefault().getPathMatcher(getGlob().getOrNull()); 20 | Path path = Path.of(getBaseFolder().get()); 21 | if (!Files.exists(path)) { 22 | return; 23 | } 24 | try { 25 | Files.walkFileTree(path, new java.nio.file.SimpleFileVisitor<>() { 26 | @Override 27 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 28 | if (matcher.matches(file)) { 29 | Files.delete(file); 30 | } 31 | return FileVisitResult.CONTINUE; 32 | } 33 | 34 | @Override 35 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 36 | if (matcher.matches(dir)) { 37 | Files.delete(dir); 38 | } 39 | return FileVisitResult.CONTINUE; 40 | } 41 | }); 42 | } catch (IOException e) { 43 | throw new TaskExecutionException(this, e); 44 | } 45 | } 46 | 47 | @Input 48 | public abstract Property getGlob(); 49 | 50 | @Input 51 | public abstract Property getBaseFolder(); 52 | } 53 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/tasks/HybrisAntTask.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.tasks; 2 | 3 | import java.util.*; 4 | 5 | import org.gradle.api.file.ConfigurableFileTree; 6 | import org.gradle.api.provider.MapProperty; 7 | import org.gradle.api.provider.Property; 8 | import org.gradle.api.tasks.Input; 9 | import org.gradle.api.tasks.Internal; 10 | import org.gradle.api.tasks.JavaExec; 11 | import org.gradle.api.tasks.options.Option; 12 | 13 | import mpern.sap.commerce.build.HybrisPlugin; 14 | import mpern.sap.commerce.build.HybrisPluginExtension; 15 | import mpern.sap.commerce.build.util.HybrisPlatform; 16 | import mpern.sap.commerce.build.util.Version; 17 | 18 | public abstract class HybrisAntTask extends JavaExec { 19 | private static final Version V_2205 = Version.parseVersion("2205.0"); 20 | 21 | private List fromCommandLine = Collections.emptyList(); 22 | 23 | public HybrisAntTask() { 24 | super(); 25 | getAntProperties().put("maven.update.dbdrivers", "false"); 26 | getMainClass().set("org.apache.tools.ant.launch.Launcher"); 27 | } 28 | 29 | @Override 30 | public void exec() { 31 | if (getNoOp().getOrElse(Boolean.FALSE)) { 32 | return; 33 | } 34 | 35 | ConfigurableFileTree files = buildPlatformAntClasspath(); 36 | setClasspath(files); 37 | 38 | HybrisPlatform platform = ((HybrisPluginExtension) getProject().getExtensions() 39 | .getByName(HybrisPlugin.HYBRIS_EXTENSION)).getPlatform(); 40 | 41 | systemProperty("ant.home", platform.getAntHome().get().getAsFile()); 42 | systemProperty("file.encoding", "UTF-8"); 43 | 44 | Map props = new LinkedHashMap<>(getAntProperties().get()); 45 | 46 | getFallbackAntProperties().get().forEach(props::putIfAbsent); 47 | 48 | // @formatter:off 49 | fromCommandLine.stream() 50 | .map(s -> s.split("=", 2)) 51 | .peek(split -> { 52 | if (split.length < 2) { 53 | getLogger().warn("Malformed antProperty; must be in the format 'key=value' (actual: {})", split[0]); 54 | } 55 | }) 56 | .filter(split -> split.length > 1) 57 | .forEach(split -> props.put(split[0], split[1])); 58 | // @formatter:on 59 | 60 | props.forEach((k, v) -> args("-D" + k + "=" + v)); 61 | 62 | Version current = Version.parseVersion(platform.getVersion().get()); 63 | 64 | // ref. hybris/bin/platform/setantenv.sh in 2205 65 | if (current.compareTo(V_2205) >= 0) { 66 | systemProperty("polyglot.js.nashorn-compat", "true"); 67 | systemProperty("polyglot.engine.WarnInterpreterOnly", "false"); 68 | 69 | jvmArgs("--add-exports", "java.xml/com.sun.org.apache.xpath.internal=ALL-UNNAMED"); 70 | jvmArgs("--add-exports", "java.xml/com.sun.org.apache.xpath.internal.objects=ALL-UNNAMED"); 71 | } 72 | 73 | workingDir(platform.getPlatformHome()); 74 | 75 | super.exec(); 76 | } 77 | 78 | private ConfigurableFileTree buildPlatformAntClasspath() { 79 | ConfigurableFileTree files = getProject().fileTree("hybris/bin/platform"); 80 | files.include("apache-ant*/lib/ant-launcher.jar"); 81 | return files; 82 | } 83 | 84 | /** 85 | * Add a new runtime property to configure the ant target 86 | * 87 | * @param key key of the property 88 | * @param value value of the property 89 | */ 90 | public void antProperty(String key, String value) { 91 | getAntProperties().put(key, value); 92 | } 93 | 94 | /** 95 | * Add a new runtime property to configure the ant target 96 | * 97 | * @param key key of the property 98 | * @param value value of the property 99 | */ 100 | public void fallbackAntProperty(String key, String value) { 101 | getFallbackAntProperties().put(key, value); 102 | } 103 | 104 | @Option(option = "antProperty", description = "Additional properties for Commerce ant targets") 105 | public void setFromCommandLine(List values) { 106 | this.fromCommandLine = values; 107 | } 108 | 109 | @Internal 110 | public abstract Property getNoOp(); 111 | 112 | @Input 113 | public abstract MapProperty getAntProperties(); 114 | 115 | @Input 116 | public abstract MapProperty getFallbackAntProperties(); 117 | } 118 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/util/Extension.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util; 2 | 3 | import java.nio.file.Path; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | public class Extension { 10 | 11 | public final String name; 12 | 13 | /** Relative location of the extension compared to the hybris/bin folder */ 14 | public final Path relativeLocation; 15 | 16 | public final ExtensionType extensionType; 17 | 18 | public final List requiredExtensions; 19 | 20 | // 21 | public Extension(String name, Path relativeLocation) { 22 | Objects.requireNonNull(name); 23 | Objects.requireNonNull(relativeLocation); 24 | this.name = name; 25 | this.relativeLocation = relativeLocation; 26 | this.requiredExtensions = Collections.emptyList(); 27 | this.extensionType = ExtensionType.UNKNOWN; 28 | } 29 | 30 | public Extension(String name, Path relativeLocation, ExtensionType extensionType, List requiredExtensions) { 31 | Objects.requireNonNull(name); 32 | Objects.requireNonNull(extensionType); 33 | Objects.requireNonNull(requiredExtensions); 34 | 35 | this.name = name; 36 | this.relativeLocation = relativeLocation; 37 | this.extensionType = extensionType; 38 | this.requiredExtensions = Collections.unmodifiableList(requiredExtensions); 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) 44 | return true; 45 | if (o == null || getClass() != o.getClass()) 46 | return false; 47 | Extension extension = (Extension) o; 48 | return name.equals(extension.name); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return Objects.hash(name); 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return new StringJoiner(", ", Extension.class.getSimpleName() + "[", "]").add("name='" + name + "'") 59 | .add("relativeLocation=" + relativeLocation).toString(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/util/ExtensionType.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util; 2 | 3 | /** 4 | * Type of extension. 5 | */ 6 | public enum ExtensionType { 7 | 8 | /** 9 | * Custom extension. 10 | */ 11 | CUSTOM, 12 | 13 | /** 14 | * Extension from the SAP distribution modules. 15 | */ 16 | SAP_MODULE, 17 | 18 | /** 19 | * SAP platform extension as a whole. 20 | */ 21 | SAP_PLATFORM, 22 | 23 | /** 24 | * Used for all extensions installed already in the system and discovered from 25 | * the platform system config. 26 | */ 27 | RUNTIME_INSTALLED, 28 | 29 | UNKNOWN 30 | } 31 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/util/HybrisPlatform.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | import java.nio.file.*; 7 | import java.nio.file.attribute.BasicFileAttributes; 8 | import java.util.Optional; 9 | import java.util.Properties; 10 | 11 | import org.gradle.api.Project; 12 | import org.gradle.api.file.Directory; 13 | import org.gradle.api.logging.Logger; 14 | import org.gradle.api.logging.Logging; 15 | import org.gradle.api.provider.Provider; 16 | 17 | public class HybrisPlatform { 18 | private static final Logger LOG = Logging.getLogger(HybrisPlatform.class); 19 | 20 | private final Provider platformDir; 21 | 22 | private final Provider platformVersion; 23 | 24 | private final Provider antHome; 25 | 26 | @javax.inject.Inject 27 | public HybrisPlatform(Project project) { 28 | platformDir = project.provider(() -> project.getLayout().getProjectDirectory().dir("hybris/bin/platform")); 29 | 30 | platformVersion = project.provider(this::readVersion); 31 | 32 | antHome = project.provider(() -> project.getLayout().getProjectDirectory().dir(getRelativeAntHomepath())); 33 | } 34 | 35 | public Provider getPlatformHome() { 36 | return platformDir; 37 | } 38 | 39 | public Provider getVersion() { 40 | return platformVersion; 41 | } 42 | 43 | public Provider getAntHome() { 44 | return antHome; 45 | } 46 | 47 | private String readVersion() { 48 | Directory orNull = platformDir.getOrNull(); 49 | 50 | if (orNull == null) { 51 | return "NONE"; 52 | } 53 | Path buildFile = orNull.file("build.number").getAsFile().toPath(); 54 | Properties properties = new Properties(); 55 | 56 | try (BufferedReader br = new BufferedReader(new FileReader(buildFile.toFile()))) { 57 | properties.load(br); 58 | } catch (IOException e) { 59 | LOG.debug("could not open build.number", e); 60 | } 61 | String bootstrappedVersion = properties.getProperty("version", "NONE"); 62 | return bootstrappedVersion; 63 | } 64 | 65 | private String getRelativeAntHomepath() { 66 | try { 67 | AntPathVisitor visitor = new AntPathVisitor(); 68 | Path platformPath = platformDir.get().getAsFile().toPath(); 69 | Files.walkFileTree(platformPath, visitor); 70 | Path antHome = visitor.getAntHome().orElseThrow(() -> new IllegalStateException( 71 | "could not find hybris platform ant in hybris/bin/platform/apache-ant*")); 72 | return antHome.toString(); 73 | } catch (IOException e) { 74 | throw new IllegalStateException("could not find hybris platform ant", e); 75 | } 76 | } 77 | 78 | private static class AntPathVisitor extends SimpleFileVisitor { 79 | private Path foundPath; 80 | private final PathMatcher antPathMatcher; 81 | 82 | public AntPathVisitor() { 83 | this.foundPath = null; 84 | antPathMatcher = FileSystems.getDefault().getPathMatcher("glob:**/apache-ant*"); 85 | } 86 | 87 | @Override 88 | public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 89 | if (antPathMatcher.matches(dir)) { 90 | foundPath = dir; 91 | return FileVisitResult.TERMINATE; 92 | } 93 | return FileVisitResult.CONTINUE; 94 | } 95 | 96 | public Optional getAntHome() { 97 | return Optional.ofNullable(foundPath); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/util/SparseBootstrap.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util; 2 | 3 | import org.gradle.api.provider.Property; 4 | import org.gradle.api.provider.SetProperty; 5 | 6 | /** 7 | * Extension configuration element for HybrisPluginExtension. 8 | */ 9 | public abstract class SparseBootstrap { 10 | 11 | public abstract Property getEnabled(); 12 | 13 | public abstract SetProperty getAlwaysIncluded(); 14 | } 15 | -------------------------------------------------------------------------------- /build-plugin/src/main/java/mpern/sap/commerce/build/util/Stopwatch.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util; 2 | 3 | /** 4 | * Utility stopwatch. 5 | */ 6 | public class Stopwatch { 7 | 8 | private static final long NANOS_IN_MILLISECOND = 1000000L; 9 | 10 | private final long start; 11 | 12 | private long duration = -1; 13 | 14 | /** 15 | * Construct and start the watch. 16 | */ 17 | public Stopwatch() { 18 | start = System.nanoTime(); 19 | } 20 | 21 | /** 22 | * Stop the watch and return the duration in milliseconds. 23 | * 24 | * @return duration in milliseconds 25 | */ 26 | public long stop() { 27 | duration = System.nanoTime() - start; 28 | return duration / NANOS_IN_MILLISECOND; 29 | } 30 | 31 | /** 32 | * Gets the duration in milliseconds. 33 | * 34 | * @return duration in ms, -1 if the watch was not stopped 35 | */ 36 | public long getDurationInMs() { 37 | return duration / NANOS_IN_MILLISECOND; 38 | } 39 | 40 | /** 41 | * Gets the duration in nanoseconds. 42 | * 43 | * @return duration in nanos, -1 if the watch was not stopped 44 | */ 45 | public long getDuration() { 46 | return duration; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /build-plugin/src/test/groovy/mpern/sap/commerce/build/extensioninfo/ExtensionXmlUtilTest.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.extensioninfo 2 | 3 | import java.nio.file.Path 4 | 5 | import spock.lang.Specification 6 | 7 | import mpern.sap.commerce.build.util.ExtensionType 8 | 9 | class ExtensionXmlUtilTest extends Specification { 10 | 11 | static final ROOT_LOCATION = "/testExtensions/" 12 | 13 | def "extensioninfo xml file parsed correctly"() { 14 | given: 15 | def sourceFile = Path.of( 16 | ExtensionXmlUtil.class.getResource("${ROOT_LOCATION}module/extension/extensioninfo.xml").toURI()).toFile() 17 | 18 | when: 19 | def extension = ExtensionXmlUtil.loadExtensionFromExtensioninfoXml(sourceFile, ROOT_LOCATION, ExtensionType.CUSTOM) 20 | 21 | then: 22 | with(extension) { 23 | name == "configurablebundlefacades" 24 | extensionType == ExtensionType.CUSTOM 25 | 26 | relativeLocation == Path.of("module/extension") 27 | requiredExtensions.size() == 2 28 | requiredExtensions.containsAll("configurablebundleservices", "commercefacades") 29 | } 30 | } 31 | 32 | def "extension without name generates error"() { 33 | given: 34 | def sourceFile = Path.of( 35 | ExtensionXmlUtil.class.getResource("${ROOT_LOCATION}module/brokenextension/extensioninfo.xml").toURI()).toFile() 36 | 37 | when: 38 | def extension = ExtensionXmlUtil.loadExtensionFromExtensioninfoXml(sourceFile, ROOT_LOCATION, ExtensionType.CUSTOM) 39 | 40 | then: 41 | ExtensionInfoException exception = thrown() 42 | exception.message.startsWith("Found extension without name in file ") 43 | } 44 | 45 | def "extension location not containing root location generates error"() { 46 | given: 47 | def sourceFile = Path.of( 48 | ExtensionXmlUtil.class.getResource("${ROOT_LOCATION}module/extension/extensioninfo.xml").toURI()).toFile() 49 | 50 | when: 51 | def extension = ExtensionXmlUtil.loadExtensionFromExtensioninfoXml(sourceFile, "anotherRoot", ExtensionType.CUSTOM) 52 | 53 | then: 54 | ExtensionInfoException exception = thrown() 55 | exception.message.startsWith("Full location [") 56 | } 57 | 58 | def "localextensions xml file parsed correctly"() { 59 | given: 60 | def sourceFile = Path.of( 61 | ExtensionXmlUtil.class.getResource("/test-localextensions.xml").toURI()).toFile() 62 | 63 | when: 64 | def extensions = ExtensionXmlUtil.loadExtensionNamesFromLocalExtensionsXML(sourceFile) 65 | 66 | then: 67 | extensions.size() == 4 68 | extensions.containsAll("payment", "backoffice", "myextensionone", "myextensiontwo") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /build-plugin/src/test/groovy/mpern/sap/commerce/build/util/VersionTest.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build.util 2 | 3 | import spock.lang.Specification 4 | 5 | class VersionTest extends Specification { 6 | 7 | static Map PREVIEW_TO_PLATFORM_PATCH = [ 8 | "2211.FP0": 4, 9 | "2211.FP1": 8 10 | ] 11 | 12 | def "version parsed correctly"() { 13 | when: 14 | def v = Version.parseVersion('1905.2') 15 | 16 | then: 17 | v.major == 19 18 | v.minor == 05 19 | v.release == 0 20 | v.patch == 2 21 | v.toString() == "1905.2" 22 | } 23 | 24 | def "version without patch parsed correctly"() { 25 | when: 26 | def v = Version.parseVersion('1905') 27 | 28 | then: 29 | v.major == 19 30 | v.minor == 05 31 | v.release == 0 32 | v.patch == Version.UNDEFINED_PART 33 | v.toString() == "1905" 34 | } 35 | 36 | def "preview version can be parsed and is mapped to correct patch"() { 37 | when: 38 | def v = Version.parseVersion("2211.FP1", PREVIEW_TO_PLATFORM_PATCH) 39 | 40 | then: 41 | v.preview 42 | v.major == 22 43 | v.minor == 11 44 | v.patch == 8 45 | v.toString() == "2211.FP1 (PREVIEW) [2211.8]" 46 | } 47 | 48 | def "preview version without mapping is parsed correctly without patch"() { 49 | when: 50 | def v = Version.parseVersion("1905.FP1", PREVIEW_TO_PLATFORM_PATCH) 51 | 52 | then: 53 | v.preview 54 | v.major == 19 55 | v.minor == 05 56 | v.patch == Version.UNDEFINED_PART 57 | v.toString() == "1905.FP1 (PREVIEW)" 58 | } 59 | 60 | def "preview version can be parsed without patch when not needed"() { 61 | when: 62 | def v = Version.parseVersion("2211.FP1") 63 | 64 | then: 65 | v.preview 66 | v.major == 22 67 | v.minor == 11 68 | v.patch == Version.UNDEFINED_PART 69 | v.toString() == "2211.FP1 (PREVIEW)" 70 | } 71 | 72 | def "jdk21 preview version can be parsed without patch when not needed"() { 73 | when: 74 | def v = Version.parseVersion("2211-jdk21.FP10") 75 | 76 | then: 77 | v.preview 78 | v.major == 22 79 | v.minor == 11 80 | v.patch == Version.UNDEFINED_PART 81 | v.toString() == "2211-jdk21.FP10 (PREVIEW)" 82 | } 83 | 84 | def "jdk21 version with patch"() { 85 | when: 86 | def v = Version.parseVersion("2211-jdk21.1") 87 | 88 | then: 89 | !v.preview 90 | v.major == 22 91 | v.minor == 11 92 | v.patch == 1 93 | v.toString() == "2211-jdk21.1" 94 | } 95 | 96 | def "jdk21 version can have test build"() { 97 | when: 98 | def v = Version.parseVersion("2211-jdk21.TEST.20250505") 99 | 100 | then: 101 | !v.preview 102 | v.major == 22 103 | v.minor == 11 104 | v.patch == 20250505 105 | v.toString() == "2211-jdk21.TEST.20250505" 106 | } 107 | 108 | def "jdk21 version can have test build without date timestamp"() { 109 | when: 110 | def v = Version.parseVersion("2211-jdk21.TEST") 111 | 112 | then: 113 | !v.preview 114 | v.major == 22 115 | v.minor == 11 116 | v.patch == Version.UNDEFINED_PART 117 | v.toString() == "2211-jdk21.TEST" 118 | } 119 | 120 | def "1808 version should be used with Java 8"() { 121 | when: 122 | def v = Version.parseVersion("1808") 123 | 124 | then: 125 | v.jdk == 8 126 | } 127 | 128 | def "2105 version should be used with Java 11"() { 129 | when: 130 | def v = Version.parseVersion("2105") 131 | 132 | then: 133 | v.jdk == 11 134 | } 135 | 136 | def "2211 version should be used with Java 17"() { 137 | when: 138 | def v = Version.parseVersion("2211") 139 | 140 | then: 141 | v.jdk == 17 142 | } 143 | 144 | def "2211-jdk21 version should be used with Java 21"() { 145 | when: 146 | def v = Version.parseVersion("2211-jdk21") 147 | 148 | then: 149 | v.jdk == 21 150 | } 151 | 152 | def "2211 and 2211-jdk21 versions are not equal"() { 153 | when: 154 | def x = Version.parseVersion("2211") 155 | def y = Version.parseVersion("2211-jdk21") 156 | 157 | then: 158 | x != y 159 | y > x 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /build-plugin/src/test/resources/test-localextensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /build-plugin/src/test/resources/testExtensions/module/brokenextension/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /build-plugin/src/test/resources/testExtensions/module/extension/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import pl.allegro.tech.build.axion.release.domain.hooks.HookContext 2 | 3 | plugins { 4 | id("pl.allegro.tech.build.axion-release") 5 | } 6 | 7 | scmVersion { 8 | localOnly = true 9 | ignoreUncommittedChanges = true 10 | 11 | checks { 12 | aheadOfRemote = false 13 | } 14 | hooks { 15 | pre( 16 | "fileUpdate", 17 | mapOf( 18 | "encoding" to "utf-8", 19 | "file" to file("README.md"), 20 | "pattern" to KotlinClosure2({ pv: String, _: HookContext -> "$pv" }), 21 | "replacement" to KotlinClosure2({ cv: String, _: HookContext -> "$cv" }), 22 | ), 23 | ) 24 | pre("commit") 25 | } 26 | } 27 | 28 | project.version = scmVersion.version 29 | -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /.gradle -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | gradlePluginPortal() 8 | } 9 | 10 | dependencies { 11 | implementation("com.diffplug.gradle.spotless:com.diffplug.gradle.spotless.gradle.plugin:7.0.3") 12 | implementation("com.gradle.plugin-publish:com.gradle.plugin-publish.gradle.plugin:1.3.1") 13 | implementation("pl.allegro.tech.build.axion-release:pl.allegro.tech.build.axion-release.gradle.plugin:1.18.18") 14 | implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:8.3.6") 15 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/mpern.commons.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | java 3 | id("com.diffplug.spotless") 4 | } 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | group = "sap.commerce" 11 | project.version = rootProject.version 12 | 13 | java { 14 | toolchain { 15 | languageVersion = JavaLanguageVersion.of(17) 16 | } 17 | } 18 | 19 | tasks.withType().configureEach { 20 | options.compilerArgs = listOf("-Xlint:deprecation", "-Xlint:unchecked") 21 | } 22 | 23 | spotless { 24 | format("misc") { 25 | // define the files to apply `misc` to 26 | target("*.md", ".gitignore") 27 | 28 | // define the steps to apply to those files 29 | trimTrailingWhitespace() 30 | leadingTabsToSpaces(4) 31 | endWithNewline() 32 | } 33 | java { 34 | target("src/*/java/**/*.java") 35 | removeUnusedImports() 36 | importOrderFile("../gradle/spotless.importorder") 37 | eclipse().configFile("../gradle/spotless.xml") 38 | } 39 | groovy { 40 | target("src/*/groovy/**/*.groovy") 41 | importOrderFile("../gradle/spotless.importorder") 42 | greclipse().configFile("../gradle/greclipse.properties") 43 | } 44 | kotlinGradle { 45 | ktlint() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/mpern.plugin.basics.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar 2 | 3 | plugins { 4 | id("groovy") 5 | `jvm-test-suite` 6 | id("com.gradle.plugin-publish") 7 | id("com.gradleup.shadow") 8 | } 9 | 10 | dependencies { 11 | implementation(project(":plugin-commons")) 12 | } 13 | 14 | testing { 15 | suites { 16 | configureEach { 17 | if (this is JvmTestSuite) { 18 | useJUnitJupiter() 19 | dependencies { 20 | implementation(platform("org.spockframework:spock-bom:2.3-groovy-3.0")) 21 | implementation("org.spockframework:spock-core") 22 | 23 | implementation(project()) 24 | implementation(project(":test-utils")) 25 | 26 | implementation(gradleTestKit()) 27 | } 28 | targets { 29 | all { 30 | testTask.configure { 31 | testLogging { 32 | events("passed", "failed", "skipped", "standardError") 33 | showStandardStreams = true 34 | showStackTraces = true 35 | showExceptions = true 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | 43 | val integrationTest by registering(JvmTestSuite::class) { 44 | targets { 45 | all { 46 | testTask.configure { 47 | shouldRunAfter("test") 48 | } 49 | } 50 | } 51 | } 52 | 53 | register("functionalTest", JvmTestSuite::class) { 54 | targets { 55 | all { 56 | testTask.configure { 57 | shouldRunAfter("test", integrationTest) 58 | } 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | tasks.named("check") { 66 | dependsOn(testing.suites.named("integrationTest"), testing.suites.named("functionalTest")) 67 | } 68 | 69 | gradlePlugin { 70 | testSourceSets(sourceSets.get("functionalTest")) 71 | 72 | website = "https://github.com/SAP/commerce-gradle-plugin" 73 | vcsUrl = "https://github.com/SAP/commerce-gradle-plugin" 74 | } 75 | 76 | tasks { 77 | named("shadowJar") { 78 | archiveClassifier = "" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /cloud-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /cloud-plugin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("mpern.commons") 3 | id("mpern.plugin.basics") 4 | } 5 | 6 | dependencies { 7 | implementation(project(":build-plugin")) 8 | integrationTestImplementation(project(":build-plugin")) 9 | } 10 | 11 | gradlePlugin { 12 | plugins { 13 | create("ccv2BuildSupport") { 14 | id = "sap.commerce.build.ccv2" 15 | implementationClass = "mpern.sap.commerce.ccv2.CloudV2Plugin" 16 | 17 | displayName = "SAP Commerce Cloud in the Public Cloud Build Support Plugin" 18 | description = """Use the CCv2 manifest.json to configure and build your local development environment""" 19 | tags = 20 | setOf( 21 | "sap commerce", 22 | "sap hybris commerce", 23 | "hybris", 24 | "sap", 25 | "commerce", 26 | "ccv2", 27 | "public cloud", 28 | "manifest", 29 | ) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/AddonValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.impl.AddonValidator 8 | 9 | class AddonValidatorSpec extends Specification { 10 | 11 | def "addon validation checks available extensions"() { 12 | given: 13 | def rawManifest = new JsonSlurper().parseText('''\ 14 | { 15 | "commerceSuiteVersion": "2005", 16 | "storefrontAddons": [ 17 | { 18 | "addon": "spartacussampledataaddon,commerceorgsamplesaddon", 19 | "storefront": "yacceleratorstorefront,yb2bacceleratorstorefront", 20 | "template": "yacceleratorstorefront" 21 | }, 22 | { 23 | "addons": ["smarteditaddon", "textfieldconfiguratortemplateaddon"], 24 | "storefront": "yacceleratorstorefront", 25 | "template": "yacceleratorstorefront" 26 | } 27 | ] 28 | } 29 | ''') as Map 30 | def manifest = Manifest.fromMap(rawManifest) 31 | def resolver = new TestExtensionResolver() 32 | resolver.addExtension("spartacussampledataaddon") 33 | resolver.addExtension("yacceleratorstorefront") 34 | resolver.addExtension("textfieldconfiguratortemplateaddon") 35 | def validator = new AddonValidator(resolver); 36 | 37 | when: 38 | def errors = validator.validate(manifest) 39 | 40 | then: 41 | errors.size() == 3 42 | errors.any{ it.level == Level.ERROR && it.message.contains("`yb2bacceleratorstorefront`")} 43 | errors.any{ it.level == Level.ERROR && it.message.contains("`commerceorgsamplesaddon`")} 44 | errors.any{ it.level == Level.ERROR && it.message.contains("`smarteditaddon`")} 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/AspectValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.impl.AspectValidator 8 | 9 | class AspectValidatorSpec extends Specification { 10 | 11 | def "validating aspects detects errors"() { 12 | given: 13 | def rawManifest = new JsonSlurper().parseText('''\ 14 | { 15 | "commerceSuiteVersion": "2011.1", 16 | "aspects": [ 17 | { "name": "invalid" }, 18 | { 19 | "name": "backoffice", 20 | "properties": [ 21 | { 22 | "key": "spring.session.enabled", 23 | "value": "false" 24 | } 25 | ], 26 | "webapps": [ 27 | { 28 | "name": "hac", 29 | "contextPath": "/hac" 30 | }, 31 | { 32 | "name": "hac", 33 | "contextPath": "/other" 34 | } 35 | ] 36 | }, 37 | { 38 | "name": "accstorefront", 39 | "webapps": [ 40 | { 41 | "name": "albinostorefront", 42 | "contextPath": "" 43 | }, 44 | { 45 | "name": "otherstorefront", 46 | "contextPath": "" 47 | }, 48 | { 49 | "name": "mediaweb", 50 | "contextPath": "media" 51 | } 52 | ] 53 | }, 54 | { 55 | "name": "admin", 56 | "webapps": [ 57 | { 58 | "name": "storefront", 59 | "contextPath": "" 60 | } 61 | ] 62 | } 63 | ] 64 | } 65 | ''') as Map 66 | def manifest = Manifest.fromMap(rawManifest) 67 | def validator = new AspectValidator(); 68 | 69 | when: 70 | def errors = validator.validate(manifest) 71 | 72 | then: 73 | errors.size() == 6 74 | errors.any {it.level == Level.ERROR && it.location == "aspects[?name == 'invalid']" && it.message.contains("not supported")} 75 | errors.any {it.level == Level.ERROR && it.location == "aspects[?name == 'admin']" && it.message.contains("Webapps")} 76 | errors.any {it.level == Level.ERROR && it.location == "aspects[?name == 'accstorefront'].webapps[1]" && it.message.contains("Context path ``")} 77 | errors.any {it.level == Level.ERROR && it.location == "aspects[?name == 'backoffice'].webapps[1]" && it.message.contains("Extension `hac`")} 78 | errors.any {it.level == Level.WARNING && it.location == "aspects[?name == 'backoffice'].properties[0]" && it.message.contains('`spring.session.enabled`')} 79 | errors.any {it.level == Level.ERROR && it.location == "aspects[?name == 'accstorefront'].webapps[2]" && it.message.contains("`/`")} 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/AspectWebappValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.Error 8 | import mpern.sap.commerce.ccv2.validation.Level 9 | import mpern.sap.commerce.ccv2.validation.impl.AspectWebappValidator 10 | 11 | class AspectWebappValidatorSpec extends Specification { 12 | 13 | def "webapps are checked against effective extensions"() { 14 | given: 15 | def rawManifest = new JsonSlurper().parseText('''\ 16 | { 17 | "commerceSuiteVersion": "2011.1", 18 | "aspects": [ 19 | { 20 | "name": "accstorefront", 21 | "webapps": [ 22 | { 23 | "name": "albinostorefront", 24 | "contextPath": "" 25 | }, 26 | { 27 | "name": "otherstorefront", 28 | "contextPath": "" 29 | }, 30 | { 31 | "name": "mediaweb", 32 | "contextPath": "media" 33 | } 34 | ] 35 | } 36 | ] 37 | } 38 | ''') as Map 39 | def manifest = Manifest.fromMap(rawManifest) 40 | def resolver = new TestExtensionResolver(); 41 | resolver.addExtension("mediaweb") 42 | resolver.addExtension("albinostorefront") 43 | def validator = new AspectWebappValidator(resolver); 44 | 45 | when: 46 | def errors = validator.validate(manifest) 47 | 48 | then: 49 | errors.size() == 1 50 | errors.any{ it.level == Level.ERROR && it.message.contains('`otherstorefront`') } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/CloudHotfolderValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | 4 | import java.nio.file.Path 5 | 6 | import groovy.json.JsonSlurper 7 | import spock.lang.Specification 8 | import spock.lang.TempDir 9 | 10 | import mpern.sap.commerce.ccv2.model.Manifest 11 | import mpern.sap.commerce.ccv2.validation.impl.CloudHotfolderValidator 12 | import mpern.sap.commerce.ccv2.validation.impl.ManifestExtensionsResolver 13 | 14 | class CloudHotfolderValidatorSpec extends Specification { 15 | 16 | @TempDir 17 | Path testProjectDir 18 | 19 | 20 | def "hotfolder validator checks backgroundProcessing properties"() { 21 | given: 22 | def rawManifest = new JsonSlurper().parseText('''\ 23 | { 24 | "commerceSuiteVersion": "2011", 25 | "extensions": [ 26 | "azurecloudhotfolder" 27 | ], 28 | "useConfig": { 29 | "properties": [ 30 | { 31 | "location": "background.properties", 32 | "aspect": "backgroundProcessing" 33 | } 34 | ] 35 | } 36 | } 37 | ''') as Map 38 | def manifest = Manifest.fromMap(rawManifest) 39 | def validator = new CloudHotfolderValidator(testProjectDir, new ManifestExtensionsResolver(testProjectDir)) 40 | def props = testProjectDir.resolve("background.properties") 41 | 42 | when: 43 | def unconfiguredErrors = validator.validate(manifest) 44 | 45 | props.text = "cluster.node.groups=yHotfolderCandidate,integration,backgroundProcessing,foo" 46 | def configuredErrors = validator.validate(manifest) 47 | 48 | then: 49 | unconfiguredErrors.size() == 1 50 | unconfiguredErrors.any{ it.level == Level.WARNING && it.code == "W-003"} 51 | configuredErrors.isEmpty() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/ExtensionResolverSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import java.nio.file.Files 4 | import java.nio.file.Path 5 | 6 | import groovy.json.JsonSlurper 7 | import spock.lang.IgnoreIf 8 | import spock.lang.Specification 9 | 10 | import mpern.sap.commerce.ccv2.model.Manifest 11 | import mpern.sap.commerce.ccv2.validation.impl.ManifestExtensionsResolver 12 | 13 | class ExtensionResolverSpec extends Specification { 14 | 15 | Path projectRoot = Path.of("manualTest") 16 | 17 | @IgnoreIf({ !Files.exists(instance.projectRoot.resolve("hybris/bin/platform")) }) 18 | def "test extension resolver"() { 19 | given: 20 | def resolver = new ManifestExtensionsResolver(projectRoot) 21 | def rawManifest = new JsonSlurper().parse(projectRoot.resolve("manifest.json").toFile(), "UTF-8") as Map 22 | def manifest = Manifest.fromMap(rawManifest) 23 | 24 | when: 25 | def result = resolver.determineEffectiveExtensions(manifest) 26 | 27 | then: 28 | !result.extensions.isEmpty() 29 | result.locations.size() == 2 30 | result.locations.any{it.contains("useConfig.extensions.location")} 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/IntExtPackValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.impl.IntExtPackValidator 8 | 9 | class IntExtPackValidatorSpec extends Specification { 10 | 11 | def "Int Ext Pack validation recognizes correct pack"(String commerce, String pack) { 12 | given: 13 | def rawManifest = new JsonSlurper().parseText(""" 14 | { 15 | "commerceSuiteVersion": "${commerce}", 16 | "extensionPacks" : [ 17 | { 18 | "name" : "hybris-commerce-integrations", 19 | "version" : "${pack}" 20 | } 21 | ] 22 | } 23 | """) as Map 24 | Manifest manifest = Manifest.fromMap(rawManifest) 25 | def validator = new IntExtPackValidator() 26 | 27 | when: 28 | def result = validator.validate(manifest) 29 | 30 | then: 31 | result.isEmpty() 32 | 33 | where: 34 | commerce | pack 35 | "2005" | "2005.2" 36 | "2011" | "2102.0" 37 | "2105" | "2108.0" 38 | "2205" | "2205.0" 39 | "2211" | "2211.0" 40 | } 41 | 42 | def "Int Ext Pack validation recognizes invalid combinations"(String commerce, String pack, String message) { 43 | given: 44 | def rawManifest = new JsonSlurper().parseText(""" 45 | { 46 | "commerceSuiteVersion": "${commerce}", 47 | "extensionPacks" : [ 48 | { 49 | "name" : "hybris-commerce-integrations", 50 | "version" : "${pack}" 51 | } 52 | ] 53 | } 54 | """) as Map 55 | Manifest manifest = Manifest.fromMap(rawManifest) 56 | def validator = new IntExtPackValidator() 57 | 58 | when: 59 | def result = validator.validate(manifest) 60 | 61 | then: 62 | result.size() == 1 63 | result.any{ it.level == Level.ERROR && it.message.toLowerCase().contains(message)} 64 | 65 | where: 66 | commerce | pack | message 67 | "2005" | "2015.2" | "not compatible" 68 | "1811" | "2005.2" | "available" 69 | "2105" | "2108" | "qualified" 70 | "2211" | "2211" | "qualified" 71 | "2211" | "2108.1" | "not compatible" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/MediaConversionValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.impl.MediaConversionValidator 8 | 9 | class MediaConversionValidatorSpec extends Specification { 10 | def "media conversion requires both service and extension"() { 11 | given: 12 | def rawManifest = new JsonSlurper().parseText('''\ 13 | { 14 | "commerceSuiteVersion": "2011", 15 | "enableImageProcessingService": true 16 | } 17 | ''') as Map 18 | def serviceOnlyManifest = Manifest.fromMap(rawManifest) 19 | 20 | rawManifest = new JsonSlurper().parseText('''\ 21 | { 22 | "commerceSuiteVersion": "2011", 23 | "extensions": [ 24 | "cloudmediaconversion" 25 | ] 26 | } 27 | ''') as Map 28 | def extensionOnlyManifest = Manifest.fromMap(rawManifest) 29 | def testResolver = new TestExtensionResolver() 30 | def validator = new MediaConversionValidator(testResolver) 31 | 32 | when: 33 | def withoutExtensions = validator.validate(serviceOnlyManifest) 34 | 35 | testResolver.addExtension("cloudmediaconversion") 36 | def withExtension = validator.validate(serviceOnlyManifest) 37 | 38 | def withoutService = validator.validate(extensionOnlyManifest) 39 | 40 | then: 41 | withoutExtensions.size() == 1 42 | withExtension.isEmpty() 43 | withoutService.size() == 1 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/PropertyValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.impl.PropertyValidator 8 | 9 | class PropertyValidatorSpec extends Specification { 10 | 11 | def "manifest properties are validated"() { 12 | given: 13 | def rawManifest = new JsonSlurper().parseText('''\ 14 | { 15 | "commerceSuiteVersion": "2011.1", 16 | "properties": [ 17 | { 18 | "key": "clustermode", 19 | "value": "false" 20 | }, 21 | { 22 | "key": "persona.property", 23 | "value": "wrong persona", 24 | "persona": "invalid" 25 | }, 26 | { 27 | "key": "valid.property", 28 | "value": "value", 29 | "persona": "production" 30 | } 31 | ] 32 | } 33 | ''') as Map 34 | def manifest = Manifest.fromMap(rawManifest) 35 | def validator = new PropertyValidator() 36 | 37 | when: 38 | def errors = validator.validate(manifest); 39 | 40 | then: 41 | errors.size() == 2 42 | errors.any{ it.level == Level.WARNING && it.location == 'properties[0]' && it.message.contains("`clustermode`")} 43 | errors.any{ it.level == Level.ERROR && it.location == 'properties[1]' && it.message.contains("`invalid`")} 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/SolrVersionValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import groovy.json.JsonSlurper 4 | import spock.lang.Specification 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest 7 | import mpern.sap.commerce.ccv2.validation.impl.SolrVersionValidator 8 | 9 | class SolrVersionValidatorSpec extends Specification { 10 | def "solrVersion version must a valid Solr major.minor version"() { 11 | given: 12 | def rawManifest = new JsonSlurper().parseText(''' 13 | { 14 | "commerceSuiteVersion": "2011", 15 | "solrVersion": "8.6.3" 16 | } 17 | ''') as Map 18 | Manifest manifest = Manifest.fromMap(rawManifest) 19 | def validator = new SolrVersionValidator(); 20 | 21 | when: 22 | def faultySolr = validator.validate(manifest) 23 | 24 | then: 25 | faultySolr.size() == 1 26 | faultySolr.any{ it.location == "solrVersion" && it.level == Level.ERROR } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/TestExtensionResolver.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | import groovy.lang.Tuple2; 4 | import mpern.sap.commerce.build.util.Extension; 5 | import mpern.sap.commerce.ccv2.model.Manifest; 6 | 7 | import java.nio.file.Path; 8 | import java.util.ArrayList; 9 | import java.util.Collections; 10 | import java.util.List; 11 | import java.util.Set; 12 | import java.util.stream.Collectors; 13 | 14 | public class TestExtensionResolver implements ExtensionsResolver { 15 | private List extensions = new ArrayList<>(); 16 | 17 | @Override 18 | public Result determineEffectiveExtensions(Manifest manifest) { 19 | return new Result(extensions, Collections.singletonList("test.extensions")); 20 | } 21 | 22 | @Override 23 | public Tuple2, List> listAllConfiguredExtensions(Manifest manifest) { 24 | return new Tuple2<>(extensions.stream().map(e -> e.name).collect(Collectors.toUnmodifiableSet()), Collections.singletonList("test.extensions")); 25 | } 26 | 27 | public void addExtension(String name) { 28 | extensions.add(new Extension(name, Path.of("test", name))); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/groovy/mpern/sap/commerce/ccv2/validation/WebrootValidatorSpec.groovy: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation 2 | 3 | import java.nio.file.Path 4 | 5 | import groovy.json.JsonSlurper 6 | import spock.lang.Specification 7 | import spock.lang.TempDir 8 | 9 | import mpern.sap.commerce.ccv2.model.Manifest 10 | import mpern.sap.commerce.ccv2.validation.impl.WebrootValidator 11 | 12 | class WebrootValidatorSpec extends Specification { 13 | 14 | @TempDir 15 | Path testProjectDir 16 | 17 | def "extension.webroot is not allowed"() { 18 | given: 19 | def rawManifest = new JsonSlurper().parseText('''\ 20 | { 21 | "commerceSuiteVersion": "1905.5", 22 | "properties": [ 23 | { 24 | "key": "hac.webroot", 25 | "value": "/hac" 26 | } 27 | ], 28 | "useConfig": { 29 | "properties": [ 30 | { 31 | "location": "webroot.properties" 32 | } 33 | ] 34 | }, 35 | "aspects": [ 36 | { 37 | "name": "backoffice", 38 | "properties": [ 39 | { 40 | "key": "backoffice.webroot", 41 | "value": "/foo", 42 | "persona": "production" 43 | } 44 | ] 45 | } 46 | ] 47 | } 48 | ''') as Map 49 | def manifest = Manifest.fromMap(rawManifest) 50 | def validator = new WebrootValidator(testProjectDir) 51 | testProjectDir.resolve("webroot.properties").text = '''\ 52 | demostorefront.webroot=/root 53 | '''.stripIndent() 54 | 55 | when: 56 | def webrootErrors = validator.validate(manifest) 57 | 58 | then: 59 | webrootErrors.size() == 3 60 | webrootErrors.any{it.code == "E-017" && it.location == 'properties[0]' && it.message.contains("hac.webroot")} 61 | webrootErrors.any{it.code == "E-017" && it.location == "aspects[?name == 'backoffice'].properties[0]" && it.message.contains("backoffice.webroot")} 62 | webrootErrors.any{it.code == "E-017" && it.location == 'useConfig.properties[0].location (webroot.properties)' && it.message.contains("demostorefront.webroot")} 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/resources/integration-test-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commerceSuiteVersion": "6.7.0.1", 3 | "disableImageReuse": true, 4 | "extensions": [ 5 | "modeltacceleratorservices", 6 | "electronicsstore", 7 | "privacyoverlayeraddon", 8 | "yacceleratorstorefront", 9 | "backoffice" 10 | ], 11 | "properties": [ 12 | { 13 | "key": "test.property.1", 14 | "value": "test.property.1.value", 15 | "persona": "production" 16 | }, 17 | { 18 | "key": "test.property.2", 19 | "value": "test.property.2.value", 20 | "persona": "development" 21 | }, 22 | { 23 | "key": "test.property.2", 24 | "value": "test.property.2.value.in.prod.only", 25 | "persona": "production" 26 | } 27 | ], 28 | "useConfig": { 29 | "properties": [ 30 | { 31 | "location": "config/local.properties" 32 | }, 33 | { 34 | "location": "config/local-dev.properties", 35 | "persona": "development" 36 | }, 37 | { 38 | "location": "config/local-stage.properties", 39 | "persona": "staging" 40 | }, 41 | { 42 | "location": "config/local-prod.properties", 43 | "persona": "production" 44 | }, 45 | { 46 | "location": "config/local-backoffice-prod.properties", 47 | "persona": "production", 48 | "aspect": "backoffice" 49 | }, 50 | { 51 | "location": "config/local-backoffice.properties", 52 | "aspect": "backoffice" 53 | } 54 | ], 55 | "extensions": { 56 | "location": "config/localextensions.xml", 57 | "exclude": [ 58 | "backoffice" 59 | ] 60 | }, 61 | "solr": { 62 | "location": "solr/custom" 63 | }, 64 | "languages": { 65 | "location": "_LANGUAGES_" 66 | } 67 | }, 68 | "storefrontAddons": [ 69 | { 70 | "addon": "privacyoverlayeraddon", 71 | "storefront": "albinostorefront", 72 | "template": "yacceleratorstorefront" 73 | }, 74 | { 75 | "addon": "albinoaddon", 76 | "storefront": "albinostorefront", 77 | "template": "yacceleratorstorefront" 78 | } 79 | ], 80 | "aspects": [ 81 | { 82 | "name": "backoffice", 83 | "properties": [ 84 | { 85 | "key": "test.property.1", 86 | "value": "test.property-1-value-prod-backoffice", 87 | "persona": "production" 88 | }, 89 | { 90 | "key": "test.property.2", 91 | "value": "test.property-2-value-backoffice" 92 | } 93 | ], 94 | "webapps": [ 95 | { 96 | "name": "hac", 97 | "contextPath": "/hac" 98 | }, 99 | { 100 | "name": "mediaweb", 101 | "contextPath": "/medias" 102 | }, 103 | { 104 | "name": "backoffice", 105 | "contextPath": "" 106 | } 107 | ] 108 | }, 109 | { 110 | "name": "accstorefront", 111 | "properties": [ 112 | { 113 | "key": "spring.session.enabled", 114 | "value": "true" 115 | }, 116 | { 117 | "key": "spring.session.yacceleratorstorefront.save", 118 | "value": "async" 119 | }, 120 | { 121 | "key": "spring.session.yacceleratorstorefront.cookie.name", 122 | "value": "JSESSIONID" 123 | }, 124 | { 125 | "key": "spring.session.yacceleratorstorefront.cookie.path", 126 | "value": "/" 127 | }, 128 | { 129 | "key": "storefrontContextRoot", 130 | "value": "" 131 | } 132 | ], 133 | "webapps": [ 134 | { 135 | "name": "mediaweb", 136 | "contextPath": "/medias" 137 | }, 138 | { 139 | "name": "albinostorefront", 140 | "contextPath": "" 141 | }, 142 | { 143 | "name": "acceleratorservices", 144 | "contextPath": "/acceleratorservices" 145 | } 146 | ] 147 | }, 148 | { 149 | "name": "backgroundProcessing", 150 | "properties": [], 151 | "webapps": [ 152 | { 153 | "name": "hac", 154 | "contextPath": "" 155 | }, 156 | { 157 | "name": "mediaweb", 158 | "contextPath": "/medias" 159 | } 160 | ] 161 | } 162 | ], 163 | "tests": { 164 | "extensions": [ 165 | "privacyoverlayeraddon", 166 | "yacceleratorstorefront" 167 | ], 168 | "annotations": [ 169 | "UnitTests", 170 | "IntegrationTests" 171 | ], 172 | "packages": [ 173 | "de.hybris.infra.*" 174 | ] 175 | }, 176 | "webTests": { 177 | "extensions": [ 178 | "yacceleratorstorefront" 179 | ], 180 | "excludedPackages": [ 181 | "de.hybris.platform.*" 182 | ] 183 | } 184 | } -------------------------------------------------------------------------------- /cloud-plugin/src/integrationTest/resources/minimal-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commerceSuiteVersion": "2211", 3 | "extensions": [ 4 | "modeltacceleratorservices", 5 | "electronicsstore", 6 | "privacyoverlayeraddon", 7 | "yacceleratorstorefront", 8 | "backoffice" 9 | ] 10 | } -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/CCv2Extension.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.gradle.api.file.DirectoryProperty; 6 | 7 | import mpern.sap.commerce.ccv2.model.Manifest; 8 | 9 | public abstract class CCv2Extension { 10 | 11 | private final Manifest manifest; 12 | 13 | @Inject 14 | public CCv2Extension(Manifest manifest) { 15 | this.manifest = manifest; 16 | } 17 | 18 | public abstract DirectoryProperty getGeneratedConfiguration(); 19 | 20 | public abstract DirectoryProperty getCloudExtensionPackFolder(); 21 | 22 | public Manifest getManifest() { 23 | return manifest; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/Addon.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.emptyOrList; 4 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.toEmpty; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public class Addon { 10 | 11 | public final String addon; 12 | public final String storefront; 13 | public final String template; 14 | public final List addons; 15 | public final List storefronts; 16 | 17 | private Addon(String addon, String storefront, String template, List addons, List storefronts) { 18 | this.addon = addon; 19 | this.storefront = storefront; 20 | this.template = template; 21 | this.addons = addons; 22 | this.storefronts = storefronts; 23 | } 24 | 25 | @SuppressWarnings("unchecked") 26 | public static Addon fromMap(Map jsonMap) { 27 | return new Addon(toEmpty((String) jsonMap.get("addon")), toEmpty((String) jsonMap.get("storefront")), 28 | toEmpty((String) jsonMap.get("template")), emptyOrList((List) jsonMap.get("addons")), 29 | emptyOrList((List) jsonMap.get("storefronts"))); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/Aspect.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.validateNullOrWhitespace; 4 | 5 | import java.util.*; 6 | 7 | public class Aspect { 8 | 9 | // https://help.sap.com/viewer/1be46286b36a4aa48205be5a96240672/v2011/en-US/8f494fb9617346188ddf21a971db84fc.html 10 | public static final String ADMIN_ASPECT = "admin"; 11 | public static final String BACKGROUND_ASPECT = "backgroundProcessing"; 12 | public static final Set ALLOWED_ASPECTS = new HashSet<>( 13 | Arrays.asList("accstorefront", "backoffice", BACKGROUND_ASPECT, ADMIN_ASPECT, "api")); 14 | 15 | public final String name; 16 | public final List properties; 17 | public final List webapps; 18 | 19 | private Aspect(String name, List properties, List webapps) { 20 | this.name = name; 21 | this.properties = Collections.unmodifiableList(properties); 22 | this.webapps = Collections.unmodifiableList(webapps); 23 | } 24 | 25 | public List getProperties() { 26 | return properties; 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | public static Aspect fromMap(Map jsonMap) { 31 | String name = validateNullOrWhitespace((String) jsonMap.get("name"), "Aspect.name must have a value"); 32 | 33 | List> raw = Optional.ofNullable((List>) jsonMap.get("properties")) 34 | .orElse(Collections.emptyList()); 35 | List properties = raw.stream().map(Property::fromMap).toList(); 36 | 37 | raw = Optional.ofNullable((List>) jsonMap.get("webapps")).orElse(Collections.emptyList()); 38 | List webapps = raw.stream().map(Webapp::fromMap).toList(); 39 | 40 | return new Aspect(name, properties, webapps); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/ExtensionPack.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.toEmpty; 4 | 5 | import java.util.Map; 6 | 7 | import mpern.sap.commerce.build.util.Version; 8 | 9 | public class ExtensionPack { 10 | public final String name; 11 | public final String version; 12 | public final String artifact; 13 | 14 | public ExtensionPack(String name, String version, String artifact) { 15 | this.name = name; 16 | this.version = version; 17 | this.artifact = artifact; 18 | } 19 | 20 | public static ExtensionPack fromMap(Map jsonMap) { 21 | String name = toEmpty((String) jsonMap.get("name")); 22 | String version = toEmpty((String) jsonMap.get("version")); 23 | String artifact = toEmpty((String) jsonMap.get("artifact")); 24 | 25 | String validationVersion; 26 | if (artifact.isEmpty()) { 27 | if ((name.isEmpty() || version.isEmpty())) { 28 | throw new IllegalArgumentException( 29 | String.format("ExtensionPack %s:%s - please specify the name and version", name, version)); 30 | } 31 | validationVersion = version; 32 | } else { 33 | final String[] artifactParts = artifact.split("[:@]"); 34 | if (artifactParts.length < 3) { 35 | throw new IllegalArgumentException("Invalid extensionPack.artifact string"); 36 | } 37 | validationVersion = artifactParts[2]; 38 | } 39 | Version.parseVersion(validationVersion); 40 | 41 | return new ExtensionPack(name, version, artifact); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/Manifest.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.*; 4 | 5 | import java.util.*; 6 | 7 | public class Manifest { 8 | public final String commerceSuiteVersion; 9 | public final String solrVersion; 10 | public final boolean useCloudExtensionPack; 11 | public final boolean enableImageProcessingService; 12 | 13 | public final List extensionPacks; 14 | 15 | public final boolean troubleshootingModeEnabled; 16 | public final boolean disableImageReuse; 17 | 18 | public final UseConfig useConfig; 19 | 20 | public final Set extensions; 21 | 22 | public final List storefrontAddons; 23 | 24 | public final List properties; 25 | 26 | public final List aspects; 27 | 28 | public final TestConfiguration tests; 29 | 30 | public final TestConfiguration webTests; 31 | 32 | public Manifest(String commerceSuiteVersion, String solrVersion, boolean useCloudExtensionPack, 33 | boolean enableImageProcessingService, boolean troubleshootingModeEnabled, boolean disableImageReuse, 34 | UseConfig useConfig, List extensionPacks, Set extensions, 35 | List storefrontAddons, List properties, List aspects, TestConfiguration tests, 36 | TestConfiguration webTests) { 37 | this.commerceSuiteVersion = commerceSuiteVersion; 38 | this.solrVersion = solrVersion; 39 | this.useCloudExtensionPack = useCloudExtensionPack; 40 | this.enableImageProcessingService = enableImageProcessingService; 41 | this.troubleshootingModeEnabled = troubleshootingModeEnabled; 42 | this.disableImageReuse = disableImageReuse; 43 | this.useConfig = useConfig; 44 | this.extensionPacks = extensionPacks; 45 | this.extensions = Collections.unmodifiableSet(extensions); 46 | this.storefrontAddons = Collections.unmodifiableList(storefrontAddons); 47 | this.properties = Collections.unmodifiableList(properties); 48 | this.aspects = Collections.unmodifiableList(aspects); 49 | this.tests = tests; 50 | this.webTests = webTests; 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public static Manifest fromMap(Map jsonMap) { 55 | String version = validateNullOrWhitespace((String) jsonMap.get("commerceSuiteVersion"), 56 | "Manifest.commerceSuiteVersion must have a value"); 57 | 58 | String solrVersion = (String) jsonMap.get("solrVersion"); 59 | if (solrVersion == null) { 60 | solrVersion = ""; 61 | } 62 | 63 | Object rawBool = jsonMap.get("useCloudExtensionPack"); 64 | boolean useExtensionPack = parseBoolean(rawBool, "useCloudExtensionPack"); 65 | 66 | rawBool = jsonMap.get("enableImageProcessingService"); 67 | boolean enableImageProcessingService = parseBoolean(rawBool, "enableImageProcessingService"); 68 | 69 | List> raw = (List>) jsonMap.get("extensionPacks"); 70 | List extensionPacks; 71 | if (raw == null) { 72 | extensionPacks = Collections.emptyList(); 73 | } else { 74 | extensionPacks = raw.stream().map(ExtensionPack::fromMap).toList(); 75 | } 76 | 77 | rawBool = jsonMap.get("troubleshootingModeEnabled"); 78 | boolean troubleshootingModeEnabled = parseBoolean(rawBool, "troubleshootingModeEnabled"); 79 | 80 | rawBool = jsonMap.get("disableImageReuse"); 81 | boolean disableImageReuse = parseBoolean(rawBool, "disableImageReuse"); 82 | 83 | UseConfig useConfig = UseConfig.fromMap((Map) jsonMap.get("useConfig")); 84 | 85 | Set extensions = emptyOrSet((List) jsonMap.get("extensions")); 86 | 87 | raw = (List>) jsonMap.get("storefrontAddons"); 88 | List addons; 89 | if (raw == null) { 90 | addons = Collections.emptyList(); 91 | } else { 92 | addons = raw.stream().map(Addon::fromMap).toList(); 93 | } 94 | 95 | raw = (List>) jsonMap.get("properties"); 96 | List properties; 97 | if (raw == null) { 98 | properties = Collections.emptyList(); 99 | } else { 100 | properties = raw.stream().map(Property::fromMap).toList(); 101 | } 102 | 103 | raw = (List>) jsonMap.get("aspects"); 104 | List aspects; 105 | if (raw == null) { 106 | aspects = Collections.emptyList(); 107 | } else { 108 | aspects = raw.stream().map(Aspect::fromMap).toList(); 109 | } 110 | 111 | Map rawConfig = (Map) jsonMap.get("tests"); 112 | TestConfiguration tests = Optional.ofNullable(rawConfig).map(TestConfiguration::fromMap) 113 | .orElse(TestConfiguration.NO_VALUE); 114 | 115 | rawConfig = (Map) jsonMap.get("webTests"); 116 | TestConfiguration webTests = Optional.ofNullable(rawConfig).map(TestConfiguration::fromMap) 117 | .orElse(TestConfiguration.NO_VALUE); 118 | 119 | return new Manifest(version, solrVersion, useExtensionPack, enableImageProcessingService, 120 | troubleshootingModeEnabled, disableImageReuse, useConfig, extensionPacks, extensions, addons, 121 | properties, aspects, tests, webTests); 122 | } 123 | 124 | // necessary to shadow groovy method getProperties 125 | public List getProperties() { 126 | return properties; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/Property.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.nullToEmpty; 4 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.validateNullOrWhitespace; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashSet; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class Property { 12 | 13 | public static final Set ALLOWED_PERSONAS = new HashSet<>( 14 | Arrays.asList("development", "staging", "production", "")); 15 | 16 | public final String key; 17 | public final String value; 18 | public final String persona; 19 | public final boolean secret; 20 | 21 | private Property(String key, String value, String persona, boolean secret) { 22 | this.key = key; 23 | this.value = value; 24 | this.persona = persona; 25 | this.secret = secret; 26 | } 27 | 28 | public static Property fromMap(Map jsonMap) { 29 | return new Property(validateNullOrWhitespace((String) jsonMap.get("key"), "Property.key must have a value"), 30 | nullToEmpty((String) jsonMap.get("value")), nullToEmpty((String) jsonMap.get("persona")), 31 | jsonMap.get("secret") != null && (boolean) jsonMap.get("secret")); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/TestConfiguration.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | import mpern.sap.commerce.ccv2.model.util.ParseUtils; 9 | 10 | public class TestConfiguration { 11 | 12 | public static final TestConfiguration NO_VALUE = new TestConfiguration(Collections.emptySet(), 13 | Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); 14 | 15 | public final Set extensions; 16 | public final Set annotations; 17 | public final Set packages; 18 | public final Set excludedPackages; 19 | 20 | private TestConfiguration(Set extensions, Set annotations, Set packages, 21 | Set excludedPackages) { 22 | this.extensions = Collections.unmodifiableSet(extensions); 23 | this.annotations = Collections.unmodifiableSet(annotations); 24 | this.packages = Collections.unmodifiableSet(packages); 25 | this.excludedPackages = Collections.unmodifiableSet(excludedPackages); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public static TestConfiguration fromMap(Map jsonMap) { 30 | Set ext = ParseUtils.emptyOrSet((List) jsonMap.get("extensions")); 31 | Set anot = ParseUtils.emptyOrSet((List) jsonMap.get("annotations")); 32 | Set pack = ParseUtils.emptyOrSet((List) jsonMap.get("packages")); 33 | Set excludedPackages = ParseUtils.emptyOrSet((List) jsonMap.get("excludedPackages")); 34 | 35 | return new TestConfiguration(ext, anot, pack, excludedPackages); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/UseConfig.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Objects; 7 | 8 | import mpern.sap.commerce.ccv2.model.useconfig.Extensions; 9 | import mpern.sap.commerce.ccv2.model.useconfig.Languages; 10 | import mpern.sap.commerce.ccv2.model.useconfig.Properties; 11 | import mpern.sap.commerce.ccv2.model.useconfig.Solr; 12 | 13 | public class UseConfig { 14 | public static final UseConfig NO_VALUE = new UseConfig(Extensions.NO_VALUE, Collections.emptyList(), Solr.NO_VALUE, 15 | Languages.NO_VALUE); 16 | 17 | public final Extensions extensions; 18 | 19 | public final List properties; 20 | 21 | public final Solr solr; 22 | 23 | public final Languages languages; 24 | 25 | private UseConfig(Extensions extensions, List properties, Solr solr, Languages languages) { 26 | this.extensions = extensions; 27 | this.properties = properties; 28 | this.solr = solr; 29 | this.languages = languages; 30 | } 31 | 32 | @SuppressWarnings("unchecked") 33 | public static UseConfig fromMap(Map input) { 34 | if (input == null) { 35 | return NO_VALUE; 36 | } 37 | Extensions extensions = Extensions.fromMap((Map) input.get("extensions")); 38 | List properties = Collections.emptyList(); 39 | List> rawProps = (List>) input.get("properties"); 40 | if (rawProps != null) { 41 | properties = rawProps.stream().map(Properties::fromMap).filter(Objects::nonNull).toList(); 42 | } 43 | Solr solr = Solr.fromMap((Map) input.get("solr")); 44 | Languages languages = Languages.fromMap((Map) input.get("languages")); 45 | return new UseConfig(extensions, properties, solr, languages); 46 | } 47 | 48 | public List getProperties() { 49 | return properties; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/Webapp.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.validateNullOrWhitespace; 4 | 5 | import java.util.Map; 6 | import java.util.Objects; 7 | 8 | public class Webapp { 9 | public final String name; 10 | public final String contextPath; 11 | 12 | public Webapp(String name, String contextPath) { 13 | this.name = name; 14 | this.contextPath = contextPath; 15 | } 16 | 17 | public static Webapp fromMap(Map jsonMap) { 18 | return new Webapp(validateNullOrWhitespace((String) jsonMap.get("name"), "Webapp.name must have a value"), 19 | Objects.requireNonNull((String) jsonMap.get("contextPath"), "Webapp.contextPath must be set")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/useconfig/Extensions.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model.useconfig; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.emptyOrSet; 4 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.toEmpty; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | public class Extensions { 12 | public static final Extensions NO_VALUE = new Extensions("", Collections.emptySet()); 13 | public final String location; 14 | public final Set exclude; 15 | 16 | private Extensions(String location, Set exclude) { 17 | this.location = location; 18 | this.exclude = Collections.unmodifiableSet(exclude); 19 | } 20 | 21 | @SuppressWarnings("unchecked") 22 | public static Extensions fromMap(Map input) { 23 | if (input == null) { 24 | return NO_VALUE; 25 | } 26 | String location = toEmpty((String) input.get("location")); 27 | Set exclude = emptyOrSet((List) input.get("exclude")); 28 | return new Extensions(location, exclude); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/useconfig/Languages.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model.useconfig; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.toEmpty; 4 | 5 | import java.util.Map; 6 | 7 | public class Languages { 8 | public static final Languages NO_VALUE = new Languages(""); 9 | public final String location; 10 | 11 | private Languages(String location) { 12 | this.location = location; 13 | } 14 | 15 | public static Languages fromMap(Map input) { 16 | if (input == null) { 17 | return NO_VALUE; 18 | } 19 | String location = toEmpty((String) input.get("location")); 20 | return new Languages(location); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/useconfig/Properties.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model.useconfig; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.toEmpty; 4 | 5 | import java.util.Map; 6 | 7 | public class Properties { 8 | public static final Properties NO_VALUE = new Properties("", "", ""); 9 | public final String location; 10 | public final String aspect; 11 | public final String persona; 12 | 13 | private Properties(String location, String aspect, String persona) { 14 | this.location = location; 15 | this.aspect = aspect; 16 | this.persona = persona; 17 | } 18 | 19 | public static Properties fromMap(Map input) { 20 | if (input == null) { 21 | return NO_VALUE; 22 | } 23 | String location = toEmpty((String) input.get("location")); 24 | String aspect = toEmpty((String) input.get("aspect")); 25 | String persona = toEmpty((String) input.get("persona")); 26 | 27 | return new Properties(location, aspect, persona); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/useconfig/Solr.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model.useconfig; 2 | 3 | import static mpern.sap.commerce.ccv2.model.util.ParseUtils.toEmpty; 4 | 5 | import java.util.Map; 6 | 7 | public class Solr { 8 | public static final Solr NO_VALUE = new Solr(""); 9 | 10 | public final String location; 11 | 12 | private Solr(String location) { 13 | this.location = location; 14 | } 15 | 16 | public static Solr fromMap(Map input) { 17 | if (input == null) { 18 | return NO_VALUE; 19 | } 20 | String location = toEmpty((String) input.get("location")); 21 | return new Solr(location); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/model/util/ParseUtils.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.model.util; 2 | 3 | import java.util.Collections; 4 | import java.util.LinkedHashSet; 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | public final class ParseUtils { 9 | public static String nullToEmpty(String v) { 10 | return v == null ? "" : v; 11 | } 12 | 13 | public static String validateNullOrWhitespace(String value, String message) { 14 | if (value == null || value.matches("\\s*")) { 15 | throw new IllegalArgumentException(message); 16 | } 17 | return value; 18 | } 19 | 20 | public static Set emptyOrSet(List list) { 21 | if (list == null || list.isEmpty()) { 22 | return Collections.emptySet(); 23 | } else { 24 | return new LinkedHashSet<>(list); 25 | } 26 | } 27 | 28 | public static List emptyOrList(List list) { 29 | if (list == null || list.isEmpty()) { 30 | return Collections.emptyList(); 31 | } else { 32 | return Collections.unmodifiableList(list); 33 | } 34 | } 35 | 36 | public static String toEmpty(String input) { 37 | if (input == null) { 38 | return ""; 39 | } else { 40 | return input.trim(); 41 | } 42 | } 43 | 44 | public static boolean parseBoolean(Object input, String fieldName) { 45 | if (input != null) { 46 | if (!(input instanceof Boolean)) { 47 | throw new IllegalArgumentException(String.format("Field %s must be a boolean value", fieldName)); 48 | } 49 | return (boolean) input; 50 | } 51 | return false; 52 | } 53 | 54 | private ParseUtils() { 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/tasks/GenerateLocalextensions.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.tasks; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.IOException; 5 | import java.nio.charset.StandardCharsets; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | import java.nio.file.StandardOpenOption; 9 | import java.time.Instant; 10 | import java.util.Set; 11 | 12 | import org.gradle.api.DefaultTask; 13 | import org.gradle.api.file.RegularFileProperty; 14 | import org.gradle.api.provider.SetProperty; 15 | import org.gradle.api.tasks.Input; 16 | import org.gradle.api.tasks.OutputFile; 17 | import org.gradle.api.tasks.TaskAction; 18 | 19 | public abstract class GenerateLocalextensions extends DefaultTask { 20 | 21 | private static final String START = """ 22 | 23 | 24 | 25 | """ 26 | .stripIndent(); 27 | private static final String END = """ 28 | 29 | "; 30 | """.stripIndent(); 31 | 32 | private static final String EXTENSION = " \n"; 33 | 34 | @TaskAction 35 | public void generateLocalextensions() throws IOException { 36 | Path target = getTarget().get().getAsFile().toPath(); 37 | Set extensions = getCloudExtensions().get(); 38 | try (BufferedWriter writer = Files.newBufferedWriter(target, StandardCharsets.UTF_8, StandardOpenOption.CREATE, 39 | StandardOpenOption.TRUNCATE_EXISTING)) { 40 | writer.write(START); 41 | writer.write(String.format("\n\n\n", getName(), Instant.now())); 42 | for (String extension : extensions) { 43 | writer.write(String.format(EXTENSION, extension)); 44 | } 45 | writer.write(END); 46 | } 47 | } 48 | 49 | @OutputFile 50 | public abstract RegularFileProperty getTarget(); 51 | 52 | @Input 53 | public abstract SetProperty getCloudExtensions(); 54 | } 55 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/tasks/PatchLocalExtensions.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.tasks; 2 | 3 | import java.io.File; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | 7 | import javax.xml.parsers.DocumentBuilder; 8 | import javax.xml.parsers.DocumentBuilderFactory; 9 | import javax.xml.transform.Transformer; 10 | import javax.xml.transform.TransformerFactory; 11 | import javax.xml.transform.dom.DOMSource; 12 | import javax.xml.transform.stream.StreamResult; 13 | 14 | import org.gradle.api.DefaultTask; 15 | import org.gradle.api.GradleException; 16 | import org.gradle.api.file.RegularFileProperty; 17 | import org.gradle.api.provider.Property; 18 | import org.gradle.api.tasks.Input; 19 | import org.gradle.api.tasks.InputFiles; 20 | import org.gradle.api.tasks.TaskAction; 21 | import org.w3c.dom.*; 22 | 23 | public abstract class PatchLocalExtensions extends DefaultTask { 24 | 25 | @TaskAction 26 | public void addCepLoadDir() throws Exception { 27 | 28 | Path hybrisBin = getProject().getRootDir().toPath().resolve(Path.of("hybris", "bin")); 29 | Path cepPath = Path.of(getCepFolder().get()); 30 | 31 | Path relativize = hybrisBin.relativize(cepPath); 32 | String cepPathString = "${HYBRIS_BIN_DIR}" + File.separator + relativize; 33 | 34 | patchLocalExtensions(cepPathString); 35 | } 36 | 37 | private void patchLocalExtensions(String cepPathString) throws Exception { 38 | Path localExtensions = getTarget().get().getAsFile().toPath(); 39 | if (!(Files.exists(localExtensions))) { 40 | getLogger().debug("{} not found; nothing to do", localExtensions); 41 | return; 42 | } 43 | 44 | DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); 45 | 46 | DocumentBuilder builder = domFactory.newDocumentBuilder(); 47 | Document doc = builder.parse(localExtensions.toFile()); 48 | 49 | NodeList paths = doc.getDocumentElement().getElementsByTagName("path"); 50 | 51 | int platformIndex = -1; 52 | int cepIndex = -1; 53 | for (int i = 0; i < paths.getLength(); i++) { 54 | Node item = paths.item(i); 55 | String dir = item.getAttributes().getNamedItem("dir").getNodeValue(); 56 | if ("${HYBRIS_BIN_DIR}".equalsIgnoreCase(dir)) { 57 | platformIndex = i; 58 | } 59 | if (cepPathString.equalsIgnoreCase(dir)) { 60 | cepIndex = i; 61 | } 62 | } 63 | if (cepIndex == -1) { 64 | getLogger().lifecycle("cloud extension pack not configured in localextensions.xml, patching..."); 65 | Comment comment = doc.createComment("generated by commerce-gradle-plugin"); 66 | paths.item(0).getParentNode().insertBefore(comment, paths.item(0)); 67 | Text textNode = doc.createTextNode("\n"); 68 | paths.item(0).getParentNode().insertBefore(textNode, paths.item(0)); 69 | Element cepPath = doc.createElement("path"); 70 | cepPath.setAttribute("dir", cepPathString); 71 | cepPath.setAttribute("autoload", "false"); 72 | paths.item(0).getParentNode().insertBefore(cepPath, paths.item(0)); 73 | textNode = doc.createTextNode("\n"); 74 | paths.item(0).getParentNode().insertBefore(textNode, paths.item(1)); 75 | textNode = doc.createTextNode("\n"); 76 | paths.item(0).getParentNode().insertBefore(textNode, paths.item(1)); 77 | 78 | } else if (cepIndex > platformIndex) { 79 | throw new GradleException(String.format("%s: must be before ", 80 | getProject().getRootDir().toPath().relativize(localExtensions), cepPathString)); 81 | } 82 | 83 | Transformer transformer = TransformerFactory.newInstance().newTransformer(); 84 | StreamResult result = new StreamResult(localExtensions.toFile()); 85 | DOMSource source = new DOMSource(doc); 86 | transformer.transform(source, result); 87 | } 88 | 89 | @InputFiles 90 | public abstract RegularFileProperty getTarget(); 91 | 92 | @Input 93 | public abstract Property getCepFolder(); 94 | } 95 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/tasks/ValidateManifest.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.tasks; 2 | 3 | import static mpern.sap.commerce.commons.Constants.CCV2_EXTENSION; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.ArrayList; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | 11 | import javax.inject.Inject; 12 | 13 | import org.gradle.api.DefaultTask; 14 | import org.gradle.api.InvalidUserDataException; 15 | import org.gradle.api.tasks.TaskAction; 16 | import org.gradle.internal.logging.text.StyledTextOutput; 17 | import org.gradle.internal.logging.text.StyledTextOutputFactory; 18 | 19 | import mpern.sap.commerce.ccv2.CCv2Extension; 20 | import mpern.sap.commerce.ccv2.validation.Error; 21 | import mpern.sap.commerce.ccv2.validation.Level; 22 | import mpern.sap.commerce.ccv2.validation.Validator; 23 | import mpern.sap.commerce.ccv2.validation.impl.*; 24 | 25 | public class ValidateManifest extends DefaultTask { 26 | 27 | private final StyledTextOutputFactory styledTextOutputFactory; 28 | 29 | @Inject 30 | public ValidateManifest(StyledTextOutputFactory styledTextOutputFactory) { 31 | this.styledTextOutputFactory = styledTextOutputFactory; 32 | } 33 | 34 | @TaskAction 35 | public void validateManifest() throws Exception { 36 | Path projectDir = getProject().getProjectDir().toPath(); 37 | 38 | List validators = new ArrayList<>(); 39 | 40 | validators.add(new AspectValidator()); 41 | validators.add(new PropertyValidator()); 42 | validators.add(new UseConfigValidator(projectDir)); 43 | ManifestExtensionsResolver resolver = new ManifestExtensionsResolver(projectDir); 44 | validators.add(new CloudHotfolderValidator(projectDir, resolver)); 45 | validators.add(new MediaConversionValidator(resolver)); 46 | validators.add(new WebrootValidator(projectDir)); 47 | validators.add(new SolrVersionValidator()); 48 | validators.add(new IntExtPackValidator()); 49 | 50 | boolean deepInspection = false; 51 | if (Files.exists(projectDir.resolve("hybris/bin/platform"))) { 52 | deepInspection = true; 53 | validators.add(new AddonValidator(resolver)); 54 | validators.add(new AspectWebappValidator(resolver)); 55 | } 56 | 57 | CCv2Extension extension = (CCv2Extension) getProject().getExtensions().getByName(CCV2_EXTENSION); 58 | 59 | List errors = new ArrayList<>(); 60 | for (Validator validator : validators) { 61 | errors.addAll(validator.validate(extension.getManifest())); 62 | } 63 | errors.sort(Comparator.comparing(Error::getLevel).thenComparing(Error::getLocation)); 64 | 65 | StyledTextOutput statusOut = styledTextOutputFactory.create(ValidateManifest.class); 66 | statusOut.withStyle(StyledTextOutput.Style.Header) 67 | .println("--------------------- Manifest Validation Results ----------------------\n"); 68 | if (errors.isEmpty()) { 69 | statusOut.withStyle(StyledTextOutput.Style.Success).println("No issues detected"); 70 | } 71 | for (Error error : errors) { 72 | switch (error.level) { 73 | case WARNING: 74 | statusOut.withStyle(StyledTextOutput.Style.Description).format("%s %s @ %s\n", error.level, error.code, 75 | error.location); 76 | statusOut.withStyle(StyledTextOutput.Style.Description).println(error.message); 77 | statusOut.formatln(toLink(error.code)); 78 | statusOut.println(); 79 | break; 80 | case ERROR: 81 | statusOut.withStyle(StyledTextOutput.Style.FailureHeader).format("%s %s @ %s\n", error.level, 82 | error.code, error.location); 83 | statusOut.withStyle(StyledTextOutput.Style.Failure).println(error.message); 84 | statusOut.formatln(toLink(error.code)); 85 | statusOut.println(); 86 | break; 87 | } 88 | } 89 | statusOut.withStyle(StyledTextOutput.Style.Header) 90 | .println("------------------------------------------------------------------------"); 91 | if (!deepInspection) { 92 | statusOut.withStyle(StyledTextOutput.Style.Info) 93 | .println("hybris/bin/platform not available. Cannot perform deep inspection."); 94 | } 95 | long numWarnings = errors.stream().filter(e -> Level.WARNING == e.level).count(); 96 | long numErrors = errors.stream().filter(e -> Level.ERROR == e.level).count(); 97 | statusOut.withStyle(StyledTextOutput.Style.Header).format("Errors: "); 98 | if (numErrors > 0) { 99 | statusOut.withStyle(StyledTextOutput.Style.FailureHeader).format("%d\n", numErrors); 100 | } else { 101 | statusOut.withStyle(StyledTextOutput.Style.SuccessHeader).format("%d\n", numErrors); 102 | } 103 | statusOut.withStyle(StyledTextOutput.Style.Header).append("Warnings: "); 104 | if (numWarnings > 0) { 105 | statusOut.withStyle(StyledTextOutput.Style.Description).format("%d\n", numWarnings); 106 | } else { 107 | statusOut.withStyle(StyledTextOutput.Style.SuccessHeader).format("%d\n", numWarnings); 108 | } 109 | if (numErrors > 0) { 110 | throw new InvalidUserDataException(String.format("Found %d errors in manifest.json", numErrors)); 111 | } 112 | } 113 | 114 | private String toLink(String code) { 115 | code = code.toLowerCase(); 116 | code = code.replace("-", ""); 117 | return "https://github.com/SAP/commerce-gradle-plugin/blob/master/docs/ccv2-validation.md#" + code; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/Error.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | import java.util.StringJoiner; 4 | 5 | public class Error { 6 | public final Level level; 7 | public final String location; 8 | public final String message; 9 | public final String code; 10 | 11 | private Error(Level level, String location, String message, String code) { 12 | this.level = level; 13 | this.location = location; 14 | this.message = message; 15 | this.code = code; 16 | } 17 | 18 | public Level getLevel() { 19 | return level; 20 | } 21 | 22 | public String getLocation() { 23 | return location; 24 | } 25 | 26 | public String getMessage() { 27 | return message; 28 | } 29 | 30 | public String getCode() { 31 | return code; 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return new StringJoiner(", ", Error.class.getSimpleName() + "[", "]").add("level=" + level) 37 | .add("location='" + location + "'").add("message='" + message + "'").add("code='" + code + "'") 38 | .toString(); 39 | } 40 | 41 | public static class Builder { 42 | private Level level = Level.ERROR; 43 | private String location; 44 | private String message; 45 | private String code; 46 | 47 | public Builder setLevel(Level level) { 48 | this.level = level; 49 | return this; 50 | } 51 | 52 | public Builder setLocation(String format, Object... args) { 53 | this.location = String.format(format, args); 54 | return this; 55 | } 56 | 57 | public Builder setMessage(String format, Object... args) { 58 | this.message = String.format(format, args); 59 | return this; 60 | } 61 | 62 | public Builder setCode(String code) { 63 | this.code = code; 64 | return this; 65 | } 66 | 67 | public Error createError() { 68 | return new Error(level, location, message, code); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/ExtensionValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import mpern.sap.commerce.ccv2.model.Manifest; 7 | 8 | public abstract class ExtensionValidator implements Validator { 9 | 10 | protected final ExtensionsResolver resolver; 11 | 12 | public ExtensionValidator(ExtensionsResolver extensionsResolver) { 13 | this.resolver = extensionsResolver; 14 | } 15 | 16 | @Override 17 | public List validate(Manifest manifest) throws Exception { 18 | ExtensionsResolver.Result result = resolver.determineEffectiveExtensions(manifest); 19 | if (result == ExtensionsResolver.Result.NO_RESULT) { 20 | return Collections.emptyList(); 21 | } else { 22 | return validateWithExtensions(manifest, result); 23 | } 24 | } 25 | 26 | protected abstract List validateWithExtensions(Manifest manifest, 27 | ExtensionsResolver.Result effectiveExtensions); 28 | 29 | protected String formatLocations(List locations) { 30 | return "Extensions loaded from:\n- " + String.join("\n- ", locations); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/ExtensionsResolver.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import groovy.lang.Tuple2; 8 | 9 | import mpern.sap.commerce.build.util.Extension; 10 | import mpern.sap.commerce.ccv2.model.Manifest; 11 | 12 | /** 13 | * Resolves information about extensions declared in the build manifest. 14 | */ 15 | public interface ExtensionsResolver { 16 | 17 | /** 18 | * Resolves the detailed information about the extensions configured in the 19 | * manifest. 20 | * 21 | * @param manifest the manifest being used 22 | * @return the operation result 23 | */ 24 | Result determineEffectiveExtensions(Manifest manifest); 25 | 26 | /** 27 | * Gets the names of the configured extensions from the manifest. 28 | * 29 | * @param manifest the manifest to get the information from 30 | * @return first element in tuple is the set of extension names that are 31 | * configured, second element contains the list of locations from where 32 | * the extension names are obtained 33 | */ 34 | Tuple2, List> listAllConfiguredExtensions(Manifest manifest); 35 | 36 | class Result { 37 | public static final Result NO_RESULT = new Result(Collections.emptyList(), Collections.emptyList()); 38 | public final List extensions; 39 | public final List locations; 40 | 41 | public Result(List extensions, List locations) { 42 | this.extensions = Collections.unmodifiableList(extensions); 43 | this.locations = Collections.unmodifiableList(locations); 44 | } 45 | 46 | public List getExtensions() { 47 | return extensions; 48 | } 49 | 50 | public List getLocations() { 51 | return locations; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/Level.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | public enum Level { 4 | WARNING, ERROR 5 | } 6 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/ValidationUtils.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | import java.util.regex.Matcher; 9 | import java.util.regex.Pattern; 10 | 11 | import groovy.lang.Tuple2; 12 | 13 | public class ValidationUtils { 14 | 15 | private static final Pattern VALID_PATH = Pattern.compile("[.a-zA-Z/_\\-0-9]+"); 16 | public static final String ERROR = "E-009"; 17 | 18 | public static Tuple2> validateAndNormalizePath(Path projectRoot, String location, String input) { 19 | Matcher matcher = VALID_PATH.matcher(input); 20 | if (!matcher.matches()) { 21 | return new Tuple2<>(null, Collections.singletonList(new Error.Builder().setLocation(location).setMessage( 22 | "Location `%s` is invalid. Must be a plain Unix-style path without any shell expansion, non-ASCII characters etc.", 23 | input).setCode(ERROR).createError())); 24 | } else { 25 | List errors = new ArrayList<>(); 26 | Path inputPath = Path.of(input); 27 | if (inputPath.isAbsolute() || inputPath.startsWith("/")) { 28 | errors.add(new Error.Builder().setLocation(location) 29 | .setMessage("Location `%s` is absolute (starts with `/`).", input).setCode(ERROR) 30 | .createError()); 31 | } 32 | for (Path component : inputPath) { 33 | if (".".equals(component.toString()) || "..".equals(component.toString())) { 34 | errors.add(new Error.Builder().setLocation(location) 35 | .setMessage("Location `%s` is relative (uses `.` or `..`).", input).setCode(ERROR) 36 | .createError()); 37 | } 38 | } 39 | Path resolved = null; 40 | if (errors.isEmpty()) { 41 | resolved = projectRoot.resolve(inputPath); 42 | if (!Files.exists(resolved)) { 43 | errors.add(new Error.Builder().setLocation(location).setMessage("Location `%s` not found", input) 44 | .setCode(ERROR).createError()); 45 | resolved = null; 46 | } 47 | } 48 | return new Tuple2<>(resolved, errors); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/Validator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation; 2 | 3 | import java.util.List; 4 | 5 | import mpern.sap.commerce.ccv2.model.Manifest; 6 | 7 | public interface Validator { 8 | 9 | List validate(Manifest manifest) throws Exception; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/AddonValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Set; 7 | import java.util.stream.Collectors; 8 | 9 | import mpern.sap.commerce.ccv2.model.Addon; 10 | import mpern.sap.commerce.ccv2.model.Manifest; 11 | import mpern.sap.commerce.ccv2.validation.Error; 12 | import mpern.sap.commerce.ccv2.validation.ExtensionValidator; 13 | import mpern.sap.commerce.ccv2.validation.ExtensionsResolver; 14 | 15 | public class AddonValidator extends ExtensionValidator { 16 | 17 | public AddonValidator(ExtensionsResolver extensionsResolver) { 18 | super(extensionsResolver); 19 | } 20 | 21 | @Override 22 | protected List validateWithExtensions(Manifest manifest, ExtensionsResolver.Result effectiveExtensions) { 23 | Set extensionNames = effectiveExtensions.extensions.stream().map(e -> e.name) 24 | .collect(Collectors.toUnmodifiableSet()); 25 | List errors = new ArrayList<>(); 26 | for (int i = 0; i < manifest.storefrontAddons.size(); i++) { 27 | Addon addon = manifest.storefrontAddons.get(i); 28 | List storefronts = new ArrayList<>(addon.storefronts); 29 | if (!addon.storefront.isEmpty()) { 30 | storefronts.addAll(Arrays.asList(addon.storefront.split(","))); 31 | } 32 | for (String s : storefronts) { 33 | if (!extensionNames.contains(s)) { 34 | errors.add( 35 | new Error.Builder().setLocation("storefrontAddons[%d]", i) 36 | .setMessage("Storefront extension `%s` not available.\n%s", s, 37 | formatLocations(effectiveExtensions.locations)) 38 | .setCode("E-001").createError()); 39 | } 40 | } 41 | List addons = new ArrayList<>(addon.addons); 42 | if (!addon.addon.isEmpty()) { 43 | addons.addAll(Arrays.asList(addon.addon.split(","))); 44 | } 45 | for (String a : addons) { 46 | if (!extensionNames.contains(a)) { 47 | errors.add( 48 | new Error.Builder().setLocation("storefrontAddons[%d]", i) 49 | .setMessage("Addon `%s` not available.\n%s", a, 50 | formatLocations(effectiveExtensions.locations)) 51 | .setCode("E-001").createError()); 52 | } 53 | } 54 | } 55 | return errors; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/AspectValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import static mpern.sap.commerce.ccv2.model.Aspect.ADMIN_ASPECT; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import mpern.sap.commerce.ccv2.model.Aspect; 11 | import mpern.sap.commerce.ccv2.model.Manifest; 12 | import mpern.sap.commerce.ccv2.model.Webapp; 13 | import mpern.sap.commerce.ccv2.validation.Error; 14 | import mpern.sap.commerce.ccv2.validation.Validator; 15 | 16 | public class AspectValidator implements Validator { 17 | 18 | public static final String WEBAPP_LOCATION = "aspects[?name == '%s'].webapps[%d]"; 19 | 20 | @Override 21 | public List validate(Manifest manifest) throws Exception { 22 | List errors = new ArrayList<>(); 23 | Map seenAspects = new HashMap<>(); 24 | for (int aspectIndex = 0; aspectIndex < manifest.aspects.size(); aspectIndex++) { 25 | Aspect aspect = manifest.aspects.get(aspectIndex); 26 | if (!Aspect.ALLOWED_ASPECTS.contains(aspect.name)) { 27 | errors.add(new Error.Builder().setLocation("aspects[?name == '%s']", aspect.name) 28 | .setMessage("Aspect `%s` not supported", aspect.name).setCode("E-002").createError()); 29 | } else { 30 | Integer previous = seenAspects.put(aspect.name, aspectIndex); 31 | if (previous != null) { 32 | errors.add(new Error.Builder().setLocation("aspects[%d]", aspectIndex) 33 | .setMessage("Aspect `%s` configured more than once. Previous location: `aspects[%d]", 34 | aspect.name, previous) 35 | .setCode("E-003").createError()); 36 | } 37 | errors.addAll(new SharedPropertyValidator(String.format("aspects[?name == '%s'].", aspect.name)) 38 | .validateProperties(aspect.properties)); 39 | if (ADMIN_ASPECT.equals(aspect.name)) { 40 | if (!aspect.webapps.isEmpty()) { 41 | errors.add(new Error.Builder().setLocation("aspects[?name == '%s']", aspect.name) 42 | .setMessage("Webapps not allowed for aspect `admin`").setCode("E-007").createError()); 43 | } 44 | } else { 45 | Map loadedExtensions = new HashMap<>(); 46 | Map webroots = new HashMap<>(); 47 | for (int j = 0; j < aspect.webapps.size(); j++) { 48 | Webapp w = aspect.webapps.get(j); 49 | previous = loadedExtensions.put(w.name, j); 50 | if (previous != null) { 51 | errors.add(new Error.Builder().setLocation(WEBAPP_LOCATION, aspect.name, j).setMessage( 52 | "Extension `%s` configured more than once. Previous location: `aspects[?name == '%s'].webapps[%d]`", 53 | w.name, aspect.name, previous).setCode("E-004").createError()); 54 | } 55 | previous = webroots.put(w.contextPath, j); 56 | if (previous != null) { 57 | errors.add(new Error.Builder().setLocation(WEBAPP_LOCATION, aspect.name, j).setMessage( 58 | "Context path `%s` configured more than once! Previous location: `aspects[?name == '%s'].webapps[%d]`", 59 | w.contextPath, aspect.name, previous).setCode("E-005").createError()); 60 | } 61 | if (!w.contextPath.isEmpty() && !w.contextPath.startsWith("/")) { 62 | errors.add(new Error.Builder().setLocation(WEBAPP_LOCATION, aspect.name, j) 63 | .setMessage("contextPath `%s` must start with `/`", w.contextPath).setCode("E-006") 64 | .createError()); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | return errors; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/AspectWebappValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import static mpern.sap.commerce.ccv2.model.Aspect.ADMIN_ASPECT; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Set; 8 | import java.util.stream.Collectors; 9 | 10 | import mpern.sap.commerce.ccv2.model.Aspect; 11 | import mpern.sap.commerce.ccv2.model.Manifest; 12 | import mpern.sap.commerce.ccv2.model.Webapp; 13 | import mpern.sap.commerce.ccv2.validation.Error; 14 | import mpern.sap.commerce.ccv2.validation.ExtensionValidator; 15 | import mpern.sap.commerce.ccv2.validation.ExtensionsResolver; 16 | 17 | public class AspectWebappValidator extends ExtensionValidator { 18 | public AspectWebappValidator(ExtensionsResolver extensionsResolver) { 19 | super(extensionsResolver); 20 | } 21 | 22 | @Override 23 | protected List validateWithExtensions(Manifest manifest, ExtensionsResolver.Result effectiveExtensions) { 24 | List errors = new ArrayList<>(); 25 | Set extensionNames = effectiveExtensions.extensions.stream().map(e -> e.name) 26 | .collect(Collectors.toUnmodifiableSet()); 27 | for (int i = 0; i < manifest.aspects.size(); i++) { 28 | Aspect aspect = manifest.aspects.get(i); 29 | if (ADMIN_ASPECT.equals(aspect.name)) { 30 | continue; 31 | } 32 | for (int j = 0; j < aspect.webapps.size(); j++) { 33 | Webapp w = aspect.webapps.get(j); 34 | // extension does not exist / not loaded 35 | if (!extensionNames.contains(w.name)) { 36 | errors.add( 37 | new Error.Builder().setLocation("aspects[?name == '%s'].webapps[%d]", aspect.name, i) 38 | .setMessage("Extension `%s` not available.\n%s", w.name, 39 | formatLocations(effectiveExtensions.locations)) 40 | .setCode("E-001").createError()); 41 | } 42 | } 43 | 44 | } 45 | return errors; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/CloudHotfolderValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import static mpern.sap.commerce.ccv2.model.Aspect.BACKGROUND_ASPECT; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.util.*; 10 | 11 | import groovy.lang.Tuple2; 12 | 13 | import mpern.sap.commerce.ccv2.model.Manifest; 14 | import mpern.sap.commerce.ccv2.validation.*; 15 | import mpern.sap.commerce.ccv2.validation.Error; 16 | 17 | public class CloudHotfolderValidator implements Validator { 18 | public static final String HOTFOLDER_EXTENSION = "azurecloudhotfolder"; 19 | 20 | private final ExtensionsResolver resolver; 21 | private final Path projectRoot; 22 | 23 | public CloudHotfolderValidator(Path projectRoot, ExtensionsResolver resolver) { 24 | this.projectRoot = projectRoot; 25 | this.resolver = resolver; 26 | } 27 | 28 | @Override 29 | public List validate(Manifest manifest) throws Exception { 30 | Tuple2, List> listing = resolver.listAllConfiguredExtensions(manifest); 31 | if (listing.getV1().contains(HOTFOLDER_EXTENSION)) { 32 | Map effectiveProperties = new HashMap<>(); 33 | manifest.aspects.stream().filter(a -> BACKGROUND_ASPECT.equals(a.name)).flatMap(a -> a.properties.stream()) 34 | .forEach(p -> effectiveProperties.put(p.key, p.value)); 35 | 36 | manifest.useConfig.properties.stream().filter(p -> BACKGROUND_ASPECT.equals(p.aspect)) 37 | .map(p -> ValidationUtils.validateAndNormalizePath(this.projectRoot, "", p.location)) 38 | .filter(p -> p.getV1() != null).map(Tuple2::getV1).forEach(p -> { 39 | try (InputStream stream = Files.newInputStream(p)) { 40 | Properties props = new Properties(); 41 | props.load(stream); 42 | props.forEach((k, v) -> effectiveProperties.put((String) k, (String) v)); 43 | } catch (IOException e) { 44 | // ignore 45 | } 46 | }); 47 | String nodeGroups = effectiveProperties.get("cluster.node.groups"); 48 | if (nodeGroups == null || !nodeGroups.contains("integration") 49 | || !nodeGroups.contains("yHotfolderCandidate")) { 50 | return Collections.singletonList(new Error.Builder().setLevel(Level.WARNING) 51 | .setLocation("aspects[?name == '%s']", BACKGROUND_ASPECT) 52 | .setMessage( 53 | "Cloud hotfolders enabled (extension `azurecloudhotfolder`), but `backgroundProcessing` nodes are not configured to process the imports (via property `cluster.node.groups`).\nPlease updated your properties according to the documentation.") 54 | .setCode("W-003").createError()); 55 | } 56 | } 57 | return Collections.emptyList(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/IntExtPackValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import java.util.*; 4 | 5 | import mpern.sap.commerce.build.util.Version; 6 | import mpern.sap.commerce.ccv2.model.ExtensionPack; 7 | import mpern.sap.commerce.ccv2.model.Manifest; 8 | import mpern.sap.commerce.ccv2.validation.Error; 9 | import mpern.sap.commerce.ccv2.validation.Validator; 10 | 11 | public class IntExtPackValidator implements Validator { 12 | 13 | private static final String PACK = "hybris-commerce-integrations"; 14 | 15 | private static final Map PLATFORM_TO_PACK; 16 | 17 | static { 18 | PLATFORM_TO_PACK = new HashMap<>(); 19 | PLATFORM_TO_PACK.put(Version.parseVersion("2005"), Version.parseVersion("2005")); 20 | PLATFORM_TO_PACK.put(Version.parseVersion("2011"), Version.parseVersion("2102")); 21 | PLATFORM_TO_PACK.put(Version.parseVersion("2105"), Version.parseVersion("2108")); 22 | PLATFORM_TO_PACK.put(Version.parseVersion("2205"), Version.parseVersion("2205")); 23 | PLATFORM_TO_PACK.put(Version.parseVersion("2211"), Version.parseVersion("2211")); 24 | } 25 | 26 | @Override 27 | public List validate(Manifest manifest) throws Exception { 28 | if (manifest.extensionPacks.isEmpty()) { 29 | return Collections.emptyList(); 30 | } 31 | List allErrors = new ArrayList<>(); 32 | List extensionPacks = manifest.extensionPacks; 33 | for (int i = 0, extensionPacksSize = extensionPacks.size(); i < extensionPacksSize; i++) { 34 | ExtensionPack extensionPack = extensionPacks.get(i); 35 | String name; 36 | String versionString; 37 | if (extensionPack.name.isEmpty()) { 38 | final String[] split = extensionPack.artifact.split(":"); 39 | name = split[1].trim(); 40 | versionString = split[2].trim(); 41 | } else { 42 | name = extensionPack.name; 43 | versionString = extensionPack.version; 44 | } 45 | if (PACK.equals(name)) { 46 | Version platform = Version.parseVersion(manifest.commerceSuiteVersion).withoutPatch(); 47 | Version pack = Version.parseVersion(versionString).withoutPatch(); 48 | Version expected = PLATFORM_TO_PACK.get(platform); 49 | List errors = new ArrayList<>(); 50 | if (!pack.equals(PLATFORM_TO_PACK.get(platform))) { 51 | String message; 52 | if (expected == null) { 53 | message = String.format("No Integration Extension Pack available for SAP Commerce %s", 54 | manifest.commerceSuiteVersion); 55 | } else { 56 | message = String.format("Integration Extension Pack %s is not compatible with SAP Commerce %s", 57 | extensionPack.version, manifest.commerceSuiteVersion); 58 | } 59 | errors.add(new Error.Builder().setLocation("extensionPacks[%d]", i).setCode("E-019") 60 | .setMessage(message).createError()); 61 | } 62 | final Version version = Version.parseVersion(versionString); 63 | if (version.getPatch() == Version.UNDEFINED_PART) { 64 | errors.add(new Error.Builder().setLocation("extensionPacks[%d]", i).setCode("E-019").setMessage( 65 | "Integration Extension Pack version %s is not fully qualified (does not include patch)", 66 | version).createError()); 67 | } 68 | allErrors.addAll(errors); 69 | } 70 | } 71 | return allErrors; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/ManifestExtensionsResolver.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import static mpern.sap.commerce.ccv2.validation.ValidationUtils.validateAndNormalizePath; 4 | 5 | import java.nio.file.Path; 6 | import java.util.*; 7 | 8 | import groovy.lang.Tuple2; 9 | 10 | import mpern.sap.commerce.build.extensioninfo.ExtensionXmlUtil; 11 | import mpern.sap.commerce.build.util.Extension; 12 | import mpern.sap.commerce.build.util.PlatformResolver; 13 | import mpern.sap.commerce.ccv2.model.Manifest; 14 | import mpern.sap.commerce.ccv2.validation.Error; 15 | import mpern.sap.commerce.ccv2.validation.ExtensionsResolver; 16 | 17 | /** 18 | * Resolves extension information from the build manifest, using the information 19 | * from the de.hybris.bootstrap.config.PlatformConfig and 20 | * de.hybris.bootstrap.config.ExtensionInfo. 21 | */ 22 | public class ManifestExtensionsResolver implements ExtensionsResolver { 23 | 24 | public static Set CLOUD_ONLY_EXTENSIONS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( 25 | // https://help.sap.com/viewer/0fa6bcf4736c46f78c248512391eb467/LATEST/en-US/b13c673497674994a7f243e3225af9b3.html 26 | "modeltacceleratorservices", 27 | // https://help.sap.com/viewer/b2f400d4c0414461a4bb7e115dccd779/LATEST/en-US/784f9480cf064d3b81af9cad5739fecc.html 28 | "modelt", 29 | // https://help.sap.com/viewer/403d43bf9c564f5a985913d1fbfbf8d7/LATEST/en-US/fba094343e624aae8f041d0170046355.html 30 | "cloudmediaconversion"))); 31 | private final Path projectRoot; 32 | 33 | public ManifestExtensionsResolver(Path projectRoot) { 34 | this.projectRoot = projectRoot; 35 | } 36 | 37 | @Override 38 | public Result determineEffectiveExtensions(Manifest manifest) { 39 | Tuple2, List> listing = listAllConfiguredExtensions(manifest); 40 | Set extensionNames = listing.getV1(); 41 | Set configuredCloudOnly = new LinkedHashSet<>(extensionNames); 42 | configuredCloudOnly.retainAll(CLOUD_ONLY_EXTENSIONS); 43 | extensionNames.removeAll(CLOUD_ONLY_EXTENSIONS); 44 | // try to load 45 | try { 46 | PlatformResolver resolver = new PlatformResolver(this.projectRoot.resolve(Path.of("hybris/bin/platform"))); 47 | List extensions = new ArrayList<>(resolver.loadListOfExtensions(extensionNames)); 48 | for (String cloudOnly : configuredCloudOnly) { 49 | extensions.add(new Extension(cloudOnly, Path.of("dummy", cloudOnly))); 50 | } 51 | return new Result(extensions, listing.getV2()); 52 | } catch (Exception e) { 53 | // ignore 54 | } 55 | return Result.NO_RESULT; 56 | } 57 | 58 | @Override 59 | public Tuple2, List> listAllConfiguredExtensions(Manifest manifest) { 60 | Set extensionNames = new LinkedHashSet<>(); 61 | List locations = new ArrayList<>(); 62 | String extensionsLocation = manifest.useConfig.extensions.location; 63 | if (!extensionsLocation.isEmpty()) { 64 | Tuple2> xmlFile = validateAndNormalizePath(this.projectRoot, "", extensionsLocation); 65 | if (xmlFile.getV1() != null) { 66 | locations.add(String.format("useConfig.extensions.location (%s)", extensionsLocation)); 67 | extensionNames 68 | .addAll(ExtensionXmlUtil.loadExtensionNamesFromLocalExtensionsXML(xmlFile.getV1().toFile())); 69 | extensionNames.removeAll(manifest.useConfig.extensions.exclude); 70 | } 71 | } 72 | if (!manifest.extensions.isEmpty()) { 73 | locations.add("extensions"); 74 | extensionNames.addAll(manifest.extensions); 75 | } 76 | return new Tuple2<>(extensionNames, locations); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/MediaConversionValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.Set; 6 | 7 | import groovy.lang.Tuple2; 8 | 9 | import mpern.sap.commerce.ccv2.model.Manifest; 10 | import mpern.sap.commerce.ccv2.validation.Error; 11 | import mpern.sap.commerce.ccv2.validation.ExtensionsResolver; 12 | import mpern.sap.commerce.ccv2.validation.Validator; 13 | 14 | public class MediaConversionValidator implements Validator { 15 | private static final String CONVERSION_EXTENSION = "cloudmediaconversion"; 16 | 17 | private final ExtensionsResolver resolver; 18 | 19 | public MediaConversionValidator(ExtensionsResolver resolver) { 20 | this.resolver = resolver; 21 | } 22 | 23 | @Override 24 | public List validate(Manifest manifest) throws Exception { 25 | Tuple2, List> result = resolver.listAllConfiguredExtensions(manifest); 26 | boolean extensionFound = result.getV1().contains(CONVERSION_EXTENSION); 27 | boolean conversionEnabled = manifest.enableImageProcessingService; 28 | 29 | if (extensionFound && !conversionEnabled) { 30 | return Collections.singletonList(new Error.Builder().setLocation("enableImageProcessingService") 31 | .setMessage("Extension `%s` configured, but image processing service is not enabled.", 32 | CONVERSION_EXTENSION) 33 | .setCode("E-016").createError()); 34 | } else if (!extensionFound && conversionEnabled) { 35 | return Collections.singletonList(new Error.Builder().setLocation("enableImageProcessingService") 36 | .setMessage("Image processing service is enabled, but extension `%s` not configured", 37 | CONVERSION_EXTENSION) 38 | .setCode("E-016").createError()); 39 | } else { 40 | return Collections.emptyList(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/PropertyValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import java.util.List; 4 | 5 | import mpern.sap.commerce.ccv2.model.Manifest; 6 | import mpern.sap.commerce.ccv2.validation.Error; 7 | import mpern.sap.commerce.ccv2.validation.Validator; 8 | 9 | public class PropertyValidator implements Validator { 10 | @Override 11 | public List validate(Manifest manifest) throws Exception { 12 | return new SharedPropertyValidator("").validateProperties(manifest.properties); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/SharedPropertyValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import static mpern.sap.commerce.ccv2.model.Property.ALLOWED_PERSONAS; 4 | 5 | import java.util.*; 6 | 7 | import mpern.sap.commerce.ccv2.model.Property; 8 | import mpern.sap.commerce.ccv2.validation.Error; 9 | import mpern.sap.commerce.ccv2.validation.Level; 10 | 11 | public class SharedPropertyValidator { 12 | 13 | // https://help.sap.com/viewer/1be46286b36a4aa48205be5a96240672/LATEST/en-US/a30160b786b545959184898b51c737fa.html 14 | // @formatter:off 15 | public static final Set MANAGED_PROPERTIES = new HashSet<>(Arrays.asList( 16 | "db.customsessionsql", // this will break DB connections 17 | 18 | // from help page 19 | "db.url", 20 | "db.driver", 21 | "db.username", 22 | "db.password", 23 | "db.tableprefix", 24 | "media.read.dir", 25 | "media.replication.dirs", 26 | "mediaweb.webroot", 27 | "media.globalSettings.cloudAzureBlobStorageStrategy.connection", 28 | "media.globalSettings.cloudAzureBlobStorageStrategy.public.base.url", 29 | 30 | "clustermode", 31 | "cluster.id", 32 | "cluster.maxid", 33 | "cluster.broadcast.methods", 34 | "cluster.broadcast.method.udp.multicastaddress", 35 | "cluster.broadcast.method.udp.port", 36 | 37 | "dynatrace.enabled", 38 | "dynatrace.agentlib", 39 | "dynatrace.name", 40 | "dynatrace.server", 41 | "tomcat.generaloptions.dynatrace", 42 | 43 | "tomcat.generaloptions.jmxsettings", 44 | "tomcat.jmx.ports", 45 | "tomcat.jmx.server.port", 46 | 47 | "tomcat.http.port", 48 | "tomcat.ssl.port", 49 | "tomcat.ajp.port", 50 | "tomcat.ajp.secureport", 51 | "proxy.http.port", 52 | "proxy.ssl.port", 53 | 54 | "tomcat.generaloptions", 55 | "java.mem", 56 | "tomcat.generaloptions.jmxsettings", 57 | "tomcat.generaloptions.jvmsettings", 58 | "tomcat.generaloptions.dynatrace", 59 | "tomcat.generaloptions.GC", 60 | 61 | "log4j.threshold", 62 | 63 | "installed.tenants", 64 | "tenant.restart.on.connection.error", 65 | 66 | "regionalcache.entityregion.evictionpolicy", 67 | "regioncache.stats.enabled", 68 | "cms.cache.enabled", 69 | "regioncache.entityregion.size", 70 | 71 | "storefront.btg.enabled", 72 | "storefront.resourceBundle.cacheSeconds", 73 | "showStorefrontDebugInfo", 74 | "storefront.show.debug.info", 75 | "storefront.granule.enabled", 76 | "storefront.staticResourceFilter.response.header.Cache-Control", 77 | "addonfilter.active", 78 | "default.session.timeout", 79 | 80 | "solrserver.instances.default.autostart", 81 | 82 | "datahub.security.https.enabled", 83 | 84 | //backoffice 85 | // "spring.session.enabled", 86 | // "spring.session.hac.save", 87 | // "backofficesearch.cronjob.nodegroup", 88 | // "spring.session.hac.cookie.name", 89 | // "spring.session.hac.cookie.path", 90 | // "task.engine.exclusive.mode", 91 | // "cluster.node.groups", 92 | 93 | "multicountrysampledataaddon.import.active" 94 | )); 95 | 96 | public static final Set BACKOFFICE_MANAGED_PROPERTIES = new HashSet<>(Arrays.asList( 97 | "spring.session.enabled", 98 | "spring.session.hac.save", 99 | "backofficesearch.cronjob.nodegroup", 100 | "spring.session.hac.cookie.name", 101 | "spring.session.hac.cookie.path", 102 | "task.engine.exclusive.mode", 103 | "cluster.node.groups" 104 | )); 105 | // @formatter:on 106 | 107 | private final String locationPrefix; 108 | 109 | public SharedPropertyValidator(String locationPrefix) { 110 | this.locationPrefix = locationPrefix; 111 | } 112 | 113 | public List validateProperties(Collection properties) { 114 | int index = 0; 115 | List errors = new ArrayList<>(); 116 | for (Property property : properties) { 117 | String location = String.format(locationPrefix + "properties[%d]", index); 118 | if (!ALLOWED_PERSONAS.contains(property.persona)) { 119 | errors.add(new Error.Builder().setLocation(location) 120 | .setMessage("Persona `%s` not supported", property.persona).setCode("E-008").createError()); 121 | } 122 | if (MANAGED_PROPERTIES.contains(property.key) 123 | || (location.contains("backoffice") && BACKOFFICE_MANAGED_PROPERTIES.contains(property.key))) { 124 | errors.add(new Error.Builder().setLevel(Level.WARNING).setLocation(location) 125 | .setMessage("Property `%s` is a managed property. Are you sure you need to modify it?", 126 | property.key) 127 | .setCode("W-001").createError()); 128 | } 129 | index += 1; 130 | } 131 | return errors; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/SolrVersionValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import mpern.sap.commerce.ccv2.model.Manifest; 9 | import mpern.sap.commerce.ccv2.validation.Error; 10 | import mpern.sap.commerce.ccv2.validation.Validator; 11 | 12 | public class SolrVersionValidator implements Validator { 13 | private static final Pattern SOLR_VERSION = Pattern.compile("^\\d+\\.\\d+$"); 14 | 15 | @Override 16 | public List validate(Manifest manifest) throws Exception { 17 | if (!manifest.solrVersion.isEmpty()) { 18 | final Matcher matcher = SOLR_VERSION.matcher(manifest.solrVersion); 19 | if (!matcher.matches()) { 20 | return Collections.singletonList(new Error.Builder().setLocation("solrVersion").setCode("E-018") 21 | .setMessage("Invalid Solr version").createError()); 22 | } 23 | } 24 | return Collections.emptyList(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cloud-plugin/src/main/java/mpern/sap/commerce/ccv2/validation/impl/WebrootValidator.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.ccv2.validation.impl; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.*; 8 | 9 | import groovy.lang.Tuple2; 10 | 11 | import mpern.sap.commerce.ccv2.model.Aspect; 12 | import mpern.sap.commerce.ccv2.model.Manifest; 13 | import mpern.sap.commerce.ccv2.model.Property; 14 | import mpern.sap.commerce.ccv2.model.useconfig.Properties; 15 | import mpern.sap.commerce.ccv2.validation.Error; 16 | import mpern.sap.commerce.ccv2.validation.ValidationUtils; 17 | import mpern.sap.commerce.ccv2.validation.Validator; 18 | 19 | public class WebrootValidator implements Validator { 20 | // shared/global - manifest 21 | // shared/global - file 22 | 23 | // aspect - webroots 24 | // aspect - properties 25 | // aspect - files 26 | 27 | // persona - props 28 | // persona - files 29 | // persona - aspect - props 30 | // persona - aspect - files 31 | 32 | private final Path projectRoot; 33 | 34 | public WebrootValidator(Path projectRoot) { 35 | this.projectRoot = projectRoot; 36 | } 37 | 38 | @Override 39 | public List validate(Manifest manifest) throws Exception { 40 | Map> occurences = new HashMap<>(); 41 | for (int i = 0; i < manifest.properties.size(); i++) { 42 | Property p = manifest.properties.get(i); 43 | String location = String.format("properties[%d]", i); 44 | checkProperty(p.key, location, occurences); 45 | } 46 | for (int i = 0; i < manifest.useConfig.properties.size(); i++) { 47 | Properties properties = manifest.useConfig.properties.get(i); 48 | Tuple2> result = ValidationUtils.validateAndNormalizePath(this.projectRoot, "", 49 | properties.location); 50 | if (result.getV1() != null) { 51 | try (InputStream stream = Files.newInputStream(result.getV1())) { 52 | java.util.Properties props = new java.util.Properties(); 53 | props.load(stream); 54 | String location = String.format("useConfig.properties[%d].location (%s)", i, properties.location); 55 | for (Map.Entry entry : props.entrySet()) { 56 | checkProperty((String) entry.getKey(), location, occurences); 57 | } 58 | } catch (IOException e) { 59 | // ignore 60 | } 61 | } 62 | } 63 | for (Aspect aspect : manifest.aspects) { 64 | for (int i = 0; i < aspect.properties.size(); i++) { 65 | Property p = aspect.properties.get(i); 66 | String location = String.format("aspects[?name == '%s'].properties[%d]", aspect.name, i); 67 | checkProperty(p.key, location, occurences); 68 | } 69 | } 70 | if (occurences.isEmpty()) { 71 | return Collections.emptyList(); 72 | } else { 73 | List errors = new ArrayList<>(); 74 | for (Map.Entry> errorEntry : occurences.entrySet()) { 75 | errors.add(new Error.Builder().setLocation(errorEntry.getKey()).setCode("E-017") 76 | .setMessage("Do not configure webroots in properties.\nFaulty properties:\n- " 77 | + String.join("\n -", errorEntry.getValue())) 78 | .createError()); 79 | } 80 | return errors; 81 | } 82 | } 83 | 84 | private void checkProperty(String key, String location, Map> occurences) { 85 | key = key.trim(); 86 | if (key.endsWith(".webroot")) { 87 | Set properties = occurences.computeIfAbsent(location, k -> new LinkedHashSet<>()); 88 | properties.add(key); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## How do I automate downloads from launchpad.support.sap.com? 4 | 5 | Using some revere engineering you can easily configure e.g. a Gradle download task ([Gradle plugin][dlplug]) 6 | to download the SAP Commerce artifacts for you. 7 | 8 | Let's say you want to download SAP Commerce 2011. 9 | 10 | 1. Search for the download on [launchpad.support.sap.com][launch] (`cx comm` is the search term that consistently works) 11 | 1. Navigate to the [download][down] 12 | ![Launchpad Search](images/launchpad-search.png) 13 | 1. Click on the icon in the "Related Info" column, click on "Content Info" 14 | 1. A new page opens 15 | ![Navigate to Content Info](images/launchpad-info.png) 16 | 1. Now, the fun part:\ 17 | Use the last part of the URL (after `object/`) of this page to construct the download URL: 18 | ``` 19 | https://launchpad.support.sap.com/#/softwarecenter/object/0020000000342432021 20 | ─────────┬───────── 21 | ┌──────────────────┘ 22 | v 23 | https://softwaredownloads.sap.com/file/0020000000342432021 24 | ``` 25 | 1. Voilà, you now have the download URL.\ 26 | Provide your S-User and its password using HTTP Basic authentication, and you are done. 27 | ![Object Info](images/launchpad-object.png) 28 | 29 | Here is how you would do it with `curl`: 30 | 31 | ```sh 32 | ❯ curl -L -b cookies.txt \ 33 | -u S1234567890 \ 34 | -o hybris-commerce-suite-2011.4.zip \ 35 | https://softwaredownloads.sap.com/file/0020000000342432021 36 | ... 37 | 100 1483M 100 1483M 0 0 6450k 0 0:03:55 0:03:55 --:--:-- 7130k 38 | ``` 39 | 40 | Another way to download via `curl`, if the approach above does not work, e.g. you are always redirected 41 | to a login page instead of the download (thank you @aepfli for discovering this!) 42 | 43 | ```sh 44 | # initialize download session, store required cookies in 'cookies.txt' 45 | ❯ curl -L -v -b cookies.txt -c cookie.txt \ 46 | -u "$SAP_USER_ID:$SAP_PASSWORD" \ 47 | -o "download.file" \ 48 | "https://origin.softwaredownloads.sap.com/tokengen/?file=${SAP_FILE_ID}" 49 | 50 | # start actual download 51 | curl -L -v -b cookies.txt \ 52 | -u "$SAP_USER_ID:$SAP_PASSWORD" \ 53 | -o "download.file" \ 54 | "https://softwaredownloads.sap.com/file/${SAP_FILE_ID}" 55 | 56 | ``` 57 | 58 | The field "Checksum" of the content info page is the sha-256 hash of the file (that comes in handy if you want to verify the download) 59 | 60 | ```sh 61 | ❯ shasum -a 256 -c <<< '5a96db9d91b5136d48f742ac0575981bbf11aadd79e2a45e357cdf9a8b3d434b *hybris-commerce-suite-2011.4.zip' 62 | hybris-commerce-suite-2011.4.zip: OK 63 | ``` 64 | 65 | ### SAP Universal ID 66 | 67 | When using SAP Universal ID the curl command may not work out of the box. Set a password for the S-User following the steps below: 68 | 69 | 1. Login and go to https://account.sap.com/manage/accounts 70 | 2. Select your S-User 71 | 3. Use `Reset Account Password` to set a password 72 | 73 | You can now use the S-User with the password in the procedure above! 74 | 75 | [launch]: https://launchpad.support.sap.com 76 | [down]: https://launchpad.support.sap.com/#/softwarecenter/template/products/%20_APP=00200682500000001943&_EVENT=DISPHIER&HEADER=Y&FUNCTIONBAR=N&EVENT=TREE&NE=NAVIGATE&ENR=73555000100200013787&V=MAINT&TA=ACTUAL&PAGE=SEARCH/CX%20COMMERCE%202011[[ 77 | 78 | [dlplug]: https://plugins.gradle.org/plugin/de.undercouch.download 79 | -------------------------------------------------------------------------------- /docs/Plugin-sap.commerce.build.ccv2.md: -------------------------------------------------------------------------------- 1 | # Plugin `sap.commerce.build.ccv2` 2 | 3 | This plugins parses Commerce Cloud v2 [`manifest.json`][manifest] file and provides it to the Gradle build script. 4 | 5 | If you also use the `sap.commerce.build` plugin, it preconfigures various tasks based on `manifest.json` 6 | 7 | [manifest]: https://help.sap.com/viewer/1be46286b36a4aa48205be5a96240672/latest/en-US/2be55790d99e4a1dad4caa7a1fc1738f.html 8 | 9 | ## Configuration 10 | 11 | The following example shows the full DSL (Domain Specific Language) with all default options and the dependencies the 12 | plugin pre-configures. 13 | 14 | ```groovy 15 | CCV2 { 16 | //target folder for the `generate*` tasks (details see below) 17 | generatedConfiguration = file('generated-configuration') 18 | 19 | //Use this property to access the manifest.json in your Gradle build script 20 | manifest = < parsed manifest.json > 21 | } 22 | 23 | ``` 24 | 25 | If you also use `sap.commerce.build` in your build, the `hybris.version` is preconfigured with `commerceSuiteVersion` of 26 | the manifest. 27 | 28 | ### `extenionPacks` Support 29 | 30 | All artifacts configured as additional `extensionPacks` in your `manifest.json` will also be unpacked into the root of 31 | your repository during [`bootstrapPlatform`][bootstrap]. This allows you to easily bootstrap e.g. the "Integration 32 | Extension Pack" locally. 33 | 34 | See also: 35 | 36 | - [Deploying the Integrations Pack on SAP Commerce Cloud][pack] 37 | - [Extension Packs][packs] 38 | 39 | **How are `extensionPacks` resolved as Maven artifacts?** 40 | 41 | - If `name` / `version` is supplied:\ 42 | `de.hybris.platform:${name}:${version}@zip` 43 | - If `artifact` is supplied:\ 44 | `${artifact}` (as is, without any changes)\ 45 | (If `artifact` is configured, `name` and `version` are ignored, as specified in the docs) 46 | 47 | [bootstrap]: /docs/Plugin-sap.commerce.build.md#bootstrapplatform 48 | 49 | [pack]: https://help.sap.com/viewer/2f43049ad8e443249e1981575adddb5d/LATEST/en-US/19bacaecbdd34cc8bd58bdd8daf428c5.html 50 | 51 | [packs]: https://help.sap.com/viewer/1be46286b36a4aa48205be5a96240672/LATEST/en-US/ad98c976ab3d433e935b4b5c89303dd5.html 52 | 53 | ## Tasks 54 | 55 | The plugin defines the following tasks 56 | 57 | ### `validateManifest` 58 | 59 | Validate `manifest.json` for common issues. If errors are detected, the task fails. Warnings are logged, but do not 60 | cause the task to fail. 61 | 62 | You can find all possible errors and warnings in [ccv2-validation.md](ccv2-validation.md) 63 | 64 | ### `installManifestAddons` 65 | 66 | **Only available if the build also uses `sap.commerce.build`** 67 | 68 | Runs `ant addonistall` for all addons defined in `storefrontAddons` of the manifest. 69 | 70 | ### `cloudTests` 71 | 72 | **Only available if the build also uses `sap.commerce.build`** 73 | 74 | Runs `ant alltests` preconfigured with the values of the [`tests`][tests] object of the manifest 75 | 76 | [tests]: https://help.sap.com/viewer/1be46286b36a4aa48205be5a96240672/latest/en-US/5ae6471137c44947a4f3051c753229d7.html 77 | 78 | ### `cloudWebTests` 79 | 80 | **Only available if the build also uses `sap.commerce.build`** 81 | 82 | Runs `ant webtests` preconfigured with the values of the [`webTests`][webtests] object of the manifest 83 | 84 | [webtests]: https://help.sap.com/viewer/1be46286b36a4aa48205be5a96240672/latest/en-US/e978c15cad464c9eabb67bd868154377.html 85 | 86 | ### `generateCloudProperties` 87 | 88 | Generates `*.properties` files (into the folder configured by `CCV2.generatedConfiguration`) per aspect and persona as 89 | defined in `manifest.json`. 90 | 91 | Filename schema: `_.properties`. 92 | 93 | The aspect `common` is used for the properties that are shared between all aspects. 94 | 95 | ### `generateCloudLocalextensions` 96 | 97 | Generates a `localextensions.xml` file (into the folder configured by `CCV2.generatedConfiguration`) based on 98 | the `extensions` list in the manifest. 99 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Plugin Documentation 2 | 3 | - [Commerce Build Plugin](Plugin-sap.commerce.build.md) 4 | 5 | Download, bootstrap, build, test, ... your SAP Commerce project using Gradle 6 | 7 | - [CCv2 Build Plugin](Plugin-sap.commerce.build.ccv2.md) 8 | 9 | Use `manifest.json` to build and configure your commerce project locally 10 | 11 | **To see the full capabilities of the plugins in action, please check out the [cv2-project-template]** 12 | 13 | [cv2-project-template]: https://github.com/sap-commerce-tools/ccv2-project-template 14 | 15 | ## FAQ 16 | 17 | Can be found here: [FAQ](FAQ.md) 18 | -------------------------------------------------------------------------------- /docs/images/launchpad-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/docs/images/launchpad-info.png -------------------------------------------------------------------------------- /docs/images/launchpad-object.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/docs/images/launchpad-object.png -------------------------------------------------------------------------------- /docs/images/launchpad-search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/docs/images/launchpad-search.png -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.warning.mode=all 2 | org.gradle.caching=true 3 | org.gradle.parallel=true 4 | -------------------------------------------------------------------------------- /gradle/greclipse.properties: -------------------------------------------------------------------------------- 1 | 2 | #Whether to use 'space', 'tab' or 'mixed' (both) characters for indentation. 3 | #The default value is 'tab'. 4 | org.eclipse.jdt.core.formatter.tabulation.char=space 5 | -------------------------------------------------------------------------------- /gradle/spotless.importorder: -------------------------------------------------------------------------------- 1 | #Organize Import Order 2 | 0=java 3 | 1=javax 4 | 2=org 5 | 3=com 6 | 4= 7 | 5=sap 8 | 6=mpern 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /manualTest/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /generated-configuration 3 | -------------------------------------------------------------------------------- /manualTest/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import mpern.sap.commerce.build.tasks.HybrisAntTask 2 | 3 | plugins { 4 | id("sap.commerce.build") version("SNAPSHOT") 5 | id("sap.commerce.build.ccv2") version("SNAPSHOT") 6 | } 7 | 8 | val repositoryURL: String by project 9 | val repositoryUser: String by project 10 | val repositoryPass: String by project 11 | 12 | repositories { 13 | maven { 14 | url = uri(repositoryURL) 15 | credentials { 16 | username = repositoryUser 17 | password = repositoryPass 18 | } 19 | } 20 | flatDir { dirs("platform") } 21 | mavenCentral() 22 | } 23 | 24 | hybris { 25 | antTaskDependencies.set(listOf("bootstrapPlatform")) 26 | 27 | sparseBootstrap { 28 | enabled = true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /manualTest/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | // path to the commerce-gradle-plugin project folder 3 | includeBuild("../") 4 | } 5 | -------------------------------------------------------------------------------- /plugin-commons/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /plugin-commons/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("mpern.commons") 3 | } 4 | -------------------------------------------------------------------------------- /plugin-commons/src/main/java/mpern/sap/commerce/commons/Constants.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.commons; 2 | 3 | public final class Constants { 4 | 5 | public static final String CCV2_EXTENSION = "CCV2"; 6 | 7 | private Constants() { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:best-practices" 5 | ], 6 | "automergeType": "branch", 7 | "ignorePaths": ["manualTest/build.gradle.kts"], 8 | "packageRules": [ 9 | { 10 | "matchUpdateTypes": ["minor", "patch", "pin", "digest", "pinDigest", "lockFileMaintenance"], 11 | "automerge": true 12 | }, 13 | { 14 | "matchDatasources": ["maven"], 15 | "matchPackageNames": ["org.spockframework:spock-bom"], 16 | "versionCompatibility": "^(?[^-]+(?-.*))$" 17 | }, 18 | { 19 | "matchDepNames": ["gradle-backward"], 20 | "allowedVersions": ">= 7.6 < 8.0" 21 | } 22 | ], 23 | 24 | "customManagers": [ 25 | { 26 | "customType": "regex", 27 | "managerFilePatterns": [ 28 | "/(^|/)\\.github/workflows/.+\\.ya?ml$/", 29 | "/(^|/)action\\.ya?ml$/" 30 | ], 31 | "datasourceTemplate": "gradle-version", 32 | "depNameTemplate": "gradle-backward", 33 | "matchStrings": [ 34 | "# renovate: gradle-backwards-compatibility\\s+-\\s+?[\"']?(?.+?)[\"']?\\s" 35 | ] 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "commerce-gradle-plugin" 2 | include("test-utils") 3 | include("plugin-commons") 4 | include("build-plugin") 5 | include("cloud-plugin") 6 | -------------------------------------------------------------------------------- /test-utils/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /genSrc 3 | src/main/resources/dummy-custom-modules/userHome 4 | -------------------------------------------------------------------------------- /test-utils/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("mpern.commons") 3 | } 4 | 5 | val generated = "$buildDir/genSrc/main/java" 6 | 7 | sourceSets["main"].java { 8 | srcDir(generated) 9 | } 10 | 11 | val generateSources by tasks.registering { 12 | 13 | inputs.property("projectDir", project.projectDir) 14 | 15 | outputs.dir(generated) 16 | 17 | doFirst { 18 | val projectDir = inputs.properties["projectDir"] as File 19 | val structure = file("$generated/mpern/sap/commerce/test/TestConstants.java") 20 | val resourcesDir = 21 | projectDir 22 | .resolve("src/main/resources") 23 | .toString() 24 | .replace("\\", "\\\\") 25 | structure.parentFile.mkdirs() 26 | structure.writeText( 27 | """ 28 | package mpern.sap.commerce.test; 29 | 30 | import java.nio.file.Path; 31 | 32 | public class TestConstants { 33 | 34 | public static final Path TEST_RESOURCES = Path.of("$resourcesDir"); 35 | 36 | public static Path testResource(String fileOrFolder) { 37 | return TEST_RESOURCES.resolve(fileOrFolder); 38 | } 39 | } 40 | """.stripIndent(), 41 | ) 42 | } 43 | } 44 | 45 | tasks.named("compileJava") { 46 | dependsOn(generateSources) 47 | } 48 | -------------------------------------------------------------------------------- /test-utils/src/main/java/mpern/sap/commerce/build/ExtensionsTestUtils.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.*; 5 | import java.nio.file.attribute.BasicFileAttributes; 6 | import java.util.List; 7 | import java.util.Locale; 8 | 9 | import mpern.sap.commerce.test.TestConstants; 10 | 11 | /** 12 | * Utility functions to generate extensions for the test. 13 | */ 14 | public final class ExtensionsTestUtils { 15 | 16 | private static final String HYBRIS_BIN_DIR = "hybris/bin/"; 17 | 18 | public static void generateExtension(Path location, String extensionName, List dependencies) 19 | throws IOException { 20 | 21 | Path extensionFolder = location.resolve(extensionName); 22 | Files.createDirectories(extensionFolder); 23 | Path extensionInfoFile = extensionFolder.resolve("extensioninfo.xml"); 24 | Files.createFile(extensionInfoFile); 25 | 26 | String firstUpExtensionName = extensionName.substring(0, 1).toUpperCase(Locale.ROOT) 27 | + extensionName.substring(1); 28 | 29 | StringBuilder dependenciesContent = new StringBuilder(); 30 | for (String dependency : dependencies) { 31 | dependenciesContent.append("") 32 | .append(System.lineSeparator()); 33 | } 34 | 35 | String extensionInfoContentPattern = "%n" 36 | + " %n" 37 | + " %n" 39 | + " %3$s%n" 40 | + " %n" 41 | + " %n" + " "; 42 | 43 | String extensionInfoContent = String.format(extensionInfoContentPattern, extensionName, firstUpExtensionName, 44 | dependenciesContent); 45 | 46 | Files.writeString(extensionInfoFile, extensionInfoContent, StandardOpenOption.CREATE, 47 | StandardOpenOption.TRUNCATE_EXISTING); 48 | } 49 | 50 | public static void ensureLocalExtensions(Path projectDir) throws Exception { 51 | Path sourceFile = TestConstants.testResource("localextensions.xml"); 52 | Path targetFile = projectDir.resolve("hybris/config/localextensions.xml"); 53 | TestUtils.ensureParents(targetFile); 54 | Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING); 55 | } 56 | 57 | public static void removeExtension(Path projectDir, String relativePath) throws IOException { 58 | Path extensionPath = projectDir.resolve(HYBRIS_BIN_DIR + relativePath); 59 | if (Files.exists(extensionPath) && Files.isDirectory(extensionPath)) { 60 | Files.walkFileTree(extensionPath, new SimpleFileVisitor() { 61 | @Override 62 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 63 | // delete the file 64 | Files.delete(file); 65 | return FileVisitResult.CONTINUE; 66 | } 67 | 68 | @Override 69 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 70 | // delete the directory after deleting its contents 71 | Files.delete(dir); 72 | return FileVisitResult.CONTINUE; 73 | } 74 | }); 75 | } 76 | } 77 | 78 | private ExtensionsTestUtils() { 79 | // no instances 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test-utils/src/main/java/mpern/sap/commerce/build/ProjectFolderTestUtils.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | import java.nio.file.*; 6 | import java.util.stream.Stream; 7 | 8 | import mpern.sap.commerce.test.TestConstants; 9 | 10 | public class ProjectFolderTestUtils { 11 | 12 | /** 13 | * Copies the content of a source test project folder (containing the hybris 14 | * folder) to a destination project folder. 15 | * 16 | * @param projectDir where to copy 17 | * @param template the name of the source test project folder template 18 | * @throws IOException 19 | * @throws URISyntaxException 20 | */ 21 | public static void prepareProjectFolder(Path projectDir, String template) throws IOException, URISyntaxException { 22 | Path sourceDir = TestConstants.testResource(template); 23 | 24 | // Iterate over the direct subdirectories of the source directory 25 | try (DirectoryStream sourceDs = Files.newDirectoryStream(sourceDir)) { 26 | for (Path subdir : sourceDs) { 27 | if (Files.isDirectory(subdir) && subdir.endsWith("hybris")) { 28 | copyDirContent(projectDir, sourceDir, subdir); 29 | } 30 | } 31 | } 32 | } 33 | 34 | private static void copyDirContent(Path destination, Path sourceDir, Path subdir) throws IOException { 35 | try (Stream streamPaths = Files.walk(subdir)) { 36 | // Copy each subdirectory and its contents to the target directory 37 | streamPaths.forEach(sourcePath -> { 38 | Path targetPath = destination.resolve(sourceDir.relativize(sourcePath)); 39 | if (!Files.exists(targetPath)) { 40 | try { 41 | Files.copy(sourcePath, targetPath); 42 | } catch (IOException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | }); 47 | } 48 | } 49 | 50 | private ProjectFolderTestUtils() { 51 | // no instances 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test-utils/src/main/java/mpern/sap/commerce/build/TestUtils.java: -------------------------------------------------------------------------------- 1 | package mpern.sap.commerce.build; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.nio.file.FileSystem; 7 | import java.nio.file.FileSystems; 8 | import java.nio.file.FileVisitResult; 9 | import java.nio.file.Files; 10 | import java.nio.file.Path; 11 | import java.nio.file.SimpleFileVisitor; 12 | import java.nio.file.StandardOpenOption; 13 | import java.nio.file.attribute.BasicFileAttributes; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | import mpern.sap.commerce.test.TestConstants; 18 | 19 | public class TestUtils { 20 | 21 | public static void generateDummyPlatform(Path destination, String version) throws IOException, URISyntaxException { 22 | generateDummyPlatformFromTemplate(destination, version, "dummy-platform"); 23 | } 24 | 25 | public static void generateDummyPlatformNewModel(Path destination, String version) 26 | throws IOException, URISyntaxException { 27 | generateDummyPlatformFromTemplate(destination, version, "dummy-platform-new-model"); 28 | } 29 | 30 | private static void generateDummyPlatformFromTemplate(Path destination, String version, String template) 31 | throws IOException, URISyntaxException { 32 | Path targetZip = destination.resolve(String.format("hybris-commerce-suite-%s.zip", version)); 33 | Map env = new HashMap<>(); 34 | env.put("create", "true"); 35 | try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:" + targetZip.toUri()), env, null)) { 36 | Path sourceDir = TestConstants.testResource(template); 37 | processSourceDir(zipfs, sourceDir); 38 | String build = String.format("version=%s\n", version); 39 | Path buildNumber = zipfs.getPath("hybris", "bin", "platform", "build.number"); 40 | Files.write(buildNumber, build.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); 41 | } 42 | } 43 | 44 | public static void generateDummyIntegrationPack(Path destination, String version) throws Exception { 45 | Path targetZip = destination.resolve(String.format("hybris-commerce-integrations-%s.zip", version)); 46 | Map env = new HashMap<>(); 47 | env.put("create", "true"); 48 | try (FileSystem zipfs = FileSystems.newFileSystem(URI.create("jar:" + targetZip.toUri()), env, null)) { 49 | Path sourceDir = TestConstants.testResource("dummy-integration-pack"); 50 | processSourceDir(zipfs, sourceDir); 51 | } 52 | } 53 | 54 | public static Path ensureParents(Path p) throws Exception { 55 | Files.createDirectories(p.getParent()); 56 | return p; 57 | } 58 | 59 | private static void processSourceDir(FileSystem zipfs, Path sourceDir) throws IOException { 60 | Files.walkFileTree(sourceDir, new SimpleFileVisitor() { 61 | @Override 62 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 63 | Path relative = sourceDir.relativize(file); 64 | if (relative.getParent() != null) { 65 | Path path = zipfs.getPath(relative.getParent().toString()); 66 | Files.createDirectories(path); 67 | } 68 | Path target = zipfs.getPath(relative.toString()); 69 | Files.copy(file, target); 70 | return super.visitFile(file, attrs); 71 | } 72 | }); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/ccv2-test-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commerceSuiteVersion": "1808.0", 3 | "extensions": [ 4 | "modeltacceleratorservices", 5 | "electronicsstore", 6 | "privacyoverlayeraddon", 7 | "yacceleratorstorefront", 8 | "backoffice" 9 | ], 10 | "properties": [ 11 | { 12 | "key": "common.property", 13 | "value": "common.property.value" 14 | }, 15 | { 16 | "key": "test.property.1", 17 | "value": "test.property.1.value", 18 | "persona": "production" 19 | }, 20 | { 21 | "key": "test.property.2", 22 | "value": "test.property.2.value", 23 | "persona": "development" 24 | }, 25 | { 26 | "key": "test.property.2", 27 | "value": "test.property.2.value.in.prod.only", 28 | "persona": "production" 29 | } 30 | ], 31 | "storefrontAddons": [ 32 | { 33 | "addon": "privacyoverlayeraddon", 34 | "storefront": "albinostorefront", 35 | "template": "yacceleratorstorefront" 36 | }, 37 | { 38 | "addon": "albinoaddon", 39 | "storefront": "albinostorefront", 40 | "template": "yacceleratorstorefront" 41 | } 42 | ], 43 | "aspects": [ 44 | { 45 | "name": "backoffice", 46 | "properties": [ 47 | { 48 | "key": "test.property.1", 49 | "value": "test.property-1-value-prod-backoffice", 50 | "persona": "production" 51 | }, 52 | { 53 | "key": "test.property.2", 54 | "value": "test.property-2-value-backoffice" 55 | } 56 | ], 57 | "webapps": [ 58 | { 59 | "name": "hac", 60 | "contextPath": "/hac" 61 | }, 62 | { 63 | "name": "mediaweb", 64 | "contextPath": "/medias" 65 | }, 66 | { 67 | "name": "backoffice", 68 | "contextPath": "" 69 | } 70 | ] 71 | }, 72 | { 73 | "name": "accstorefront", 74 | "properties": [ 75 | { 76 | "key": "spring.session.enabled", 77 | "value": "true" 78 | }, 79 | { 80 | "key": "spring.session.yacceleratorstorefront.save", 81 | "value": "async" 82 | }, 83 | { 84 | "key": "spring.session.yacceleratorstorefront.cookie.name", 85 | "value": "JSESSIONID" 86 | }, 87 | { 88 | "key": "spring.session.yacceleratorstorefront.cookie.path", 89 | "value": "/" 90 | }, 91 | { 92 | "key": "storefrontContextRoot", 93 | "value": "" 94 | } 95 | ], 96 | "webapps": [ 97 | { 98 | "name": "mediaweb", 99 | "contextPath": "/medias" 100 | }, 101 | { 102 | "name": "albinostorefront", 103 | "contextPath": "" 104 | }, 105 | { 106 | "name": "acceleratorservices", 107 | "contextPath": "/acceleratorservices" 108 | } 109 | ] 110 | }, 111 | { 112 | "name": "backgroundProcessing", 113 | "properties": [], 114 | "webapps": [ 115 | { 116 | "name": "hac", 117 | "contextPath": "" 118 | }, 119 | { 120 | "name": "mediaweb", 121 | "contextPath": "/medias" 122 | } 123 | ] 124 | } 125 | ], 126 | "tests": { 127 | "extensions": [ 128 | "privacyoverlayeraddon", 129 | "yacceleratorstorefront" 130 | ], 131 | "annotations": [ 132 | "UnitTests", 133 | "IntegrationTests" 134 | ], 135 | "packages": [ 136 | "de.hybris.infra.*" 137 | ] 138 | }, 139 | "webTests": { 140 | "extensions": [ 141 | "yacceleratorstorefront" 142 | ], 143 | "excludedPackages": [ 144 | "de.hybris.platform.*" 145 | ] 146 | } 147 | } -------------------------------------------------------------------------------- /test-utils/src/main/resources/cloud-extension-pack-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "commerceSuiteVersion": "1905", 3 | "useCloudExtensionPack": true, 4 | "storefrontAddons": [ 5 | { 6 | "addon": "privacyoverlayeraddon", 7 | "storefront": "albinostorefront", 8 | "template": "yacceleratorstorefront" 9 | }, 10 | { 11 | "addon": "albinoaddon", 12 | "storefront": "albinostorefront", 13 | "template": "yacceleratorstorefront" 14 | } 15 | ], 16 | "aspects": [ 17 | { 18 | "name": "backoffice", 19 | "properties": [ 20 | { 21 | "key": "test.property.1", 22 | "value": "test.property-1-value-prod-backoffice", 23 | "persona": "production" 24 | }, 25 | { 26 | "key": "test.property.2", 27 | "value": "test.property-2-value-backoffice" 28 | } 29 | ], 30 | "webapps": [ 31 | { 32 | "name": "hac", 33 | "contextPath": "/hac" 34 | }, 35 | { 36 | "name": "mediaweb", 37 | "contextPath": "/medias" 38 | }, 39 | { 40 | "name": "backoffice", 41 | "contextPath": "" 42 | } 43 | ] 44 | }, 45 | { 46 | "name": "accstorefront", 47 | "properties": [ 48 | { 49 | "key": "spring.session.enabled", 50 | "value": "true" 51 | }, 52 | { 53 | "key": "spring.session.yacceleratorstorefront.save", 54 | "value": "async" 55 | }, 56 | { 57 | "key": "spring.session.yacceleratorstorefront.cookie.name", 58 | "value": "JSESSIONID" 59 | }, 60 | { 61 | "key": "spring.session.yacceleratorstorefront.cookie.path", 62 | "value": "/" 63 | }, 64 | { 65 | "key": "storefrontContextRoot", 66 | "value": "" 67 | } 68 | ], 69 | "webapps": [ 70 | { 71 | "name": "mediaweb", 72 | "contextPath": "/medias" 73 | }, 74 | { 75 | "name": "albinostorefront", 76 | "contextPath": "" 77 | }, 78 | { 79 | "name": "acceleratorservices", 80 | "contextPath": "/acceleratorservices" 81 | } 82 | ] 83 | }, 84 | { 85 | "name": "backgroundProcessing", 86 | "properties": [], 87 | "webapps": [ 88 | { 89 | "name": "hac", 90 | "contextPath": "" 91 | }, 92 | { 93 | "name": "mediaweb", 94 | "contextPath": "/medias" 95 | } 96 | ] 97 | } 98 | ], 99 | "tests": { 100 | "extensions": [ 101 | "privacyoverlayeraddon", 102 | "yacceleratorstorefront" 103 | ], 104 | "annotations": [ 105 | "UnitTests", 106 | "IntegrationTests" 107 | ], 108 | "packages": [ 109 | "de.hybris.infra.*" 110 | ] 111 | }, 112 | "webTests": { 113 | "extensions": [ 114 | "yacceleratorstorefront" 115 | ], 116 | "excludedPackages": [ 117 | "de.hybris.platform.*" 118 | ] 119 | } 120 | } -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-custom-modules/hybris/bin/custom/module/myextensionone/CodeOne.java: -------------------------------------------------------------------------------- 1 | class CodeOne { 2 | 3 | } -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-custom-modules/hybris/bin/custom/module/myextensionone/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-custom-modules/hybris/bin/custom/module/myextensiontwo/CodeTwo.java: -------------------------------------------------------------------------------- 1 | class CodeTwo { 2 | 3 | } -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-custom-modules/hybris/bin/custom/module/myextensiontwo/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-integration-pack/hybris/bin/modules/scpi/sapcpiproductexchange/project.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-integration-pack/hybris/bin/modules/scpi/sapcpiproductexchange/project.properties -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/build_and_install.sh: -------------------------------------------------------------------------------- 1 | 2 | zip -r dummy * 3 | 4 | mvn install:install-file -Dfile=dummy.zip -DgroupId=de.hybris.platform -DartifactId=hybris-commerce-suite -Dversion=TEST -Dpackaging=zip 5 | 6 | touch test.jar 7 | mvn install:install-file -Dfile=test-jar.jar -DgroupId=some.database -DartifactId=jdbc -Dversion=TEST -Dpackaging=jar -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris-Mobile-Apps-SDK/dummy.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform-new-model/hybris-Mobile-Apps-SDK/dummy.txt -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/api-registry/apiregistryservices/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/backoffice-framework/backoffice/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/backoffice-framework/ybackoffice/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/base-commerce/basecommerce/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/base-commerce/payment/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/platform/yempty/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/rule-engine/ruleengine/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/modules/search-services/searchservices/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/platform/build.number: -------------------------------------------------------------------------------- 1 | version=TEST 2 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/platform/ext/commons/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/hybris/bin/platform/ext/core/extensioninfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/installer/install.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform-new-model/installer/install.sh -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/test-jar.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform-new-model/test-jar.jar -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform-new-model/test.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform-new-model/test.jar -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/build_and_install.sh: -------------------------------------------------------------------------------- 1 | 2 | zip -r dummy * 3 | 4 | mvn install:install-file -Dfile=dummy.zip -DgroupId=de.hybris.platform -DartifactId=hybris-commerce-suite -Dversion=TEST -Dpackaging=zip 5 | 6 | touch test.jar 7 | mvn install:install-file -Dfile=test-jar.jar -DgroupId=some.database -DartifactId=jdbc -Dversion=TEST -Dpackaging=jar -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/hybris-Mobile-Apps-SDK/dummy.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform/hybris-Mobile-Apps-SDK/dummy.txt -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/hybris/bin/ext-template/yaccelerator/src/dummy.java: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform/hybris/bin/ext-template/yaccelerator/src/dummy.java -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/hybris/bin/platform/build.number: -------------------------------------------------------------------------------- 1 | version=TEST 2 | -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/installer/install.sh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform/installer/install.sh -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/test-jar.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform/test-jar.jar -------------------------------------------------------------------------------- /test-utils/src/main/resources/dummy-platform/test.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SAP/commerce-gradle-plugin/b47c653c0ba2708cbf913ebb790820fbc8dd4556/test-utils/src/main/resources/dummy-platform/test.jar -------------------------------------------------------------------------------- /test-utils/src/main/resources/localextensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------