├── .circleci └── config.yml ├── .github ├── CODE_OF_CONDUCT.md └── CONTRIBUTING.md ├── .gitignore ├── LICENSE ├── README.md ├── data-config.json ├── pom.xml └── src └── main ├── java └── com │ └── adobe │ └── aem │ └── compgenerator │ ├── AemCompGenerator.java │ ├── Constants.java │ ├── exceptions │ ├── GeneratorException.java │ └── package-info.java │ ├── javacodemodel │ ├── ImplementationBuilder.java │ ├── InterfaceBuilder.java │ ├── IsNullExpression.java │ ├── JavaCodeBuilder.java │ ├── JavaCodeModel.java │ ├── PrologCodeWriter.java │ ├── RenameFileCodeWriter.java │ ├── TernaryOperator.java │ ├── TestClassBuilder.java │ └── package-info.java │ ├── models │ ├── BaseModel.java │ ├── GenerationConfig.java │ ├── Options.java │ ├── ProjectSettings.java │ ├── Property.java │ ├── Tab.java │ └── package-info.java │ ├── package-info.java │ └── utils │ ├── CommonUtils.java │ ├── ComponentUtils.java │ ├── DesignDialogUtils.java │ ├── DialogUtils.java │ ├── HTMLUtils.java │ ├── XMLUtils.java │ └── package-info.java └── resources ├── META-INF └── MANIFEST.MF ├── log4j2.xml ├── template-copyright-css.txt ├── template-copyright-text.txt ├── template-copyright-xml.txt ├── template-copyright.txt └── template-htl.txt /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Java Maven CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-java/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/openjdk:8-jdk 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/postgres:9.4 16 | 17 | working_directory: ~/repo 18 | 19 | environment: 20 | # Customize the JVM maximum heap limit 21 | MAVEN_OPTS: -Xmx3200m 22 | 23 | steps: 24 | - checkout 25 | 26 | # Download and cache dependencies 27 | - restore_cache: 28 | keys: 29 | - v1-dependencies-{{ checksum "pom.xml" }} 30 | # fallback to using the latest cache if no exact match is found 31 | - v1-dependencies- 32 | 33 | - run: mvn dependency:go-offline 34 | 35 | - save_cache: 36 | paths: 37 | - ~/.m2 38 | key: v1-dependencies-{{ checksum "pom.xml" }} 39 | 40 | # run tests! 41 | - run: mvn integration-test -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Adobe Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at Grp-opensourceoffice@adobe.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for choosing to contribute! 4 | 5 | The following are a set of guidelines to follow when contributing to this project. 6 | 7 | ## Code Of Conduct 8 | 9 | This project adheres to the Adobe [code of conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold 10 | this code. Please report unacceptable behavior to the team. 11 | 12 | ## Contributor License Agreement 13 | 14 | All third-party contributions to this project must be accompanied by a signed contributor license agreement. This gives 15 | Adobe permission to redistribute your contributions as part of the project. 16 | [Sign our CLA](http://opensource.adobe.com/cla.html). You only need to submit an Adobe CLA one time, so if you have 17 | submitted one previously, you are good to go! 18 | 19 | ## How to contribute 20 | 21 | New code contributions should be made primarily using GitHub pull requests. This involves creating a fork of the 22 | project in your personal space, adding your new code in a branch and triggering a pull request. 23 | 24 | See how to perform pull requests at https://help.github.com/articles/using-pull-requests. 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/eclipse,java,maven 2 | 3 | ### Eclipse ### 4 | *.pydevproject 5 | .metadata 6 | .gradle 7 | bin/ 8 | tmp/ 9 | *.tmp 10 | *.bak 11 | *.swp 12 | *~.nib 13 | local.properties 14 | .settings/ 15 | .loadpath 16 | 17 | # Eclipse Core 18 | .project 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # JDT-specific (Eclipse Java Development Tools) 30 | .classpath 31 | 32 | # Java annotation processor (APT) 33 | .factorypath 34 | 35 | # PDT-specific 36 | .buildpath 37 | 38 | # sbteclipse plugin 39 | .target 40 | 41 | # TeXlipse plugin 42 | .texlipse 43 | 44 | 45 | ### Java ### 46 | *.class 47 | 48 | # Mobile Tools for Java (J2ME) 49 | .mtj.tmp/ 50 | 51 | # Package Files # 52 | *.war 53 | *.ear 54 | 55 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 56 | hs_err_pid* 57 | 58 | 59 | ### Maven ### 60 | target/ 61 | pom.xml.tag 62 | pom.xml.releaseBackup 63 | pom.xml.versionsBackup 64 | pom.xml.next 65 | release.properties 66 | dependency-reduced-pom.xml 67 | buildNumber.properties 68 | .mvn/timing.properties 69 | 70 | ### Vault ### 71 | .vlt 72 | 73 | ### IntelliJ ### 74 | .idea/ 75 | *.iml 76 | 77 | ### MacOS ### 78 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AEM Component Generator 2 | 3 | [![CircleCI](https://circleci.com/gh/adobe/aem-component-generator.svg?style=svg)](https://circleci.com/gh/adobe/aem-component-generator) 4 | 5 | AEM Component Generator is a java project that enables developers to generate the base structure of an 6 | AEM component using a JSON configuration file specifying component and dialog properties and other configuration 7 | options. 8 | 9 | Generated code includes: 10 | - `cq:dialog` for component properties 11 | - `dialogshared`/`dialogglobal` for shared/global component properties 12 | - Supports all basic field types, multifields, and image upload fields 13 | - Sling Model 14 | - Includes fully coded interface and implementation classes 15 | - Follows WCM Core component standards 16 | - Enables FE-only development for most authorable components 17 | - HTL file for rendering the component 18 | - Includes an object reference to the Sling Model 19 | - Includes the default WCM Core placeholder template for when the component is not yet configured 20 | - Stubbed clientlib (JS/CSS) following component client library patterns of WCM Core 21 | 22 | ## Dependencies 23 | The AEM Component Generator itself bundles all the dependencies it needs to execute. However, the 24 | **generated code has dependencies on ACS AEM Commons version 4.2.0+** for the following sling model injector annotations. 25 | - `@ChildResourceFromRequest` for injecting child resources as model classes (e.g. image fields, composite multifields) 26 | - `@SharedValueMapValue` for injecting shared/global component property field values 27 | 28 | ## How To Use 29 | 30 | To see the AEM Component Generator in action, 31 | [watch this video](https://s3.amazonaws.com/HS2Presentations/AEMPublic/2019-Adobe-AEM-Component-Code-Generator-Demo-Bounteous.mp4). 32 | Detailed steps for using the generator are found below. 33 | 34 | Step 1: Clone the project from github. 35 | 36 | Step 2: Update the demo config file (`data-config.json`) to your company defaults, removing references to `NewCo`/`newco` 37 | in the `project-settings` and `group` values. 38 | 39 | Step 3: Build the project by running `mvn clean install` from the main project folder. 40 | 41 | Step 4: Copy the generated `component-generator-N.N.jar` file (under the `target` folder) to a location 42 | from which you wish to generate AEM component code. Note that code will be generated at a relative path from which 43 | the generator is executed, which can be different from where the jar file is located. 44 | 45 | Step 5: Copy the `data-config.json` file from this project to the same location and update with relevant configs for 46 | your component. 47 | 48 | - `project-settings`: contains configuration options related to your AEM project 49 | - `project-settings.code-owner`: the name of the company/user this code belongs to - will replace `${CODEOWNER}` in the template files with this configured value 50 | - `project-settings.copyright-year`: the copyright year this code belongs to - will replace `${YEAR}` in the template files with this configured value. If one is not specified, will default to the current year. 51 | - `project-settings.bundle-path`: path to the java code of your main bundle 52 | - `project-settings.test-path`: path to the java code of your test cases 53 | - `project-settings.apps-path`: path to the `/apps` root 54 | - `project-settings.component-path`: path to the project's components directory, relative to the `/apps` folder 55 | - `project-settings.model-interface-pkg`: Java package for the interface model objects 56 | - `project-settings.model-impl-pkg`: Java package for the implementation model objects 57 | - `name`: folder name for the component 58 | - `title`: human readable component name, also used as the title for dialogs 59 | - `group`: component group 60 | - `type`: component folder type - content, form, structure 61 | - `options.js`: whether to create an empty JS lib for the component (shared with CSS lib) 62 | - `options.jstxt`: whether to create the js.txt mapping file within the clientlib. Set to `false` when this file is not needed within your clientlib 63 | - `options.css`: whether to create an empty CSS lib for the component (shared with JS lib) 64 | - `options.csstxt`: whether to create the css.txt mapping file within the clientlib. Set to `false` when this file is not needed within your clientlib 65 | - `options.html`: whether to create a default HTML file for the component 66 | - `options.html-content`: generate dialog fields in the html file 67 | - `options.slingmodel`: whether to create a sling model for the component 68 | - Class name is derived from converting "name" prop above to camel case (e.g. "google-maps" -> `GoogleMaps`/`GoogleMapsImpl`) 69 | - Fields are derived from dialog properties (see below) 70 | - `options.testclass`: whether to create a test class for the component's sling model 71 | - Test methods will fail with reason as yet to be implemented. 72 | - `options.junit-major-version`: provide major version identifier of junit to generate test classes accordingly. Currently, 4 or 5 is supported. 73 | - `options.content-exporter`: whether to configure sling model for content export 74 | - `options.model-adaptables`: array of adaptables to include in the Sling Model ('request' and/or 'resource') 75 | - `options.generic-javadoc`: whether to create generic javadoc for the getters in the model interface 76 | - `options.properties-tabs`: properties to create tabs structure in standard dialog for this component. If empty, properties will be created without tab structure 77 | - `options.properties-tabs[].id`: the tab "name" 78 | - `options.properties-tabs[].label`: the "title" of the tab 79 | - `options.properties-tabs[].fields`: all the properties to be added in the tab. 80 | - `options.properties-shared-tabs`: shared properties to create tabs structure for this component in shared dialog. If empty, properties will be created without tab structure in shared dialog for this component. 81 | - `options.properties-shared-tabs[].id`: the tab "name" 82 | - `options.properties-shared-tabs[].label`: the "title" of the tab 83 | - `options.properties-shared-tabs[].fields`: all the properties to be added in the tab. 84 | - `options.properties-global-tabs`: global properties to create in tabs for this component in global dialog. If empty, properties will be created without tab structure in global dialog for this component. 85 | - `options.properties-global-tabs[].id`: the tab "name" 86 | - `options.properties-global-tabs[].label`: the "title" of the tab 87 | - `options.properties-global-tabs[].fields`: all the properties to be added in the tab. 88 | - `options.properties`: properties to create in standard dialog for this component. If empty, no standard dialog will be created. This sample includes one of every possible sling:resourceType 89 | - `options.properties[].field`: the property "name" and java variable name. 90 | - `options.properties[].javadoc`: the javadoc associated with the property 91 | - `options.properties[].type`: the property field type 92 | - `options.properties[].label`: the `fieldLabel` associated with the property 93 | - `options.properties[].description`: the `fieldDescription` associated with the property 94 | - `options.properties[].items`: any child items needed for the specified property type 95 | - `options.properties[].attributes`: any additional attributes to be associated with property in `cq:dialog` 96 | - `options.properties[].model-name`: **(Multifield type Only)** the name of the sling model class generated for a multifield property 97 | - `options.properties[].use-existing-model`: **(Multifield type Only)** whether or not to generate a new sling model for the multifield property 98 | - `options.properties[].json-expose`: by default, the content exporter will ignore all properties unless `json-expose` is set to `true` 99 | - `options.properties[].json-property`: the json key for the property to be used when content export is configured 100 | - `options.properties-shared`: properties to create in shared dialog for this component. If empty, no shared dialog will be created 101 | - `options.properties-global`: properties to create in global dialog for this component. If empty, no global dialog will be created 102 | 103 | Step 6: To generate a component, navigate to the main folder of your AEM project and execute the following command. 104 | Note that paths specified in `project-settings` configs (above) will be relative to this location. 105 | 106 | ```sh 107 | $ java -jar 108 | ``` 109 | 110 | - `jarfile`: path to `component-generator-N.N.jar` file (replacing `N.N` with the applicable numbers) 111 | - `configfile`: path to `data-config.json` file 112 | 113 | Example: 114 | ```sh 115 | $ java -jar scripts/compgen/component-generator-1.0.jar scripts/compgen/data-config.json 116 | ``` 117 | 118 | Successful component generation should result in output similar to the following: 119 | ``` 120 | [17:57:50.427 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/.content.xml 121 | [17:57:50.441 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/_cq_dialog/.content.xml 122 | [17:57:50.443 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/dialogglobal/.content.xml 123 | [17:57:50.446 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/dialogshared/.content.xml 124 | [17:57:50.447 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/clientlibs/.content.xml 125 | [17:57:50.453 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/clientlibs/site/css/demo-comp.less 126 | [17:57:50.454 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/clientlibs/site/js/demo-comp.js 127 | [17:57:50.456 [INFO ] CommonUtils @93] - Created: ui.apps/src/main/content/jcr_root/apps/newco/components/content/demo-comp/demo-comp.html 128 | [17:57:50.456 [INFO ] ComponentUtils @85] - --------------* Component 'demo-comp' successfully generated *-------------- 129 | [17:57:50.476 [INFO ] CommonUtils @93] - Created: core/src/main/java/com/newco/aem/base/core/models/DemoComp.java 130 | [17:57:50.488 [INFO ] CommonUtils @93] - Created: core/src/main/java/com/newco/aem/base/core/models/impl/DemoCompImpl.java 131 | [17:57:50.488 [INFO ] JavaCodeModel @103] - --------------* Sling Model successfully generated *-------------- 132 | ``` 133 | 134 | ## Troubleshooting 135 | ### JSON Export Failing 136 | If you attempt to fetch your component's model json via a `.model.json` selector/extension and get the following error: 137 | ``` 138 | Invalid recursion selector value 'model' 139 | 140 | Cannot serve request to /content///jcr:content/.model.json in org.apache.sling.servlets.get.DefaultGetServlet 141 | ``` 142 | The most likely cause is that your sling model is not actually deployed to AEM. To validate, first check the bundles 143 | console in AEM at `/system/console/bundles` to validate that your java bundle containing the generated sling model is 144 | indeed on the server and in `Active` state. Assuming it is, check the Sling Models console at `/system/console/status-slingmodels` 145 | to validate that your sling model is deployed and running. The default `demo-comp` component produced by the `data-config.json` 146 | provided with the project should generate a fully deployable and functioning sling model. If you are experiencing this 147 | error with the demo component, the most likely cause is that either your build failed to deploy the java bundle to the 148 | AEM server, or the bundle deployed to the server but is not running. 149 | 150 | ### Shared Property Injection Failing 151 | If your logs have an error that looks like this: 152 | ``` 153 | Caused by: java.lang.IllegalArgumentException: No Sling Models Injector registered for source 'shared-component-properties-valuemap'.
 154 | ``` 155 | This is because you do not have [Shared Component Properties](https://adobe-consulting-services.github.io/acs-aem-commons/features/shared-component-properties/) 156 | configured on your AEM instance. This often happens to people experimenting with the demo `data-config.json` provided 157 | with the project, which creates a component with both a shared and a global property for testing. 158 | 159 | To resolve this issue, you can configure Shared Component Properties on your AEM instance if you plan to use that feature 160 | in your components. If you dont plan to use Shared Component Properties, however, empty out (or delete) the following 161 | values inside of the `data-config.json` file used to generate your component: `properties-shared-tabs`, 162 | `properties-shared`, `properties-global-tabs`, `properties-global`. Once this is complete, regenerate your component, 163 | which will remove any fields in your sling model attempting to be injected by `@SharedValueMapValue`. 164 | 165 | ## Contributing 166 | 167 | Originally developed and contributed by [Bounteous](https://www.bounteous.com/insights/2019/07/31/aem-component-generator/). 168 | 169 | Contributions are welcomed! Read the [Contributing Guide](.github/CONTRIBUTING.md) for more information. 170 | 171 | ## Licensing 172 | 173 | This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information. 174 | -------------------------------------------------------------------------------- /data-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "project-settings": { 3 | "code-owner": "NewCo Incorporated", 4 | "bundle-path": "core/src/main/java", 5 | "test-path": "core/src/test/java", 6 | "apps-path": "ui.apps/src/main/content/jcr_root/apps", 7 | "component-path": "newco/components", 8 | "model-interface-pkg": "com.newco.aem.base.core.models", 9 | "model-impl-pkg": "com.newco.aem.base.core.models.impl" 10 | }, 11 | "name": "demo-comp", 12 | "title": "Demo Component", 13 | "group": "NewCo Base", 14 | "type": "content", 15 | "options": { 16 | "js": true, 17 | "jstxt": true, 18 | "css": true, 19 | "csstxt": true, 20 | "html": true, 21 | "html-content": false, 22 | "slingmodel": true, 23 | "testclass": false, 24 | "junit-major-version": 5, 25 | "content-exporter": false, 26 | "model-adaptables": [ 27 | "request" 28 | ], 29 | "generic-javadoc": false, 30 | "properties-tabs": [ 31 | { 32 | "id": "tab-1", 33 | "label": "Tab 1", 34 | "fields": [ 35 | "textfieldTest", 36 | "checkTest", 37 | "pathfieldTest", 38 | "pagefieldTest", 39 | "tagfieldTest", 40 | "textareaTest", 41 | "dateTest", 42 | "selectTest", 43 | "radioTest", 44 | "hiddenTest", 45 | "numberfieldTest" 46 | ] 47 | }, 48 | { 49 | "id": "tab-2", 50 | "label": "Tab 2", 51 | "fields": [ 52 | "imageTest", 53 | "headingTest", 54 | "colors", 55 | "links" 56 | ] 57 | } 58 | ], 59 | "properties-shared-tabs" : [ 60 | { 61 | "id": "tab-shared", 62 | "label": "Tab Shared", 63 | "fields": ["sharedTextfieldTest"] 64 | } 65 | ], 66 | "properties-global-tabs" : [ 67 | { 68 | "id": "tab-global", 69 | "label": "Tab Global", 70 | "fields": ["globalTextfieldTest"] 71 | } 72 | ], 73 | "properties": [ 74 | { 75 | "field": "textfieldTest", 76 | "description": "Adds a fieldDescription tooltip", 77 | "javadoc": "Returns a text value tooltip used somewhere in the component", 78 | "type": "textfield", 79 | "label": "Textfield Test", 80 | "json-expose": true, 81 | "attributes": {} 82 | }, 83 | { 84 | "field": "checkTest", 85 | "type": "checkbox", 86 | "json-expose": true, 87 | "attributes": { 88 | "value": "{Boolean}true", 89 | "text": "Checkbox Test" 90 | } 91 | }, 92 | { 93 | "field": "pathfieldTest", 94 | "type": "pathfield", 95 | "label": "Pathfield Test", 96 | "json-expose": true, 97 | "attributes": { 98 | "rootPath": "/content" 99 | } 100 | }, 101 | { 102 | "field": "pagefieldTest", 103 | "type": "pagefield", 104 | "label": "Pagefield Test", 105 | "json-expose": true, 106 | "attributes": { 107 | "rootPath": "/content" 108 | } 109 | }, 110 | { 111 | "field": "tagfieldTest", 112 | "type": "tagfield", 113 | "label": "Tags Test", 114 | "json-expose": true, 115 | "attributes": { 116 | "multiple": "{Boolean}true" 117 | } 118 | }, 119 | { 120 | "field": "textareaTest", 121 | "type": "textarea", 122 | "label": "Textarea Test", 123 | "json-expose": true, 124 | "attributes": {} 125 | }, 126 | { 127 | "field": "dateTest", 128 | "description": "Context tooltip for authors.", 129 | "type": "datepicker", 130 | "json-expose": true, 131 | "label": "Select Date", 132 | "attributes": { 133 | "displayedFormat": "MM/DD/YYYY" 134 | } 135 | }, 136 | { 137 | "field": "selectTest", 138 | "type": "select", 139 | "label": "Select Test", 140 | "json-property": "selection", 141 | "json-expose": true, 142 | "attributes": { 143 | "defaultValue": "opt1", 144 | "value": "opt1" 145 | }, 146 | "items": [ 147 | { 148 | "field": "option1", 149 | "attributes": { 150 | "selected": "true", 151 | "text": "Option 1", 152 | "value": "opt1" 153 | } 154 | }, 155 | { 156 | "field": "option2", 157 | "attributes": { 158 | "text": "Option 2", 159 | "value": "opt2" 160 | } 161 | } 162 | ] 163 | }, 164 | { 165 | "field": "radioTest", 166 | "type": "radiogroup", 167 | "label": "Radio Test", 168 | "json-expose": true, 169 | "attributes": { 170 | "vertical": "{Boolean}false" 171 | }, 172 | "items": [ 173 | { 174 | "field": "radio1", 175 | "type": "radio", 176 | "attributes": { 177 | "checked": "{Boolean}true", 178 | "name": "radioTest", 179 | "text": "Radio 1", 180 | "value": "rad1" 181 | } 182 | }, 183 | { 184 | "field": "radio2", 185 | "type": "radio", 186 | "attributes": { 187 | "checked": "{Boolean}true", 188 | "name": "radioTest", 189 | "text": "Radio 2", 190 | "value": "rad2" 191 | } 192 | } 193 | ] 194 | }, 195 | { 196 | "field": "hiddenTest", 197 | "type": "hidden", 198 | "attributes": { 199 | "value": "hidden value" 200 | } 201 | }, 202 | { 203 | "field": "numberfieldTest", 204 | "type": "numberfield", 205 | "label": "Numberfield Test", 206 | "json-expose": true, 207 | "attributes": { 208 | "max": "{Double}20", 209 | "min": "{Double}0", 210 | "step": "1", 211 | "value": "{Long}20" 212 | } 213 | }, 214 | { 215 | "field": "imageTest", 216 | "type": "image", 217 | "label": "Image Test", 218 | "json-expose": true 219 | }, 220 | { 221 | "field": "headingTest", 222 | "type": "heading", 223 | "label": "Heading Test" 224 | }, 225 | { 226 | "field": "colors", 227 | "type": "multifield", 228 | "label": "Colors", 229 | "json-expose": true, 230 | "items": [ 231 | { 232 | "type": "textfield", 233 | "label": "Color" 234 | } 235 | ] 236 | }, 237 | { 238 | "field": "links", 239 | "type": "multifield", 240 | "model-name": "DemoLink", 241 | "use-existing-model": false, 242 | "label": "Links", 243 | "json-expose": true, 244 | "items": [ 245 | { 246 | "field": "path", 247 | "type": "pathfield", 248 | "label": "Path", 249 | "description": "Path to the page", 250 | "json-expose": true 251 | }, 252 | { 253 | "field": "label", 254 | "type": "textfield", 255 | "label": "Label", 256 | "description": "Label to display on the link", 257 | "json-expose": true 258 | }, 259 | { 260 | "field": "linkIcon", 261 | "type": "image", 262 | "label": "Link Icon", 263 | "json-expose": true 264 | } 265 | ] 266 | } 267 | ], 268 | "properties-shared": [ 269 | { 270 | "field": "sharedTextfieldTest", 271 | "type": "textfield", 272 | "label": "Shared Textfield Test", 273 | "json-expose": true 274 | } 275 | ], 276 | "properties-global": [ 277 | { 278 | "field": "globalTextfieldTest", 279 | "type": "textfield", 280 | "label": "Global Textfield Test", 281 | "json-expose": true 282 | } 283 | ] 284 | } 285 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | adobe 7 | component-generator 8 | 1.2.4-SNAPSHOT 9 | 10 | 11 | 12 | org.apache.maven.plugins 13 | maven-compiler-plugin 14 | 3.8.1 15 | 16 | UTF-8 17 | 1.8 18 | 1.8 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-resources-plugin 24 | 3.1.0 25 | 26 | UTF-8 27 | 28 | 29 | 30 | maven-assembly-plugin 31 | 32 | 33 | package 34 | 35 | single 36 | 37 | 38 | 39 | 40 | component-generator-${project.version} 41 | false 42 | 43 | 44 | com.adobe.aem.compgenerator.AemCompGenerator 45 | 46 | 47 | 48 | jar-with-dependencies 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-antrun-plugin 55 | 3.0.0 56 | 57 | 58 | smoketest-generation-example-basedir 59 | pre-integration-test 60 | 61 | run 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | org.codehaus.mojo 73 | exec-maven-plugin 74 | 3.0.0 75 | 76 | 77 | smoketest-generation-example 78 | integration-test 79 | 80 | exec 81 | 82 | 83 | java 84 | 85 | -jar 86 | ../component-generator-${project.version}.jar 87 | ../../data-config.json 88 | 89 | ${project.build.directory}/smoketest 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | javadoc 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-javadoc-plugin 104 | 3.1.0 105 | 106 | 107 | attach-javadocs 108 | 109 | jar 110 | 111 | 112 | 113 | package 114 | 115 | javadoc 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | sonar 125 | 126 | 127 | 128 | org.sonarsource.scanner.maven 129 | sonar-maven-plugin 130 | 3.6.0.1398 131 | 132 | 133 | package 134 | 135 | sonar 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | adobe-public-releases 147 | Adobe Public Repository 148 | https://repo.adobe.com/nexus/content/groups/public/ 149 | default 150 | 151 | 152 | 153 | 154 | adobe-public-releases 155 | Adobe Public Repository 156 | https://repo.adobe.com/nexus/content/groups/public/ 157 | default 158 | 159 | 160 | 161 | 162 | com.fasterxml.jackson.core 163 | jackson-databind 164 | 2.12.7.1 165 | 166 | 167 | org.apache.commons 168 | commons-text 169 | 1.10.0 170 | 171 | 172 | org.junit.jupiter 173 | junit-jupiter-api 174 | 5.3.2 175 | 176 | 177 | junit 178 | junit 179 | 4.13.1 180 | 181 | 182 | org.apache.commons 183 | commons-lang3 184 | 3.9 185 | 186 | 187 | net.sf.saxon 188 | Saxon-HE 189 | 9.7.0-15 190 | 191 | 192 | com.sun.codemodel 193 | codemodel 194 | 2.6 195 | 196 | 197 | org.apache.logging.log4j 198 | log4j-core 199 | 2.17.1 200 | 201 | 202 | org.apache.sling 203 | org.apache.sling.api 204 | 2.25.4 205 | 206 | 207 | org.apache.sling 208 | org.apache.sling.models.api 209 | 1.3.6 210 | 211 | 212 | org.apache.felix 213 | org.apache.felix.http.servlet-api 214 | 1.1.2 215 | 216 | 217 | com.adobe.acs 218 | acs-aem-commons-bundle 219 | [4.2.0,) 220 | 221 | 222 | com.adobe.cq 223 | com.adobe.cq.export.json 224 | 0.1.10 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/AemCompGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator; 21 | 22 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 23 | import com.adobe.aem.compgenerator.javacodemodel.JavaCodeModel; 24 | import com.adobe.aem.compgenerator.models.GenerationConfig; 25 | import com.adobe.aem.compgenerator.utils.CommonUtils; 26 | import com.adobe.aem.compgenerator.utils.ComponentUtils; 27 | import org.apache.logging.log4j.LogManager; 28 | import org.apache.logging.log4j.Logger; 29 | 30 | import java.io.File; 31 | 32 | /** 33 | * Root of the AEM Component generator. 34 | * 35 | * AemCompGenerator reads the json data file input and creates folder, file 36 | * structure of an AEM component and sling model interface with member values 37 | * and getters. 38 | */ 39 | public class AemCompGenerator { 40 | private static final Logger LOG = LogManager.getLogger(AemCompGenerator.class); 41 | 42 | public static void main(String[] args) { 43 | try { 44 | String configPath = "data-config.json"; 45 | if (args.length > 0) { 46 | configPath = args[0]; 47 | } 48 | 49 | File configFile = new File(configPath); 50 | 51 | if (CommonUtils.isFileBlank(configFile)) { 52 | throw new GeneratorException("Config file missing / empty."); 53 | } 54 | 55 | GenerationConfig config = CommonUtils.getComponentData(configFile); 56 | 57 | if (config == null) { 58 | throw new GeneratorException("Config file is empty / null !!"); 59 | } 60 | 61 | if (!config.isValid() || !CommonUtils.isModelValid(config.getProjectSettings())) { 62 | throw new GeneratorException("Mandatory fields missing in the data-config.json !"); 63 | } 64 | 65 | String compDir = config.getProjectSettings().getAppsPath() + "/" 66 | + config.getProjectSettings().getComponentPath() + "/" 67 | + config.getType() + "/" + config.getName(); 68 | config.setCompDir(compDir); 69 | 70 | //builds component folder and file structure. 71 | ComponentUtils generatorUtils = new ComponentUtils(config); 72 | generatorUtils.buildComponent(); 73 | 74 | //builds sling model based on config. 75 | if (config.getOptions() != null && config.getOptions().isHasSlingModel()) { 76 | JavaCodeModel javaCodeModel = new JavaCodeModel(); 77 | javaCodeModel.buildSlingModel(config); 78 | } 79 | } catch (Exception e) { 80 | LOG.error("Failed to generate aem component.", e); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator; 21 | 22 | public class Constants { 23 | 24 | public static final String RESOURCE_TYPE_CHECKBOX = "granite/ui/components/coral/foundation/form/checkbox"; 25 | public static final String RESOURCE_TYPE_CONTAINER = "granite/ui/components/coral/foundation/container"; 26 | public static final String RESOURCE_TYPE_DATEPICKER = "granite/ui/components/coral/foundation/form/datepicker"; 27 | public static final String RESOURCE_TYPE_DIALOG = "cq/gui/components/authoring/dialog"; 28 | public static final String RESOURCE_TYPE_FIELDSET = "granite/ui/components/foundation/form/fieldset"; 29 | public static final String RESOURCE_TYPE_FIXEDCOLUMNS = "granite/ui/components/foundation/layouts/fixedcolumns"; 30 | public static final String RESOURCE_TYPE_HEADING = "granite/ui/components/coral/foundation/heading"; 31 | public static final String RESOURCE_TYPE_HIDDEN = "granite/ui/components/coral/foundation/form/hidden"; 32 | public static final String RESOURCE_TYPE_IMAGE = "cq/gui/components/authoring/dialog/fileupload"; 33 | public static final String RESOURCE_TYPE_IMAGE_HIDDEN_TYPE = "core/wcm/components/image/v2/image"; 34 | public static final String RESOURCE_TYPE_INCLUDE = "granite/ui/components/coral/foundation/include"; 35 | public static final String RESOURCE_TYPE_MULTIFIELD = "granite/ui/components/coral/foundation/form/multifield"; 36 | public static final String RESOURCE_TYPE_NUMBER = "granite/ui/components/coral/foundation/form/numberfield"; 37 | public static final String RESOURCE_TYPE_PAGEFIELD = "cq/gui/components/coral/common/form/pagefield"; 38 | public static final String RESOURCE_TYPE_PATHFIELD = "granite/ui/components/coral/foundation/form/pathfield"; 39 | public static final String RESOURCE_TYPE_RADIOGROUP = "granite/ui/components/coral/foundation/form/radiogroup"; 40 | public static final String RESOURCE_TYPE_SECTION = "granite/ui/components/foundation/section"; 41 | public static final String RESOURCE_TYPE_SELECT = "granite/ui/components/coral/foundation/form/select"; 42 | public static final String RESOURCE_TYPE_TABS = "granite/ui/components/coral/foundation/tabs"; 43 | public static final String RESOURCE_TYPE_TAGFIELD = "cq/gui/components/coral/common/form/tagfield"; 44 | public static final String RESOURCE_TYPE_TEXTAREA = "granite/ui/components/coral/foundation/form/textarea"; 45 | public static final String RESOURCE_TYPE_TEXTFIELD = "granite/ui/components/coral/foundation/form/textfield"; 46 | public static final String RESOURCE_TYPE_CORAL_FIXEDCOLUMNS = "granite/ui/components/coral/foundation/fixedcolumns"; 47 | 48 | public static final String TYPE_CHECKBOX = "checkbox"; 49 | public static final String TYPE_DATEPICKER = "datepicker"; 50 | public static final String TYPE_HEADING = "heading"; 51 | public static final String TYPE_IMAGE = "image"; 52 | public static final String TYPE_MULTIFIELD = "multifield"; 53 | public static final String TYPE_HIDDEN = "hidden"; 54 | public static final String TYPE_TAGFIELD = "tagfield"; 55 | 56 | public static final String STYLE_SYSTEM_TAB_PATH = "/mnt/overlay/cq/gui/components/authoring/dialog/style/tab_design/styletab"; 57 | 58 | public static final String DIALOG_TYPE_DESIGN_DIALOG = "_cq_design_dialog"; 59 | public static final String DIALOG_TYPE_DIALOG = "_cq_dialog"; 60 | public static final String DIALOG_TYPE_GLOBAL = "dialogglobal"; 61 | public static final String DIALOG_TYPE_SHARED = "dialogshared"; 62 | public static final String FILENAME_CONTENT_XML = ".content.xml"; 63 | 64 | public static final String PROPERTY_TYPE_GLOBAL = "global"; 65 | public static final String PROPERTY_TYPE_PRIVATE = "private"; 66 | public static final String PROPERTY_TYPE_SHARED = "shared"; 67 | 68 | public static final String PROPERTY_JCR_TITLE = "jcr:title"; 69 | public static final String PROPERTY_SLING_RESOURCETYPE = "sling:resourceType"; 70 | public static final String PROPERTY_FIELDLABEL = "fieldLabel"; 71 | public static final String PROPERTY_FIELDDESC = "fieldDescription"; 72 | public static final String PROPERTY_NAME = "name"; 73 | public static final String PROPERTY_TEXT = "text"; 74 | public static final String PROPERTY_COMPOSITE = "composite"; 75 | public static final String PROPERTY_CQ_MSM_LOCKABLE = "cq-msm-lockable"; 76 | 77 | public static final String JCR_ROOT_NODE = "jcr:root"; 78 | public static final String JCR_PRIMARY_TYPE = "jcr:primaryType"; 79 | public static final String NT_UNSTRUCTURED = "nt:unstructured"; 80 | 81 | public static final String TYPE_CQ_CLIENTLIB_FOLDER = "cq:ClientLibraryFolder"; 82 | public static final String TYPE_CQ_COMPONENT = "cq:Component"; 83 | public static final String TYPE_SLING_FOLDER = "sling:Folder"; 84 | 85 | public static final String TEMPLATE_COPYRIGHT_CSS = "template-copyright-css.txt"; 86 | public static final String TEMPLATE_COPYRIGHT_JAVA = "template-copyright.txt"; 87 | public static final String TEMPLATE_COPYRIGHT_HTL = "template-htl.txt"; 88 | public static final String TEMPLATE_COPYRIGHT_TEXT = "template-copyright-text.txt"; 89 | public static final String TEMPLATE_COPYRIGHT_XML = "template-copyright-xml.txt"; 90 | 91 | public static final String STRING_GET = "get"; 92 | public static final String STRING_TEST = "test"; 93 | 94 | public static final String RENAME_FILE_DATE_PATTERN = "yyyyMMddHHmmss"; 95 | 96 | public static final int JUNIT_VERSION_5 = 5; 97 | public static final int JUNIT_VERSION_4 = 4; 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/exceptions/GeneratorException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.exceptions; 21 | 22 | public class GeneratorException extends RuntimeException { 23 | 24 | private static final long serialVersionUID = -548600394249617384L; 25 | 26 | public GeneratorException(String errorMessage) { 27 | super(errorMessage); 28 | } 29 | 30 | public GeneratorException(String errorMessage, Throwable cause) { 31 | super(errorMessage, cause); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/exceptions/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | /** 21 | * Package contains the exceptions provided by the application. 22 | * @since 1.0 23 | */ 24 | package com.adobe.aem.compgenerator.exceptions; -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/ImplementationBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.javacodemodel; 21 | 22 | import com.adobe.acs.commons.models.injectors.annotation.ChildResourceFromRequest; 23 | import com.adobe.acs.commons.models.injectors.annotation.SharedValueMapValue; 24 | import com.adobe.aem.compgenerator.Constants; 25 | import com.adobe.aem.compgenerator.models.GenerationConfig; 26 | import com.adobe.aem.compgenerator.models.Property; 27 | import com.adobe.cq.export.json.ComponentExporter; 28 | import com.adobe.cq.export.json.ExporterConstants; 29 | import com.fasterxml.jackson.annotation.JsonIgnore; 30 | import com.fasterxml.jackson.annotation.JsonProperty; 31 | import com.sun.codemodel.JAnnotationArrayMember; 32 | import com.sun.codemodel.JAnnotationUse; 33 | import com.sun.codemodel.JClass; 34 | import com.sun.codemodel.JClassAlreadyExistsException; 35 | import com.sun.codemodel.JCodeModel; 36 | import com.sun.codemodel.JDefinedClass; 37 | import com.sun.codemodel.JFieldVar; 38 | import com.sun.codemodel.JMethod; 39 | import com.sun.codemodel.JMod; 40 | import com.sun.codemodel.JPackage; 41 | import com.sun.codemodel.JExpr; 42 | import com.sun.codemodel.JExpression; 43 | import org.apache.commons.lang3.StringUtils; 44 | import org.apache.logging.log4j.LogManager; 45 | import org.apache.logging.log4j.Logger; 46 | import org.apache.sling.api.SlingHttpServletRequest; 47 | import org.apache.sling.api.resource.Resource; 48 | import org.apache.sling.models.annotations.Exporter; 49 | import org.apache.sling.models.annotations.Model; 50 | import org.apache.sling.models.annotations.injectorspecific.InjectionStrategy; 51 | import org.apache.sling.models.annotations.injectorspecific.SlingObject; 52 | import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; 53 | 54 | import java.util.Collections; 55 | import java.util.HashMap; 56 | import java.util.List; 57 | import java.util.Map; 58 | import java.util.Objects; 59 | 60 | import static com.sun.codemodel.JMod.PRIVATE; 61 | 62 | public class ImplementationBuilder extends JavaCodeBuilder { 63 | private static final Logger LOG = LogManager.getLogger(ImplementationBuilder.class); 64 | private static final String INJECTION_STRATEGY = "injectionStrategy"; 65 | private static final String OPTIONAL_INJECTION_STRATEGY = "OPTIONAL"; 66 | private static final String SLING_MODEL_EXPORTER_NAME = "SLING_MODEL_EXPORTER_NAME"; 67 | private static final String SLING_MODEL_EXTENSION = "SLING_MODEL_EXTENSION"; 68 | 69 | private final String className; 70 | private final JClass interfaceClass; 71 | private final JPackage implPackage; 72 | private final String[] adaptables; 73 | private final boolean isAllowExporting; 74 | 75 | private Map fieldJsonExposeMap = new HashMap<>(); 76 | private Map fieldJsonPropertyMap = new HashMap<>(); 77 | 78 | /** 79 | * Construct a new Sling Model implementation class. 80 | * 81 | * @param codeModel 82 | * @param generationConfig 83 | * @param className 84 | * @param interfaceClass 85 | */ 86 | public ImplementationBuilder(JCodeModel codeModel, 87 | GenerationConfig generationConfig, 88 | String className, 89 | JClass interfaceClass) { 90 | super(codeModel, generationConfig); 91 | this.className = className; 92 | this.interfaceClass = interfaceClass; 93 | this.implPackage = codeModel._package(generationConfig.getProjectSettings().getModelImplPackage()); 94 | this.adaptables = generationConfig.getOptions().getModelAdaptables(); 95 | this.isAllowExporting = generationConfig.getOptions().isAllowExporting(); 96 | } 97 | 98 | public JDefinedClass build(String resourceType) throws JClassAlreadyExistsException { 99 | JDefinedClass jc = this.implPackage._class(this.className)._implements(this.interfaceClass); 100 | addSlingAnnotations(jc, this.interfaceClass, resourceType); 101 | 102 | addFieldVars(jc, globalProperties, Constants.PROPERTY_TYPE_GLOBAL); 103 | addFieldVars(jc, sharedProperties, Constants.PROPERTY_TYPE_SHARED); 104 | addFieldVars(jc, privateProperties, Constants.PROPERTY_TYPE_PRIVATE); 105 | 106 | addGetters(jc); 107 | addExportedTypeMethod(jc); 108 | return jc; 109 | } 110 | 111 | private void addSlingAnnotations(JDefinedClass jDefinedClass, JClass adapterClass, String resourceType) { 112 | JAnnotationUse jAUse = jDefinedClass.annotate(codeModel.ref(Model.class)); 113 | JAnnotationArrayMember adaptablesArray = jAUse.paramArray("adaptables"); 114 | for (String adaptable : adaptables) { 115 | if ("resource".equalsIgnoreCase(adaptable)) { 116 | adaptablesArray.param(codeModel.ref(Resource.class)); 117 | } 118 | if ("request".equalsIgnoreCase(adaptable)) { 119 | adaptablesArray.param(codeModel.ref(SlingHttpServletRequest.class)); 120 | } 121 | } 122 | if (this.isAllowExporting) { 123 | jAUse.paramArray("adapters").param(adapterClass).param(codeModel.ref(ComponentExporter.class)); 124 | } else { 125 | jAUse.param("adapters", adapterClass); 126 | } 127 | if (StringUtils.isNotBlank(resourceType)) { 128 | jAUse.param("resourceType", resourceType); 129 | } 130 | if (this.isAllowExporting) { 131 | jAUse = jDefinedClass.annotate(codeModel.ref(Exporter.class)); 132 | jAUse.param("name", codeModel.ref(ExporterConstants.class).staticRef(SLING_MODEL_EXPORTER_NAME)); 133 | jAUse.param("extensions", codeModel.ref(ExporterConstants.class).staticRef(SLING_MODEL_EXTENSION)); 134 | } 135 | } 136 | 137 | /** 138 | * adds fields to java model. 139 | * 140 | * @param properties 141 | * @param propertyType 142 | */ 143 | private void addFieldVars(JDefinedClass jc, List properties, final String propertyType) { 144 | properties.stream() 145 | .filter(Objects::nonNull) 146 | .forEach(property -> addFieldVar(jc, property, propertyType)); 147 | } 148 | 149 | /** 150 | * add field variable to to jc. 151 | * 152 | * @param property 153 | */ 154 | private void addFieldVar(JDefinedClass jc, Property property, final String propertyType) { 155 | if (property != null && StringUtils.isNotBlank(property.getField())) { 156 | if (property.getType().equalsIgnoreCase(Constants.TYPE_HEADING)) { 157 | return; 158 | } 159 | 160 | if (!property.getType().equalsIgnoreCase("multifield")) { // non multifield properties 161 | addPropertyAsPrivateField(jc, property, propertyType); 162 | } else if (property.getItems().size() == 1) { // multifield with single property 163 | addPropertyAsPrivateField(jc, property, propertyType); 164 | } else if (property.getItems().size() > 1) { // composite multifield 165 | addPropertyAndObjectAsPrivateField(jc, property); 166 | } 167 | } 168 | } 169 | 170 | /** 171 | * method that add the fieldname as private to jc. 172 | * 173 | * @param property 174 | * @param propertyType 175 | */ 176 | private void addPropertyAsPrivateField(JDefinedClass jc, Property property, final String propertyType) { 177 | String fieldType = JavaCodeModel.getFieldType(property); 178 | 179 | JClass fieldClass = null; 180 | if (property.getType().equalsIgnoreCase("multifield")) { 181 | fieldClass = codeModel.ref(fieldType).narrow(codeModel.ref(JavaCodeModel.getFieldType(property.getItems().get(0)))); 182 | } else if (property.getType().equalsIgnoreCase("tagfield")) { 183 | fieldClass = codeModel.ref(fieldType).narrow(String.class); 184 | } else { 185 | fieldClass = codeModel.ref(fieldType); 186 | } 187 | JFieldVar jFieldVar = jc.field(PRIVATE, fieldClass, property.getField()); 188 | 189 | if (StringUtils.equalsIgnoreCase(property.getType(), "image")) { 190 | jFieldVar.annotate(codeModel.ref(ChildResourceFromRequest.class)) 191 | .param(INJECTION_STRATEGY, 192 | codeModel.ref(InjectionStrategy.class).staticRef(OPTIONAL_INJECTION_STRATEGY)); 193 | 194 | } else if (StringUtils.equalsIgnoreCase(propertyType, Constants.PROPERTY_TYPE_PRIVATE)) { 195 | jFieldVar.annotate(codeModel.ref(ValueMapValue.class)) 196 | .param(INJECTION_STRATEGY, 197 | codeModel.ref(InjectionStrategy.class).staticRef(OPTIONAL_INJECTION_STRATEGY)); 198 | } else { 199 | jFieldVar.annotate(codeModel.ref(SharedValueMapValue.class)) 200 | .param(INJECTION_STRATEGY, 201 | codeModel.ref(InjectionStrategy.class).staticRef(OPTIONAL_INJECTION_STRATEGY)); 202 | } 203 | 204 | setupFieldGetterAnnotations(jFieldVar, property); 205 | } 206 | 207 | /** 208 | * method that add the fieldname as private and adds a class to jc 209 | */ 210 | private void addPropertyAndObjectAsPrivateField(JDefinedClass jc, Property property) { 211 | String modelClassName = JavaCodeModel.getMultifieldInterfaceName(property); 212 | 213 | // Create the multifield item 214 | if (!property.getUseExistingModel()) { 215 | buildImplementation(property.getItems(), modelClassName); 216 | } 217 | 218 | String fieldType = JavaCodeModel.getFieldType(property); 219 | JClass narrowedClass = codeModel.ref(generationConfig.getProjectSettings().getModelInterfacePackage() + "." + modelClassName); 220 | JClass fieldClass = codeModel.ref(fieldType).narrow(narrowedClass); 221 | JFieldVar jFieldVar = jc.field(PRIVATE, fieldClass, property.getField()); 222 | jFieldVar.annotate(codeModel.ref(ChildResourceFromRequest.class)) 223 | .param(INJECTION_STRATEGY, 224 | codeModel.ref(InjectionStrategy.class).staticRef(OPTIONAL_INJECTION_STRATEGY)); 225 | 226 | setupFieldGetterAnnotations(jFieldVar, property); 227 | } 228 | 229 | /** 230 | * adds getters to all the fields available in the java class. 231 | * 232 | * @param jc 233 | */ 234 | private void addGetters(JDefinedClass jc) { 235 | Map fieldVars = jc.fields(); 236 | if (!fieldVars.isEmpty()) { 237 | for (Map.Entry entry : fieldVars.entrySet()) { 238 | if (entry.getValue() != null) { 239 | addGetter(jc, entry.getValue()); 240 | } 241 | } 242 | } 243 | } 244 | 245 | /** 246 | * add getter method for jFieldVar passed in. 247 | * 248 | * @param jc 249 | * @param jFieldVar 250 | */ 251 | private void addGetter(JDefinedClass jc, JFieldVar jFieldVar) { 252 | JMethod getMethod = jc.method(JMod.PUBLIC, jFieldVar.type(), getMethodFormattedString(jFieldVar.name())); 253 | getMethod.annotate(codeModel.ref(Override.class)); 254 | 255 | if (this.isAllowExporting) { 256 | if (!this.fieldJsonExposeMap.get(jFieldVar.name())) { 257 | getMethod.annotate(codeModel.ref(JsonIgnore.class)); 258 | } 259 | 260 | if (StringUtils.isNotBlank(this.fieldJsonPropertyMap.get(jFieldVar.name()))) { 261 | getMethod.annotate(codeModel.ref(JsonProperty.class)) 262 | .param("value", this.fieldJsonPropertyMap.get(jFieldVar.name())); 263 | } 264 | } 265 | 266 | 267 | if (jFieldVar.type().erasure().fullName().equals(List.class.getName())) { 268 | JExpression condition = new IsNullExpression(jFieldVar, false); 269 | JExpression ifTrue = codeModel.ref(Collections.class).staticInvoke("unmodifiableList").arg(jFieldVar); 270 | JExpression ifFalse = JExpr._null(); 271 | getMethod.body()._return(new TernaryOperator(condition, ifTrue, ifFalse)); 272 | } else { 273 | getMethod.body()._return(jFieldVar); 274 | } 275 | } 276 | 277 | /** 278 | * builds method name out of field variable. 279 | * 280 | * @param fieldVariable 281 | * @return String returns formatted getter method name. 282 | */ 283 | private String getMethodFormattedString(String fieldVariable) { 284 | if (StringUtils.isNotBlank(fieldVariable) && StringUtils.length(fieldVariable) > 0) { 285 | return Constants.STRING_GET + Character.toTitleCase(fieldVariable.charAt(0)) + fieldVariable.substring(1); 286 | } 287 | return fieldVariable; 288 | } 289 | 290 | private void buildImplementation(List properties, String modelClassName) { 291 | try { 292 | JClass childInterfaceClass = codeModel.ref(generationConfig.getProjectSettings().getModelInterfacePackage() + "." + modelClassName); 293 | JDefinedClass implClass = this.implPackage._class(modelClassName + "Impl")._implements(childInterfaceClass); 294 | addSlingAnnotations(implClass, childInterfaceClass, null); 295 | addFieldVars(implClass, properties, Constants.PROPERTY_TYPE_PRIVATE); 296 | addGetters(implClass); 297 | addExportedTypeMethod(implClass); 298 | } catch (JClassAlreadyExistsException ex) { 299 | LOG.error("Failed to generate child implementation classes.", ex); 300 | } 301 | } 302 | 303 | private void setupFieldGetterAnnotations(JFieldVar jFieldVar, Property property) { 304 | boolean isFieldJsonExpose = false; 305 | String fieldJsonPropertyValue = ""; 306 | 307 | if (this.isAllowExporting) { 308 | isFieldJsonExpose = property.isShouldExporterExpose(); 309 | fieldJsonPropertyValue = property.getJsonProperty(); 310 | } 311 | 312 | this.fieldJsonExposeMap.put(jFieldVar.name(), isFieldJsonExpose); 313 | this.fieldJsonPropertyMap.put(jFieldVar.name(), fieldJsonPropertyValue); 314 | } 315 | 316 | private void addExportedTypeMethod(JDefinedClass jc) { 317 | if (this.isAllowExporting) { 318 | JFieldVar jFieldVar = jc.field(PRIVATE, codeModel.ref(Resource.class), "resource"); 319 | jFieldVar.annotate(codeModel.ref(SlingObject.class)); 320 | JMethod method = jc.method(JMod.PUBLIC, codeModel.ref(String.class), "getExportedType"); 321 | method.annotate(codeModel.ref(Override.class)); 322 | method.body()._return(jFieldVar.invoke("getResourceType")); 323 | } 324 | } 325 | } 326 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/InterfaceBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.javacodemodel; 21 | 22 | import com.adobe.cq.export.json.ComponentExporter; 23 | import com.adobe.aem.compgenerator.Constants; 24 | import com.adobe.aem.compgenerator.utils.CommonUtils; 25 | import com.adobe.aem.compgenerator.models.GenerationConfig; 26 | import com.adobe.aem.compgenerator.models.Property; 27 | import com.fasterxml.jackson.annotation.JsonIgnore; 28 | import com.fasterxml.jackson.annotation.JsonProperty; 29 | import com.sun.codemodel.JClassAlreadyExistsException; 30 | import com.sun.codemodel.JCodeModel; 31 | import com.sun.codemodel.JDefinedClass; 32 | import com.sun.codemodel.JDocComment; 33 | import com.sun.codemodel.JMethod; 34 | import com.sun.codemodel.JPackage; 35 | import com.sun.codemodel.JType; 36 | import org.apache.commons.lang3.StringUtils; 37 | import org.apache.commons.text.CaseUtils; 38 | import org.apache.commons.text.StringEscapeUtils; 39 | import org.apache.logging.log4j.LogManager; 40 | import org.apache.logging.log4j.Logger; 41 | 42 | import java.util.List; 43 | import java.util.Objects; 44 | 45 | import static com.sun.codemodel.JMod.NONE; 46 | import static com.adobe.aem.compgenerator.javacodemodel.JavaCodeModel.getFieldType; 47 | 48 | /** 49 | *

50 | * Manages generating the necessary details to create the sling model interface. 51 | *

52 | */ 53 | public class InterfaceBuilder extends JavaCodeBuilder { 54 | private static final Logger LOG = LogManager.getLogger(InterfaceBuilder.class); 55 | 56 | private final boolean isAllowExporting; 57 | private String interfaceClassName; 58 | 59 | /** 60 | * Construct a interface class builder 61 | * 62 | * @param codeModel The {@link JCodeModel codeModel} 63 | * @param generationConfig The {@link GenerationConfig generationConfig} 64 | * @param interfaceName The name of the interface 65 | */ 66 | public InterfaceBuilder(JCodeModel codeModel, GenerationConfig generationConfig, String interfaceName) { 67 | super(codeModel, generationConfig); 68 | this.interfaceClassName = interfaceName; 69 | this.isAllowExporting = generationConfig.getOptions().isAllowExporting(); 70 | } 71 | 72 | /** 73 | * Builds the interface class based on the configuration file. 74 | * 75 | * @return reference to the Interface 76 | */ 77 | public JDefinedClass build() { 78 | String comment = "Defines the {@code " 79 | + generationConfig.getJavaFormatedName() 80 | + "} Sling Model used for the {@code " 81 | + CommonUtils.getResourceType(generationConfig) 82 | + "} component."; 83 | 84 | return buildInterface(this.interfaceClassName, comment, globalProperties, sharedProperties, privateProperties); 85 | } 86 | 87 | /** 88 | * method just adds getters based on the properties of generationConfig 89 | * 90 | * @param jc the interface class 91 | * @param properties the list of properties 92 | */ 93 | private void addGettersWithoutFields(JDefinedClass jc, List properties) { 94 | if (properties != null && !properties.isEmpty()) { 95 | properties.stream() 96 | .filter(Objects::nonNull) 97 | .forEach(property -> { 98 | if (property.getType().equalsIgnoreCase(Constants.TYPE_HEADING)) { 99 | return; 100 | } 101 | 102 | JMethod method = jc.method(NONE, getGetterMethodReturnType(property), Constants.STRING_GET + property.getFieldGetterName()); 103 | addJavadocToMethod(method, property); 104 | 105 | if (this.isAllowExporting) { 106 | if (!property.isShouldExporterExpose()) { 107 | method.annotate(codeModel.ref(JsonIgnore.class)); 108 | } 109 | 110 | if (StringUtils.isNotBlank(property.getJsonProperty())) { 111 | method.annotate(codeModel.ref(JsonProperty.class)) 112 | .param("value", property.getJsonProperty()); 113 | } 114 | } 115 | 116 | if (property.getType().equalsIgnoreCase("multifield") 117 | && property.getItems().size() > 1) { 118 | buildMultifieldInterface(property); 119 | } 120 | }); 121 | } 122 | } 123 | 124 | private void buildMultifieldInterface(Property property) { 125 | if (!property.getUseExistingModel()) { 126 | String modelInterfaceName = JavaCodeModel.getMultifieldInterfaceName(property); 127 | String childComment = "Defines the {@code " 128 | + modelInterfaceName 129 | + "} Sling Model used for the multifield in {@code " 130 | + CommonUtils.getResourceType(generationConfig) 131 | + "} component."; 132 | 133 | buildInterface(modelInterfaceName, childComment, property.getItems()); 134 | } 135 | } 136 | 137 | @SafeVarargs 138 | private final JDefinedClass buildInterface(String interfaceName, String comment, List... propertiesLists) { 139 | try { 140 | JPackage jPackage = codeModel._package(generationConfig.getProjectSettings().getModelInterfacePackage()); 141 | JDefinedClass interfaceClass = jPackage._interface(interfaceName); 142 | interfaceClass.javadoc().append(comment); 143 | interfaceClass.annotate(codeModel.ref("org.osgi.annotation.versioning.ConsumerType")); 144 | 145 | if (this.isAllowExporting) { 146 | interfaceClass._extends(codeModel.ref(ComponentExporter.class)); 147 | } 148 | if (propertiesLists != null) { 149 | for (List properties : propertiesLists) { 150 | addGettersWithoutFields(interfaceClass, properties); 151 | } 152 | } 153 | return interfaceClass; 154 | } catch (JClassAlreadyExistsException e) { 155 | LOG.error("Failed to generate child interface.", e); 156 | } 157 | 158 | return null; 159 | } 160 | 161 | /** 162 | * Gets the return type of the getter method based on what type of property it is referring to. 163 | * 164 | * @param property the property for which the return type is calculated. 165 | * @return the type being returned by the getter 166 | */ 167 | private JType getGetterMethodReturnType(final Property property) { 168 | String fieldType = getFieldType(property); 169 | if (property.getType().equalsIgnoreCase(Constants.TYPE_MULTIFIELD)) { 170 | if (property.getItems().size() == 1) { 171 | return codeModel.ref(fieldType).narrow(codeModel.ref(getFieldType(property.getItems().get(0)))); 172 | } else { 173 | String narrowedClassName = StringUtils.defaultString(property.getModelName(), 174 | CaseUtils.toCamelCase(property.getField(), true) + "Multifield"); 175 | return codeModel.ref(fieldType).narrow(codeModel.ref(narrowedClassName)); 176 | } 177 | } else if (property.getType().equalsIgnoreCase(Constants.TYPE_TAGFIELD)) { 178 | return codeModel.ref(fieldType).narrow(String.class); 179 | } else { 180 | return codeModel.ref(fieldType); 181 | } 182 | } 183 | 184 | /** 185 | * Adds Javadoc to the method based on the information in the property and the generation config options. 186 | * 187 | * @param method 188 | * @param property 189 | */ 190 | private void addJavadocToMethod(JMethod method, Property property) { 191 | String javadocStr = null; 192 | if (StringUtils.isNotBlank(property.getJavadoc())) { 193 | javadocStr = property.getJavadoc(); 194 | } else if (generationConfig.getOptions() != null && generationConfig.getOptions().isHasGenericJavadoc()) { 195 | javadocStr = "Get the " + property.getField() + "."; 196 | } 197 | if (StringUtils.isNotBlank(javadocStr)) { 198 | JDocComment javadoc = method.javadoc(); 199 | javadoc.append(javadocStr).append(""); 200 | javadoc.append("\n\n@return " + StringEscapeUtils.escapeHtml4(getGetterMethodReturnType(property).name())); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/IsNullExpression.java: -------------------------------------------------------------------------------- 1 | package com.adobe.aem.compgenerator.javacodemodel; 2 | 3 | import com.sun.codemodel.JFieldVar; 4 | import com.sun.codemodel.JExpressionImpl; 5 | import com.sun.codemodel.JFormatter; 6 | 7 | final class IsNullExpression extends JExpressionImpl { 8 | private final JFieldVar variable; 9 | private final boolean isNullType; 10 | 11 | public IsNullExpression(JFieldVar variable, boolean isNullType) { 12 | this.variable = variable; 13 | this.isNullType = isNullType; 14 | } 15 | 16 | @Override 17 | public void generate(JFormatter f) { 18 | if (this.isNullType) { 19 | f.g(this.variable).p(" == null"); 20 | } else { 21 | f.g(this.variable).p(" != null"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/JavaCodeBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.javacodemodel; 21 | 22 | import com.adobe.aem.compgenerator.models.GenerationConfig; 23 | import com.adobe.aem.compgenerator.models.Property; 24 | import com.adobe.aem.compgenerator.utils.CommonUtils; 25 | import com.sun.codemodel.JCodeModel; 26 | import org.apache.commons.lang3.StringUtils; 27 | 28 | import java.util.Collections; 29 | import java.util.HashSet; 30 | import java.util.List; 31 | import java.util.Objects; 32 | import java.util.Set; 33 | import java.util.stream.Collectors; 34 | 35 | public abstract class JavaCodeBuilder { 36 | 37 | protected final JCodeModel codeModel; 38 | protected final GenerationConfig generationConfig; 39 | protected final List globalProperties; 40 | protected final List sharedProperties; 41 | protected final List privateProperties; 42 | 43 | protected JavaCodeBuilder(JCodeModel codeModel, GenerationConfig generationConfig) { 44 | this.codeModel = codeModel; 45 | this.generationConfig = generationConfig; 46 | 47 | Set occurredProperties = new HashSet<>(); 48 | 49 | this.globalProperties = filterProperties(occurredProperties, 50 | CommonUtils.getSortedPropertiesBasedOnTabs(generationConfig.getOptions().getGlobalProperties(), generationConfig.getOptions().getGlobalTabProperties())); 51 | occurredProperties.addAll(this.globalProperties); 52 | 53 | this.sharedProperties = filterProperties(occurredProperties, 54 | CommonUtils.getSortedPropertiesBasedOnTabs(generationConfig.getOptions().getSharedProperties(), generationConfig.getOptions().getSharedTabProperties())); 55 | occurredProperties.addAll(this.sharedProperties); 56 | 57 | this.privateProperties = filterProperties(occurredProperties, 58 | CommonUtils.getSortedPropertiesBasedOnTabs(generationConfig.getOptions().getProperties(), generationConfig.getOptions().getTabProperties())); 59 | occurredProperties.addAll(this.privateProperties); 60 | 61 | } 62 | 63 | /** 64 | * Filters the given properties for invalid fields and returns all that are not contained in occurredProperties. 65 | * 66 | * @param occurredProperties 67 | * @param originalProperties 68 | * @return filtered properties 69 | */ 70 | private static List filterProperties(Set occurredProperties, List originalProperties) { 71 | List properties; 72 | if (originalProperties != null) { 73 | properties = originalProperties.stream() 74 | .filter(Objects::nonNull) 75 | .filter(property -> StringUtils.isNotBlank(property.getField())) 76 | .filter(property -> StringUtils.isNotBlank(property.getType())) 77 | .filter(property -> !(occurredProperties.contains(property))) 78 | .collect(Collectors.toList()); 79 | } else { 80 | properties = Collections.emptyList(); 81 | } 82 | return properties; 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/JavaCodeModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.javacodemodel; 21 | 22 | import com.adobe.aem.compgenerator.Constants; 23 | import com.adobe.aem.compgenerator.models.GenerationConfig; 24 | import com.adobe.aem.compgenerator.models.Property; 25 | import com.adobe.aem.compgenerator.utils.CommonUtils; 26 | import com.sun.codemodel.*; 27 | import org.apache.commons.lang3.StringUtils; 28 | import org.apache.commons.text.CaseUtils; 29 | import org.apache.logging.log4j.LogManager; 30 | import org.apache.logging.log4j.Logger; 31 | 32 | import java.io.File; 33 | import java.io.IOException; 34 | import java.util.Iterator; 35 | import java.util.List; 36 | 37 | /** 38 | * Root of the code. 39 | *

40 | *

41 | * Here's your JavaCodeModel application. 42 | * 43 | *

 44 |  * JavaCodeModel jcm = new JavaCodeModel();
 45 |  *
 46 |  * // generate source code and write them from jcm.
 47 |  * jcm.buildSlingModel(generationConfig);
 48 |  * ...
 49 |  * 
50 | *

51 | * JavaCodeModel creates source code of your sling-model interface and implementation 52 | * using user data config configuration object. 53 | */ 54 | public class JavaCodeModel { 55 | private static final Logger LOG = LogManager.getLogger(JavaCodeModel.class); 56 | 57 | private final JCodeModel codeModel; 58 | private final JCodeModel codeModelTest; 59 | 60 | private GenerationConfig generationConfig; 61 | private JDefinedClass jc; 62 | private JDefinedClass jcImpl; 63 | 64 | private List globalProperties; 65 | private List sharedProperties; 66 | private List privateProperties; 67 | 68 | public JavaCodeModel() { 69 | this.codeModel = new JCodeModel(); 70 | this.codeModelTest = new JCodeModel(); 71 | } 72 | 73 | /** 74 | * Builds your slingModel interface and implementation class with all required 75 | * sling annotation, fields and getters based on the generationConfig. 76 | * 77 | * @param generationConfig the configuration for generating the java code 78 | */ 79 | public void buildSlingModel(GenerationConfig generationConfig) { 80 | try { 81 | this.generationConfig = generationConfig; 82 | buildInterface(); 83 | buildImplClass(); 84 | if (generationConfig.getOptions().isHasTestClass()) { 85 | buildTestClass(); 86 | } 87 | generateCodeFiles(); 88 | String withTestsStr = generationConfig.getOptions().isHasTestClass() ? "with test class " : StringUtils.EMPTY; 89 | LOG.info("--------------* Sling Model {}successfully generated *--------------", withTestsStr); 90 | 91 | 92 | } catch (JClassAlreadyExistsException | IOException e) { 93 | LOG.error("Failed to create sling model.", e); 94 | } 95 | } 96 | 97 | /** 98 | * Builds your slingModel test class with all required Test annotation, 99 | * method stubs based on the generationConfig. 100 | */ 101 | private void buildTestClass() throws JClassAlreadyExistsException { 102 | JPackage slingModelImplPackage = codeModel._package(jcImpl._package().name()); 103 | for (Iterator it = slingModelImplPackage.classes(); it.hasNext(); ) { 104 | JDefinedClass slingModelImplClass = it.next(); 105 | TestClassBuilder builder = new TestClassBuilder(codeModelTest, generationConfig, slingModelImplClass.name() + "Test", slingModelImplClass); 106 | builder.build(); 107 | } 108 | } 109 | 110 | /** 111 | * Builds your slingModel interface with all required annotation, 112 | * fields and getters based on the generationConfig. 113 | */ 114 | private void buildInterface() { 115 | InterfaceBuilder builder = new InterfaceBuilder(codeModel, generationConfig, generationConfig.getJavaFormatedName()); 116 | jc = builder.build(); 117 | } 118 | 119 | /** 120 | * Builds your slingModel implementation with all required sling annotation, 121 | * fields and getters based on the generationConfig. 122 | */ 123 | private void buildImplClass() throws JClassAlreadyExistsException { 124 | ImplementationBuilder builder = new ImplementationBuilder(codeModel, generationConfig, generationConfig.getJavaFormatedName() + "Impl", jc); 125 | jcImpl = builder.build(CommonUtils.getResourceType(generationConfig)); 126 | } 127 | 128 | /** 129 | * Generates the slingModel file based on values from the config and the current codeModel object. 130 | */ 131 | private void generateCodeFiles() throws IOException { 132 | // RenameFileCodeWritern to rename existing files 133 | File bundlePath = new File(generationConfig.getProjectSettings().getBundlePath()); 134 | if (!bundlePath.exists()) { 135 | bundlePath.mkdirs(); 136 | } 137 | CodeWriter codeWriter = new RenameFileCodeWriter(bundlePath); 138 | 139 | // PrologCodeWriter to prepend the copyright template in each file 140 | String templateString = CommonUtils.getTemplateFileAsString(Constants.TEMPLATE_COPYRIGHT_JAVA, generationConfig); 141 | PrologCodeWriter prologCodeWriter = new PrologCodeWriter(codeWriter, templateString); 142 | 143 | codeModel.build(prologCodeWriter); 144 | if (generationConfig.getOptions().isHasTestClass()) { 145 | File testPath = new File(generationConfig.getProjectSettings().getTestPath()); 146 | if (!testPath.exists()) { 147 | testPath.mkdirs(); 148 | } 149 | CodeWriter codeWriterTest = new RenameFileCodeWriter(testPath); 150 | PrologCodeWriter prologCodeWriterTest = new PrologCodeWriter(codeWriterTest, templateString); 151 | codeModelTest.build(prologCodeWriterTest); 152 | } 153 | 154 | } 155 | 156 | /** 157 | * Generates the sling model interface name for a multifield type 158 | * 159 | * @param property the property definition for the multifield type 160 | * @return the sling model interface name 161 | */ 162 | public static String getMultifieldInterfaceName(Property property) { 163 | return StringUtils.defaultString(property.getModelName(), CaseUtils.toCamelCase(property.getField(), true) + "Multifield"); 164 | } 165 | 166 | /** 167 | * Get the java fieldType based on the type input in the generationConfig 168 | * 169 | * @param property the property definition 170 | * @return String returns relevant java type of string passed in. 171 | */ 172 | public static String getFieldType(Property property) { 173 | String type = property.getType(); 174 | if (type.equalsIgnoreCase("textfield") 175 | || type.equalsIgnoreCase("pagefield") 176 | || type.equalsIgnoreCase("pathfield") 177 | || type.equalsIgnoreCase("textarea") 178 | || type.equalsIgnoreCase("hidden") 179 | || type.equalsIgnoreCase("select") 180 | || type.equalsIgnoreCase("radiogroup")) { 181 | return "java.lang.String"; 182 | } else if (type.equalsIgnoreCase("numberfield")) { 183 | return "java.lang.Long"; 184 | } else if (type.equalsIgnoreCase("checkbox")) { 185 | return "java.lang.Boolean"; 186 | } else if (type.equalsIgnoreCase("datepicker")) { 187 | return "java.util.Calendar"; 188 | } else if (type.equalsIgnoreCase("image")) { 189 | return "com.adobe.cq.wcm.core.components.models.Image"; 190 | } else if (type.equalsIgnoreCase("multifield") 191 | || type.equalsIgnoreCase("tagfield")) { 192 | return "java.util.List"; 193 | } 194 | return type; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/PrologCodeWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.javacodemodel; 21 | 22 | import java.io.IOException; 23 | import java.io.PrintWriter; 24 | import java.io.Writer; 25 | 26 | import com.sun.codemodel.CodeWriter; 27 | import com.sun.codemodel.JPackage; 28 | import com.sun.codemodel.writer.FilterCodeWriter; 29 | 30 | public class PrologCodeWriter extends FilterCodeWriter { 31 | 32 | /** prolog comment */ 33 | private final String prolog; 34 | 35 | /** 36 | * @param core 37 | * This CodeWriter will be used to actually create a storage for files. 38 | * PrologCodeWriter simply decorates this underlying CodeWriter by 39 | * adding prolog comments. 40 | * @param prolog 41 | * Strings that will be added as comments 42 | * and will be inserted at the beginning of each line to make it 43 | * a valid Java comment. 44 | */ 45 | public PrologCodeWriter(CodeWriter core, String prolog ) { 46 | super(core); 47 | this.prolog = prolog; 48 | } 49 | 50 | @Override 51 | public Writer openSource(JPackage pkg, String fileName) throws IOException { 52 | Writer w = super.openSource(pkg,fileName); 53 | 54 | PrintWriter out = new PrintWriter(w); 55 | // write prolog if this is a java source file 56 | if (prolog != null) { 57 | 58 | String s = prolog; 59 | int idx; 60 | while ((idx = s.indexOf('\n')) != -1) { 61 | out.println(s.substring(0, idx)); 62 | s = s.substring(idx + 1); 63 | } 64 | } 65 | out.flush(); 66 | return w; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/RenameFileCodeWriter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.javacodemodel; 21 | 22 | import com.adobe.aem.compgenerator.utils.CommonUtils; 23 | import com.sun.codemodel.JPackage; 24 | import com.sun.codemodel.writer.FileCodeWriter; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | 29 | public class RenameFileCodeWriter extends FileCodeWriter { 30 | 31 | private final File target; 32 | 33 | public RenameFileCodeWriter(File target) throws IOException { 34 | super(target); 35 | this.target = target; 36 | } 37 | 38 | @Override 39 | protected File getFile(JPackage pkg, String fileName) throws IOException { 40 | File dir; 41 | if (pkg.isUnnamed()) { 42 | dir = target; 43 | } else { 44 | dir = new File(target, pkg.name().replace('.', File.separatorChar)); 45 | } 46 | 47 | if (!dir.exists()) { 48 | dir.mkdirs(); 49 | } 50 | 51 | String filePath = new File(dir, fileName).getPath(); 52 | File codeFile = CommonUtils.getNewFileAtPathAndRenameExisting(filePath); 53 | return codeFile; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/TernaryOperator.java: -------------------------------------------------------------------------------- 1 | package com.adobe.aem.compgenerator.javacodemodel; 2 | 3 | import com.sun.codemodel.JExpression; 4 | import com.sun.codemodel.JExpressionImpl; 5 | import com.sun.codemodel.JFormatter; 6 | 7 | final class TernaryOperator extends JExpressionImpl { 8 | 9 | JExpression condition; 10 | JExpression ifTrue; 11 | JExpression ifFalse; 12 | 13 | public TernaryOperator(JExpression condition, JExpression ifTrue, JExpression ifFalse) { 14 | this.condition = condition; 15 | this.ifTrue = ifTrue; 16 | this.ifFalse = ifFalse; 17 | } 18 | 19 | @Override 20 | public void generate(JFormatter f) { 21 | f.g(condition).p(" ? ").g(ifTrue).p(" : ").g(ifFalse); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/TestClassBuilder.java: -------------------------------------------------------------------------------- 1 | package com.adobe.aem.compgenerator.javacodemodel; 2 | 3 | import com.adobe.aem.compgenerator.Constants; 4 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 5 | import com.adobe.aem.compgenerator.models.GenerationConfig; 6 | import com.sun.codemodel.*; 7 | import org.apache.commons.lang3.StringUtils; 8 | import org.junit.Assert; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.jupiter.api.BeforeEach; 12 | 13 | import java.util.Map; 14 | 15 | /** 16 | * The type Test class builder. 17 | */ 18 | public class TestClassBuilder extends JavaCodeBuilder { 19 | 20 | private static final String JUNIT_UNSUPPORTED_VERSION_EXCEPTION = "Component generator either supports Junit 5 or Junit 4."; 21 | 22 | private final String className; 23 | private final JDefinedClass implementationClass; 24 | private final JPackage testPackage; 25 | 26 | /** 27 | * Instantiates a new Test class builder. 28 | * 29 | * @param codeModelTest the code model 30 | * @param generationConfig the generation config 31 | * @param className the class name 32 | * @param implementationClass the implementation class 33 | */ 34 | protected TestClassBuilder(JCodeModel codeModelTest, GenerationConfig generationConfig, String className, 35 | JDefinedClass implementationClass) { 36 | super(codeModelTest, generationConfig); 37 | this.className = className; 38 | this.implementationClass = implementationClass; 39 | this.testPackage = codeModelTest._package(generationConfig.getProjectSettings().getModelImplPackage()); 40 | } 41 | 42 | /** 43 | * Build. 44 | * 45 | * @throws JClassAlreadyExistsException the j class already exists exception 46 | */ 47 | public void build() throws JClassAlreadyExistsException { 48 | JDefinedClass jc = this.testPackage._class(this.className); 49 | addSetupMethod(jc); 50 | addTestMethods(jc, implementationClass); 51 | } 52 | 53 | private void addSetupMethod(JDefinedClass jc) { 54 | JMethod setUpMethod = jc.method(JMod.PUBLIC, jc.owner().VOID, "setUp") 55 | ._throws(Exception.class); 56 | int junitVersion = generationConfig.getOptions().getJunitVersion(); 57 | if (junitVersion == Constants.JUNIT_VERSION_5) { 58 | setUpMethod.annotate(codeModel.ref(BeforeEach.class)); 59 | } else if ((junitVersion == Constants.JUNIT_VERSION_4)) { 60 | setUpMethod.annotate(codeModel.ref(Before.class)); 61 | } else { 62 | throw new GeneratorException(JUNIT_UNSUPPORTED_VERSION_EXCEPTION); 63 | } 64 | JBlock block = setUpMethod.body(); 65 | block.directStatement("// TODO: Test Setup"); 66 | } 67 | 68 | private void addTestMethods(JDefinedClass jc, JDefinedClass implementationClass) { 69 | Map fieldVars = implementationClass.fields(); 70 | if (!fieldVars.isEmpty()) { 71 | for (Map.Entry entry : fieldVars.entrySet()) { 72 | if (entry.getValue() != null) { 73 | int junitVersion = generationConfig.getOptions().getJunitVersion(); 74 | if (junitVersion == Constants.JUNIT_VERSION_5) { 75 | addTestMethod(jc, entry.getValue(), org.junit.jupiter.api.Test.class, org.junit.jupiter.api.Assertions.class); 76 | } else if (junitVersion == Constants.JUNIT_VERSION_4) { 77 | addTestMethod(jc, entry.getValue(), Test.class, Assert.class); 78 | } else { 79 | throw new GeneratorException(JUNIT_UNSUPPORTED_VERSION_EXCEPTION); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | 86 | private void addTestMethod(JDefinedClass jc, JFieldVar jFieldVar, Class testClassAnnotation, 87 | Class assertClassAnnotation) { 88 | JMethod getMethod = jc.method(JMod.PUBLIC, jc.owner().VOID, getMethodFormattedString(jFieldVar.name())); 89 | getMethod.annotate(codeModel.ref(testClassAnnotation)); 90 | JBlock block = getMethod.body(); 91 | JClass assertClassRef = codeModel.ref(assertClassAnnotation); 92 | JInvocation assertNotYetImplemented = assertClassRef.staticInvoke("fail").arg("Not Yet Implemented"); 93 | block.add(assertNotYetImplemented); 94 | } 95 | 96 | private String getMethodFormattedString(String fieldVariable) { 97 | if (StringUtils.isNotBlank(fieldVariable) && StringUtils.length(fieldVariable) > 0) { 98 | return Constants.STRING_TEST 99 | + StringUtils.capitalize(Constants.STRING_GET) 100 | + Character.toTitleCase(fieldVariable.charAt(0)) + fieldVariable.substring(1); 101 | } 102 | return fieldVariable; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/javacodemodel/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | /** 21 | * Package contains the java code generation classes. 22 | * @since 1.0 23 | */ 24 | package com.adobe.aem.compgenerator.javacodemodel; -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/BaseModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.models; 21 | 22 | public interface BaseModel { 23 | 24 | boolean isValid(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/GenerationConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.models; 21 | 22 | import com.fasterxml.jackson.annotation.JsonProperty; 23 | import org.apache.commons.lang3.StringUtils; 24 | import org.apache.commons.text.CaseUtils; 25 | 26 | public class GenerationConfig implements BaseModel { 27 | 28 | @JsonProperty("name") 29 | private String name; 30 | 31 | @JsonProperty("title") 32 | private String title; 33 | 34 | @JsonProperty("group") 35 | private String group; 36 | 37 | @JsonProperty("type") 38 | private String type; 39 | 40 | @JsonProperty("project-settings") 41 | private ProjectSettings projectSettings; 42 | 43 | @JsonProperty("options") 44 | private Options options; 45 | 46 | // Non-JSON property runtime variables 47 | private String compDir; 48 | private String javaFormatedName; 49 | 50 | 51 | public String getName() { 52 | if(StringUtils.isNotBlank(name)){ 53 | return name.replaceAll("[^a-z0-9+]", "-"); 54 | } 55 | return name; 56 | } 57 | 58 | public void setName(String name) { 59 | this.name = name; 60 | } 61 | 62 | public String getJavaFormatedName() { 63 | if(StringUtils.isNotBlank(name)){ 64 | javaFormatedName = CaseUtils.toCamelCase(name.replaceAll("-", " "), true); 65 | } 66 | return javaFormatedName; 67 | } 68 | 69 | public String getTitle() { 70 | return title; 71 | } 72 | 73 | public void setTitle(String title) { 74 | this.title = title; 75 | } 76 | 77 | public String getGroup() { 78 | return group; 79 | } 80 | 81 | public void setGroup(String group) { 82 | this.group = group; 83 | } 84 | 85 | public String getType() { 86 | return type; 87 | } 88 | 89 | public void setType(String type) { 90 | this.type = type; 91 | } 92 | 93 | public ProjectSettings getProjectSettings() { 94 | return projectSettings; 95 | } 96 | 97 | public void setProjectSettings(ProjectSettings projectSettings) { 98 | this.projectSettings = projectSettings; 99 | } 100 | 101 | public Options getOptions() { 102 | return options; 103 | } 104 | 105 | public void setOptions(Options options) { 106 | this.options = options; 107 | } 108 | 109 | public String getCompDir() { 110 | return compDir; 111 | } 112 | 113 | public void setCompDir(String compDir) { 114 | this.compDir = compDir; 115 | } 116 | 117 | @Override 118 | public boolean isValid() { 119 | return StringUtils.isNotBlank(name) && StringUtils.isNotBlank(type) 120 | && isValidTestPath(projectSettings.getTestPath(), options.isHasTestClass()); 121 | } 122 | 123 | private boolean isValidTestPath(String testPath, boolean hasTestClass) { 124 | return !hasTestClass || StringUtils.isNotBlank(testPath); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/Options.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.models; 21 | 22 | import com.fasterxml.jackson.annotation.JsonProperty; 23 | import org.apache.commons.lang3.ArrayUtils; 24 | 25 | import java.util.List; 26 | 27 | public class Options implements BaseModel { 28 | 29 | private final static String[] DEFAULT_ADAPTABLES = new String[] { "request" }; 30 | 31 | @JsonProperty("generic-javadoc") 32 | private boolean hasGenericJavadoc; 33 | 34 | @JsonProperty("js") 35 | private boolean hasJs; 36 | 37 | @JsonProperty("jstxt") 38 | private boolean hasJsTxt; 39 | 40 | @JsonProperty("css") 41 | private boolean hasCss; 42 | 43 | @JsonProperty("csstxt") 44 | private boolean hasCssTxt; 45 | 46 | @JsonProperty("html") 47 | private boolean hasHtml; 48 | 49 | @JsonProperty("testclass") 50 | private boolean hasTestClass; 51 | 52 | @JsonProperty("junit-major-version") 53 | private int junitVersion; 54 | 55 | @JsonProperty("slingmodel") 56 | private boolean hasSlingModel; 57 | 58 | @JsonProperty("content-exporter") 59 | private boolean allowExporting; 60 | 61 | @JsonProperty("model-adaptables") 62 | private String[] modelAdaptables; 63 | 64 | @JsonProperty(value = "html-content") 65 | private boolean htmlContent; 66 | 67 | @JsonProperty("properties") 68 | private List properties; 69 | 70 | @JsonProperty("properties-global") 71 | private List globalProperties; 72 | 73 | @JsonProperty("properties-shared") 74 | private List sharedProperties; 75 | 76 | @JsonProperty("properties-tabs") 77 | private List tabProperties; 78 | 79 | @JsonProperty("properties-shared-tabs") 80 | private List sharedTabProperties; 81 | 82 | @JsonProperty("properties-global-tabs") 83 | private List globalTabProperties; 84 | 85 | public boolean isHasGenericJavadoc() { 86 | return hasGenericJavadoc; 87 | } 88 | 89 | public void setHasGenericJavadoc(boolean hasGenericJavadoc) { 90 | this.hasGenericJavadoc = hasGenericJavadoc; 91 | } 92 | 93 | public boolean isHasJs() { 94 | return hasJs; 95 | } 96 | 97 | public void setHasJs(boolean hasJs) { 98 | this.hasJs = hasJs; 99 | } 100 | 101 | public boolean isHasJsTxt() { 102 | return hasJsTxt; 103 | } 104 | 105 | public boolean isHasTestClass() { 106 | return hasTestClass; 107 | } 108 | 109 | public void setHasTestClass(boolean hasTestClass) { 110 | this.hasTestClass = hasTestClass; 111 | } 112 | 113 | public void setJunitVersion(int junitVersion) { 114 | this.junitVersion = junitVersion; 115 | } 116 | 117 | public int getJunitVersion() { 118 | return junitVersion; 119 | } 120 | 121 | public void setHasJsTxt(final boolean hasJsTxt) { 122 | this.hasJsTxt = hasJsTxt; 123 | } 124 | 125 | public boolean isHasCss() { 126 | return hasCss; 127 | } 128 | 129 | public void setHasCss(boolean hasCss) { 130 | this.hasCss = hasCss; 131 | } 132 | 133 | public boolean isHasCssTxt() { 134 | return hasCssTxt; 135 | } 136 | 137 | public void setHasCssTxt(final boolean hasCssTxt) { 138 | this.hasCssTxt = hasCssTxt; 139 | } 140 | 141 | public boolean isHasHtml() { 142 | return hasHtml; 143 | } 144 | 145 | public void setHasHtml(boolean hasHtml) { 146 | this.hasHtml = hasHtml; 147 | } 148 | 149 | public boolean isHasSlingModel() { 150 | return hasSlingModel; 151 | } 152 | 153 | public void setHasSlingModel(boolean hasSlingModel) { 154 | this.hasSlingModel = hasSlingModel; 155 | } 156 | 157 | public boolean isAllowExporting() { 158 | return this.allowExporting; 159 | } 160 | 161 | public void setAllowExporting(boolean allowExporting) { 162 | this.allowExporting = allowExporting; 163 | } 164 | 165 | public String[] getModelAdaptables() { 166 | if (ArrayUtils.isEmpty(modelAdaptables)) { 167 | return DEFAULT_ADAPTABLES; 168 | } 169 | return modelAdaptables; 170 | } 171 | 172 | public void setModelAdaptables(final String[] modelAdaptables) { 173 | this.modelAdaptables = modelAdaptables; 174 | } 175 | 176 | public boolean isHtmlContent() { 177 | return htmlContent; 178 | } 179 | 180 | public void setHtmlContent(boolean htmlContent) { 181 | this.htmlContent = htmlContent; 182 | } 183 | 184 | public List getProperties() { 185 | return properties; 186 | } 187 | 188 | public void setProperties(List properties) { 189 | this.properties = properties; 190 | } 191 | 192 | public List getGlobalProperties() { 193 | return globalProperties; 194 | } 195 | 196 | public void setGlobalProperties(List globalProperties) { 197 | this.globalProperties = globalProperties; 198 | } 199 | 200 | public List getSharedProperties() { 201 | return sharedProperties; 202 | } 203 | 204 | public void setSharedProperties(List sharedProperties) { 205 | this.sharedProperties = sharedProperties; 206 | } 207 | 208 | public List getTabProperties() { 209 | return tabProperties; 210 | } 211 | 212 | public void setTabProperties(List tabProperties) { 213 | this.tabProperties = tabProperties; 214 | } 215 | 216 | public List getSharedTabProperties() { 217 | return sharedTabProperties; 218 | } 219 | 220 | public void setSharedTabProperties(List sharedTabProperties) { 221 | this.sharedTabProperties = sharedTabProperties; 222 | } 223 | 224 | public List getGlobalTabProperties() { 225 | return globalTabProperties; 226 | } 227 | 228 | public void setGlobalTabProperties(List globalTabProperties) { 229 | this.globalTabProperties = globalTabProperties; 230 | } 231 | 232 | @Override 233 | public boolean isValid() { 234 | return true; 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/ProjectSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.models; 21 | 22 | import java.util.Calendar; 23 | 24 | import com.fasterxml.jackson.annotation.JsonProperty; 25 | import org.apache.commons.lang3.StringUtils; 26 | 27 | public class ProjectSettings implements BaseModel { 28 | 29 | @JsonProperty("code-owner") 30 | private String codeOwner; 31 | 32 | @JsonProperty("copyright-year") 33 | private String year = "" + Calendar.getInstance().get(Calendar.YEAR); 34 | 35 | @JsonProperty("model-interface-pkg") 36 | private String modelInterfacePackage; 37 | 38 | @JsonProperty("model-impl-pkg") 39 | private String modelImplPackage; 40 | 41 | @JsonProperty("component-path") 42 | private String componentPath; 43 | 44 | @JsonProperty("bundle-path") 45 | private String bundlePath; 46 | 47 | @JsonProperty("test-path") 48 | private String testPath; 49 | 50 | @JsonProperty("apps-path") 51 | private String appsPath; 52 | 53 | public String getCodeOwner() { 54 | return codeOwner; 55 | } 56 | 57 | public void setCodeOwner(final String codeOwner) { 58 | this.codeOwner = codeOwner; 59 | } 60 | 61 | public String getYear() { 62 | return year; 63 | } 64 | 65 | public void setYear(final String year) { 66 | this.year = year; 67 | if (StringUtils.isBlank(this.year)) { 68 | this.year = "" + Calendar.getInstance().get(Calendar.YEAR); 69 | } 70 | } 71 | 72 | public String getModelInterfacePackage() { 73 | return modelInterfacePackage; 74 | } 75 | 76 | public void setModelInterfacePackage(String modelInterfacePackage) { 77 | this.modelInterfacePackage = modelInterfacePackage; 78 | } 79 | 80 | public String getModelImplPackage() { 81 | return modelImplPackage; 82 | } 83 | 84 | public void setModelImplPackage(String modelImplPackage) { 85 | this.modelImplPackage = modelImplPackage; 86 | } 87 | 88 | public String getComponentPath() { 89 | return componentPath; 90 | } 91 | 92 | public void setComponentPath(String componentPath) { 93 | this.componentPath = componentPath; 94 | } 95 | 96 | public String getBundlePath() { 97 | return bundlePath; 98 | } 99 | 100 | public void setBundlePath(String bundlePath) { 101 | this.bundlePath = bundlePath; 102 | } 103 | 104 | public String getTestPath() { 105 | return testPath; 106 | } 107 | 108 | public void setTestPath(String testPath) { 109 | this.testPath = testPath; 110 | } 111 | 112 | public String getAppsPath() { 113 | return appsPath; 114 | } 115 | 116 | public void setAppsPath(String appsPath) { 117 | this.appsPath = appsPath; 118 | } 119 | 120 | @Override 121 | public boolean isValid() { 122 | return StringUtils.isNotBlank(modelInterfacePackage) && StringUtils.isNotBlank(modelImplPackage) 123 | && StringUtils.isNotBlank(componentPath) && StringUtils.isNotBlank(bundlePath) && StringUtils.isNotBlank(appsPath); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/Property.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.models; 21 | 22 | import com.fasterxml.jackson.annotation.JsonProperty; 23 | import org.apache.commons.lang3.StringUtils; 24 | import org.apache.commons.text.CaseUtils; 25 | 26 | import java.util.List; 27 | import java.util.Map; 28 | 29 | public class Property implements BaseModel { 30 | 31 | @JsonProperty("field") 32 | private String field; 33 | 34 | @JsonProperty("type") 35 | private String type; 36 | 37 | @JsonProperty("label") 38 | private String label; 39 | 40 | @JsonProperty("description") 41 | private String description; 42 | 43 | @JsonProperty("javadoc") 44 | private String javadoc; 45 | 46 | @JsonProperty("json-property") 47 | private String jsonProperty; 48 | 49 | @JsonProperty("json-expose") 50 | private boolean shouldExporterExpose; 51 | 52 | @JsonProperty("attributes") 53 | private Map attributes; 54 | 55 | @JsonProperty(value = "items") 56 | private List items; 57 | 58 | @JsonProperty(value = "model-name") 59 | private String modelName; 60 | 61 | @JsonProperty(value = "use-existing-model", defaultValue = "false") 62 | private boolean useExistingModel; 63 | 64 | public String getField() { 65 | if (StringUtils.isNotBlank(field)) { 66 | return field; 67 | } else if (StringUtils.isNoneBlank(label)) { 68 | return CaseUtils.toCamelCase(label.replaceAll("[^A-Za-z0-9+]", " "), false); 69 | } 70 | return field; 71 | } 72 | 73 | public String getFieldGetterName() { 74 | if (StringUtils.isNotBlank(field)) { 75 | return StringUtils.capitalize(field); 76 | } else if (StringUtils.isNoneBlank(label)) { 77 | return CaseUtils.toCamelCase(label.replaceAll("[^A-Za-z0-9+]", " "), true); 78 | } 79 | return field; 80 | } 81 | 82 | public void setField(String field) { 83 | this.field = field; 84 | } 85 | 86 | public String getType() { 87 | return type; 88 | } 89 | 90 | public void setType(String type) { 91 | this.type = type; 92 | } 93 | 94 | public String getLabel() { 95 | return label; 96 | } 97 | 98 | public void setLabel(String label) { 99 | this.label = label; 100 | } 101 | 102 | public Map getAttributes() { 103 | return attributes; 104 | } 105 | 106 | public void setAttributes(Map attributes) { 107 | this.attributes = attributes; 108 | } 109 | 110 | public String getDescription() { 111 | return description; 112 | } 113 | 114 | public void setDescription(String description) { 115 | this.description = description; 116 | } 117 | 118 | public String getJavadoc() { 119 | return javadoc; 120 | } 121 | 122 | public void setJavadoc(String javadoc) { 123 | this.javadoc = javadoc; 124 | } 125 | 126 | public List getItems() { 127 | return items; 128 | } 129 | 130 | public void setItems(List items) { 131 | this.items = items; 132 | } 133 | 134 | public String getModelName() { 135 | return modelName; 136 | } 137 | 138 | public void setModelName(String modelName) { 139 | this.modelName = modelName; 140 | } 141 | 142 | public boolean getUseExistingModel() { 143 | return useExistingModel; 144 | } 145 | 146 | public void setUseExistingModel(boolean useExistingModel) { 147 | this.useExistingModel = useExistingModel; 148 | } 149 | 150 | public boolean isShouldExporterExpose() { 151 | return shouldExporterExpose; 152 | } 153 | 154 | public void setShouldExporterExpose(boolean shouldExporterExpose) { 155 | this.shouldExporterExpose = shouldExporterExpose; 156 | } 157 | 158 | public String getJsonProperty() { 159 | return jsonProperty; 160 | } 161 | 162 | public void setJsonProperty(String jsonProperty) { 163 | this.jsonProperty = jsonProperty; 164 | } 165 | 166 | @Override 167 | public boolean isValid() { 168 | return true; 169 | } 170 | 171 | @Override 172 | public boolean equals(Object obj) { 173 | if (obj == this) { 174 | return true; 175 | } 176 | 177 | if (!(obj instanceof Property)) { 178 | return false; 179 | } 180 | 181 | Property property = (Property) obj; 182 | return property.getField().equals(this.getField()) && property.getType().equals(this.getType()); 183 | } 184 | 185 | @Override 186 | public int hashCode() { 187 | return getField().hashCode() + getType().hashCode(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/Tab.java: -------------------------------------------------------------------------------- 1 | package com.adobe.aem.compgenerator.models; 2 | 3 | import java.util.List; 4 | 5 | import org.apache.commons.lang3.StringUtils; 6 | 7 | import com.fasterxml.jackson.annotation.JsonProperty; 8 | 9 | public class Tab implements BaseModel { 10 | 11 | @JsonProperty("id") 12 | private String id; 13 | 14 | @JsonProperty("label") 15 | private String label; 16 | 17 | @JsonProperty("fields") 18 | private List fields; 19 | 20 | public String getId() { 21 | return id; 22 | } 23 | 24 | public void setId(String id) { 25 | this.id = id; 26 | } 27 | 28 | public String getLabel() { 29 | return label; 30 | } 31 | 32 | public void setLabel(String label) { 33 | this.label = label; 34 | } 35 | 36 | public List getFields() { 37 | return fields; 38 | } 39 | 40 | public void setFields(List fields) { 41 | this.fields = fields; 42 | } 43 | 44 | @Override 45 | public boolean isValid() { 46 | return StringUtils.isNotBlank(id) && fields != null && !fields.isEmpty(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/models/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | /** 21 | * Package contains model classes used by the application. 22 | * @since 1.0 23 | */ 24 | package com.adobe.aem.compgenerator.models; -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | /** 21 | * This is the core package for the application 22 | * @since 1.0 23 | */ 24 | package com.adobe.aem.compgenerator; -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/CommonUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.utils; 21 | 22 | import com.adobe.aem.compgenerator.Constants; 23 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 24 | import com.adobe.aem.compgenerator.models.BaseModel; 25 | import com.adobe.aem.compgenerator.models.GenerationConfig; 26 | import com.adobe.aem.compgenerator.models.Property; 27 | import com.adobe.aem.compgenerator.models.Tab; 28 | import com.fasterxml.jackson.databind.DeserializationFeature; 29 | import com.fasterxml.jackson.databind.ObjectMapper; 30 | import org.apache.commons.lang3.StringUtils; 31 | import org.apache.commons.text.StringSubstitutor; 32 | import org.apache.logging.log4j.LogManager; 33 | import org.apache.logging.log4j.Logger; 34 | 35 | import java.io.*; 36 | import java.nio.file.Files; 37 | import java.nio.file.Path; 38 | import java.nio.file.Paths; 39 | import java.text.SimpleDateFormat; 40 | import java.util.*; 41 | import java.util.function.Function; 42 | import java.util.stream.Collectors; 43 | 44 | public class CommonUtils { 45 | 46 | private static final Logger LOG = LogManager.getLogger(CommonUtils.class); 47 | private static final Date CURRENT_TIME = new Date(System.currentTimeMillis()); 48 | 49 | /** 50 | * Method to map JSON content from given file into given GenerationConfig type. 51 | * 52 | * @param jsonDataFile data-config file 53 | * @return GenerationConfig java class with the mapped content in json file 54 | */ 55 | public static GenerationConfig getComponentData(File jsonDataFile) { 56 | if (jsonDataFile.exists()) { 57 | try { 58 | ObjectMapper mapper = new ObjectMapper(); 59 | mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 60 | return mapper.readValue(jsonDataFile, GenerationConfig.class); 61 | } catch (IOException e) { 62 | throw new GeneratorException(String.format("Exception while reading config file. %n %s", e.getMessage()), e); 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | /** 69 | * Renames the file at the given path (if it exists) and returns a new File with the given path. 70 | * 71 | * @param path file path 72 | * @return File with the given path 73 | * @throws IOException exception 74 | */ 75 | public static File getNewFileAtPathAndRenameExisting(String path) throws IOException { 76 | File file = new File(path); 77 | if (file.exists()) { 78 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Constants.RENAME_FILE_DATE_PATTERN); 79 | String date = simpleDateFormat.format(CURRENT_TIME); 80 | File oldFile = new File(path + ".sv." + date); 81 | 82 | boolean isSuccess = file.renameTo(oldFile); 83 | if (isSuccess) { 84 | LOG.info("Replaced: " + path + " (Old file: " + oldFile.getName() + ")"); 85 | return file; 86 | } else { 87 | throw new IOException(); 88 | } 89 | } 90 | 91 | LOG.info("Created: " + path); 92 | return file; 93 | } 94 | 95 | /** 96 | * Method checks if the file exists and not empty. 97 | * 98 | * @param file file to check. 99 | * @return boolean return true when file not exists or length is zero. 100 | */ 101 | public static boolean isFileBlank(File file) { 102 | return file.exists() && file.length() != 0 ? false : true; 103 | } 104 | 105 | /** 106 | * Method to read the content of the provided template file as string. 107 | * 108 | * @param filePath Path to the template file in the project 109 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 110 | * @return String return content of the resource file as string or null when file not exists 111 | */ 112 | public static String getTemplateFileAsString(String filePath, GenerationConfig generationConfig) { 113 | try (InputStream inputStream = CommonUtils.class.getClassLoader().getResourceAsStream(filePath)) { 114 | BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); 115 | Map stringsToReplaceValueMap = getStringsToReplaceValueMap(generationConfig); 116 | StringSubstitutor stringSubstitutor = new StringSubstitutor(stringsToReplaceValueMap); 117 | String content = reader.lines().collect(Collectors.joining(System.lineSeparator())); 118 | return stringSubstitutor.replace(content); 119 | } catch (IOException e) { 120 | LOG.error("Failed to read " + filePath + " from the classpath.", e); 121 | } 122 | return null; 123 | } 124 | 125 | /** 126 | * Creates a new folder. 127 | * 128 | * @param folderPath The path where the folder gets created 129 | * @return Path 130 | * @throws Exception exception 131 | */ 132 | public static Path createFolder(String folderPath) throws Exception { 133 | Path path = Paths.get(folderPath); 134 | if (Files.notExists(path)) { 135 | return Files.createDirectories(path); 136 | } 137 | return path; 138 | } 139 | 140 | /** 141 | * Determines if the model included is valid and not null. 142 | * 143 | * @param model The {@link BaseModel} object 144 | * @return boolean 145 | */ 146 | public static boolean isModelValid(BaseModel model) { 147 | return model != null && model.isValid(); 148 | } 149 | 150 | /** 151 | * Creates a new file with the correct copyright text appearing at the top. 152 | * 153 | * @param path Full path including the new file name 154 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 155 | * @throws IOException exception 156 | */ 157 | public static void createFileWithCopyRight(String path, GenerationConfig generationConfig) throws IOException { 158 | String template = Constants.TEMPLATE_COPYRIGHT_JAVA; 159 | if (path.endsWith("js") || path.endsWith("java")) { 160 | template = Constants.TEMPLATE_COPYRIGHT_JAVA; 161 | } else if (path.endsWith("less")) { 162 | template = Constants.TEMPLATE_COPYRIGHT_CSS; 163 | } else if (path.endsWith("xml")) { 164 | template = Constants.TEMPLATE_COPYRIGHT_XML; 165 | } else if (path.endsWith("html")) { 166 | template = Constants.TEMPLATE_COPYRIGHT_HTL; 167 | } 168 | 169 | BufferedWriter writer = getFileWriterFromTemplate(path, template, generationConfig); 170 | writer.close(); 171 | } 172 | 173 | /** 174 | * Creates the css.txt or js.txt file for a clientLib. 175 | * 176 | * @param path Full path including the new file name 177 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 178 | * @param clientLibFileName The less/js file's name 179 | * @throws IOException exception 180 | */ 181 | public static void createClientlibTextFile(String path, 182 | GenerationConfig generationConfig, String clientLibFileName) throws IOException { 183 | 184 | BufferedWriter writer = getFileWriterFromTemplate(path, Constants.TEMPLATE_COPYRIGHT_TEXT, generationConfig); 185 | writer.newLine(); 186 | 187 | if (path.endsWith("js.txt")) { 188 | writer.write("#base=js"); 189 | } 190 | 191 | if (path.endsWith("css.txt")) { 192 | writer.write("#base=css"); 193 | } 194 | 195 | writer.newLine(); 196 | writer.newLine(); 197 | writer.write(clientLibFileName); 198 | writer.close(); 199 | } 200 | 201 | /** 202 | * Construct a resource type from the {@link GenerationConfig} object. 203 | * 204 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 205 | * @return String 206 | */ 207 | public static String getResourceType(GenerationConfig generationConfig) { 208 | return generationConfig.getProjectSettings().getComponentPath() + "/" 209 | + generationConfig.getType() + "/" + generationConfig.getName(); 210 | } 211 | 212 | /** 213 | * Creates a {@link BufferedWriter} from the provided 'template'. 214 | * 215 | * @param path Full path including the new file name 216 | * @param template The template to use when creating the {@link BufferedWriter} 217 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 218 | * @throws IOException exception 219 | */ 220 | private static BufferedWriter getFileWriterFromTemplate(String path, 221 | String template, GenerationConfig generationConfig) throws IOException { 222 | 223 | File file = getNewFileAtPathAndRenameExisting(path); 224 | String templateString = getTemplateFileAsString(template, generationConfig); 225 | BufferedWriter writer = new BufferedWriter(new FileWriter(file)); 226 | writer.write(templateString); 227 | return writer; 228 | } 229 | 230 | /** 231 | * Creates a map of strings to replace placeholder values on template files. 232 | * 233 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 234 | * @return Map 235 | */ 236 | private static Map getStringsToReplaceValueMap(GenerationConfig generationConfig) { 237 | if (generationConfig != null) { 238 | Map map = new HashMap<>(); 239 | map.put("name", generationConfig.getName()); 240 | map.put("title", generationConfig.getTitle()); 241 | map.put("sightly", StringUtils.uncapitalize(generationConfig.getJavaFormatedName())); 242 | map.put("slingModel", generationConfig.getProjectSettings().getModelInterfacePackage() + "." + generationConfig.getJavaFormatedName()); 243 | map.put("CODEOWNER", generationConfig.getProjectSettings().getCodeOwner()); 244 | map.put("YEAR", generationConfig.getProjectSettings().getYear()); 245 | map.put("htmlOutput", generationConfig.getOptions().isHtmlContent() ? HTMLUtils.renderHtml(generationConfig) : " "); 246 | return map; 247 | } 248 | return null; 249 | } 250 | 251 | 252 | /** 253 | * Gets the Sorted properties based on tabs. If the tabs are not present, all properties will be considered. 254 | * 255 | * @param properties The {@link Property} 256 | * @param tabs The {@link Tab} 257 | * @return List 258 | */ 259 | public static List getSortedPropertiesBasedOnTabs(List properties, List tabs) { 260 | List updatedPropertiesList = null; 261 | if (null != tabs && !tabs.isEmpty()) { 262 | List sortedProperties = new ArrayList<>(); 263 | Map propertiesMap = properties.stream() 264 | .collect(Collectors.toMap(Property::getField, Function.identity())); 265 | for (Tab tab : tabs) { 266 | sortedProperties.addAll(tab.getFields().stream().map(propertiesMap::get).collect(Collectors.toList())); 267 | } 268 | updatedPropertiesList = sortedProperties; 269 | } else { 270 | updatedPropertiesList = properties; 271 | } 272 | return updatedPropertiesList; 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/ComponentUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.utils; 21 | 22 | import com.adobe.aem.compgenerator.Constants; 23 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 24 | import com.adobe.aem.compgenerator.models.GenerationConfig; 25 | import org.apache.logging.log4j.LogManager; 26 | import org.apache.logging.log4j.Logger; 27 | import org.w3c.dom.Document; 28 | import org.w3c.dom.Element; 29 | 30 | import javax.xml.parsers.DocumentBuilderFactory; 31 | import java.nio.file.Path; 32 | 33 | /** 34 | *

35 | * ComponentUtils class helps in building elements of component 36 | * like folders, xml file, html with content based data-config file. 37 | */ 38 | 39 | public class ComponentUtils { 40 | 41 | private static final Logger LOG = LogManager.getLogger(ComponentUtils.class); 42 | 43 | private GenerationConfig generationConfig; 44 | 45 | public ComponentUtils(GenerationConfig config) { 46 | this.generationConfig = config; 47 | } 48 | 49 | /** 50 | * Builds your base folder structure of a component includes component folder 51 | * itself, _cq_dialog with field properties, dialogglobal with properties-global, 52 | * HTML, clientlibs folder. 53 | */ 54 | public void buildComponent() throws Exception { 55 | if (generationConfig == null) { 56 | throw new GeneratorException("Config file cannot be empty / null !!"); 57 | } 58 | 59 | //creates base component folder. 60 | createFolderWithContentXML(generationConfig.getCompDir(), Constants.TYPE_CQ_COMPONENT); 61 | 62 | //create _cq_dialog xml with user input properties in json. 63 | DialogUtils.createDialogXml(generationConfig, Constants.DIALOG_TYPE_DIALOG); 64 | 65 | //create dialogglobal xml file with user input global properties in json. 66 | if (generationConfig.getOptions().getGlobalProperties() != null && 67 | !generationConfig.getOptions().getGlobalProperties().isEmpty()) { 68 | DialogUtils.createDialogXml(generationConfig, Constants.DIALOG_TYPE_GLOBAL); 69 | } 70 | 71 | //create dialogshared xml file with user input global properties in json. 72 | if (generationConfig.getOptions().getSharedProperties() != null && 73 | !generationConfig.getOptions().getSharedProperties().isEmpty()) { 74 | DialogUtils.createDialogXml(generationConfig, Constants.DIALOG_TYPE_SHARED); 75 | } 76 | 77 | //create _cq_design_dialog xml. 78 | DesignDialogUtils.createDesignDialogXml(generationConfig, Constants.DIALOG_TYPE_DESIGN_DIALOG); 79 | 80 | //builds clientLib and placeholder files for js and css. 81 | createClientLibs(); 82 | 83 | //builds sightly html file using htl template from resource. 84 | createHtl(); 85 | 86 | LOG.info("--------------* Component '" + generationConfig.getName() + "' successfully generated *--------------"); 87 | 88 | } 89 | 90 | /** 91 | * Builds default clientlib structure with js and css file under folder. 92 | */ 93 | private void createClientLibs() { 94 | String clientLibDirPath = generationConfig.getCompDir() + "/clientlibs"; 95 | try { 96 | if (generationConfig.getOptions().isHasJs() || generationConfig.getOptions().isHasCss()) { 97 | createFolderWithContentXML(clientLibDirPath, Constants.TYPE_SLING_FOLDER); 98 | 99 | String clientLibSiteDirPath = clientLibDirPath + "/site"; 100 | createFolderWithContentXML(clientLibSiteDirPath, Constants.TYPE_CQ_CLIENTLIB_FOLDER); 101 | 102 | if (generationConfig.getOptions().isHasCss()) { 103 | String clientLibCssFolder = clientLibSiteDirPath + "/css"; 104 | CommonUtils.createFolder(clientLibCssFolder); 105 | 106 | String clientLibCssFileName = generationConfig.getName() + ".less"; 107 | String clientLibCssFilePath = clientLibCssFolder + "/" + clientLibCssFileName; 108 | CommonUtils.createFileWithCopyRight(clientLibCssFilePath, generationConfig); 109 | 110 | if (generationConfig.getOptions().isHasCssTxt()) { 111 | String clientLibCssTextFile = clientLibSiteDirPath + "/css.txt"; 112 | CommonUtils.createClientlibTextFile(clientLibCssTextFile, generationConfig, clientLibCssFileName); 113 | } 114 | } 115 | 116 | if (generationConfig.getOptions().isHasJs()) { 117 | String clientLibJsFolder = clientLibSiteDirPath + "/js"; 118 | CommonUtils.createFolder(clientLibJsFolder); 119 | 120 | String clientLibJsFileName = generationConfig.getName() + ".js"; 121 | String clientLibJsFilePath = clientLibJsFolder + "/" + clientLibJsFileName; 122 | CommonUtils.createFileWithCopyRight(clientLibJsFilePath, generationConfig); 123 | 124 | if (generationConfig.getOptions().isHasJsTxt()) { 125 | String clientLibJsTextFile = clientLibSiteDirPath + "/js.txt"; 126 | CommonUtils.createClientlibTextFile(clientLibJsTextFile, generationConfig, clientLibJsFileName); 127 | } 128 | } 129 | } 130 | } catch (Exception e) { 131 | throw new GeneratorException("Exception while creating clientLibs : " + clientLibDirPath, e); 132 | } 133 | } 134 | 135 | /** 136 | * Creates a folder on given path and adds content.xml file based on the folderType. 137 | * 138 | * @param path Full path including the new file name 139 | * @param folderType The 'jcr:primaryType' of the folder 140 | * @throws Exception exception 141 | */ 142 | private void createFolderWithContentXML(String path, String folderType) throws Exception { 143 | Path folderPath = CommonUtils.createFolder(path); 144 | try { 145 | Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 146 | Element rootElement = XMLUtils.createRootElement(doc, generationConfig); 147 | 148 | //set attributes based on folderType. 149 | if (folderType.equalsIgnoreCase(Constants.TYPE_CQ_COMPONENT)) { 150 | rootElement.setAttribute(Constants.JCR_PRIMARY_TYPE, folderType); 151 | rootElement.setAttribute(Constants.PROPERTY_JCR_TITLE, generationConfig.getTitle()); 152 | rootElement.setAttribute("componentGroup", generationConfig.getGroup()); 153 | } else if (folderType.equalsIgnoreCase(Constants.TYPE_SLING_FOLDER)) { 154 | rootElement.setAttribute(Constants.JCR_PRIMARY_TYPE, folderType); 155 | } else if (folderType.equalsIgnoreCase(Constants.TYPE_CQ_CLIENTLIB_FOLDER)) { 156 | rootElement.setAttribute(Constants.JCR_PRIMARY_TYPE, folderType); 157 | rootElement.setAttribute("allowProxy", "{Boolean}true"); 158 | String dotReplacedComponentPath = generationConfig.getProjectSettings().getComponentPath().replace("/", "."); 159 | rootElement.setAttribute("categories", "[" + dotReplacedComponentPath + "." + generationConfig.getName() + "]"); 160 | } 161 | 162 | doc.appendChild(rootElement); 163 | XMLUtils.transformDomToFile(doc, folderPath + "/" + Constants.FILENAME_CONTENT_XML); 164 | } catch (Exception e) { 165 | throw new GeneratorException("Exception while creating Folder/xml : " + path, e); 166 | } 167 | } 168 | 169 | /** 170 | * Create default HTML file based the provided template. 171 | */ 172 | private void createHtl() { 173 | try { 174 | CommonUtils.createFileWithCopyRight(generationConfig.getCompDir() 175 | + "/" + generationConfig.getName() + ".html", generationConfig); 176 | } catch (Exception e) { 177 | throw new GeneratorException("Exception while creating HTML : " + generationConfig.getCompDir(), e); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/DesignDialogUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.utils; 21 | 22 | import com.adobe.aem.compgenerator.Constants; 23 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 24 | import com.adobe.aem.compgenerator.models.GenerationConfig; 25 | import javax.xml.parsers.DocumentBuilderFactory; 26 | import org.w3c.dom.Document; 27 | import org.w3c.dom.Element; 28 | 29 | public class DesignDialogUtils extends DialogUtils { 30 | 31 | /** 32 | * Creates the design dialog xml by adding the style system tab. 33 | * 34 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 35 | * @param dialogType The design dialog type 36 | */ 37 | public static void createDesignDialogXml(final GenerationConfig generationConfig, String dialogType) { 38 | try { 39 | String designDialogPath = generationConfig.getCompDir() + "/" + dialogType; 40 | CommonUtils.createFolder(designDialogPath); 41 | 42 | Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 43 | Element rootElement = createDialogRoot(doc, generationConfig, dialogType); 44 | buildDesignDialogStructure(doc, rootElement); 45 | 46 | doc.appendChild(rootElement); 47 | XMLUtils.transformDomToFile(doc, designDialogPath + "/" + Constants.FILENAME_CONTENT_XML); 48 | } catch (Exception e) { 49 | throw new GeneratorException("Exception while creating Design Dialog xml.", e); 50 | } 51 | } 52 | 53 | /** 54 | * Fully builds the design dialog's structure on the {@link Element} 'root'. 55 | * 56 | * @param document The {@link Document} object 57 | * @param root The root node to append children nodes to 58 | */ 59 | private static void buildDesignDialogStructure(Document document, Element root) { 60 | Element containerElement = document.createElement("content"); 61 | containerElement.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 62 | containerElement.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_CONTAINER); 63 | 64 | Element tabsElement = document.createElement("tabs"); 65 | tabsElement.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 66 | tabsElement.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_TABS); 67 | tabsElement.setAttribute("maximized", "{Boolean}true"); 68 | 69 | Element styleTabElement = document.createElement("styletab"); 70 | styleTabElement.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 71 | styleTabElement.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_INCLUDE); 72 | styleTabElement.setAttribute("path", Constants.STYLE_SYSTEM_TAB_PATH); 73 | 74 | root.appendChild(containerElement) 75 | .appendChild(createUnStructuredNode(document, "items")) 76 | .appendChild(tabsElement) 77 | .appendChild(createUnStructuredNode(document, "items")) 78 | .appendChild(styleTabElement); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/DialogUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.utils; 21 | 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Objects; 25 | import java.util.Optional; 26 | import java.util.function.Function; 27 | import java.util.stream.Collectors; 28 | 29 | import javax.xml.parsers.DocumentBuilderFactory; 30 | 31 | import org.apache.commons.lang3.StringUtils; 32 | import org.apache.commons.text.CaseUtils; 33 | import org.w3c.dom.Document; 34 | import org.w3c.dom.Element; 35 | import org.w3c.dom.Node; 36 | 37 | import com.adobe.aem.compgenerator.Constants; 38 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 39 | import com.adobe.aem.compgenerator.models.GenerationConfig; 40 | import com.adobe.aem.compgenerator.models.Property; 41 | import com.adobe.aem.compgenerator.models.Tab; 42 | 43 | public class DialogUtils { 44 | 45 | /** 46 | * Creates dialog xml by adding the properties in data-config json file. 47 | * 48 | * @param generationConfig The {@link GenerationConfig} object with all the 49 | * populated values 50 | * @param dialogType The type of dialog to create (regular, shared or global) 51 | */ 52 | public static void createDialogXml(final GenerationConfig generationConfig, final String dialogType) { 53 | String dialogPath = generationConfig.getCompDir() + "/" + dialogType; 54 | try { 55 | CommonUtils.createFolder(dialogPath); 56 | 57 | Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); 58 | Element rootElement = createDialogRoot(doc, generationConfig, dialogType); 59 | 60 | if (dialogType.equalsIgnoreCase(Constants.DIALOG_TYPE_DIALOG)) { 61 | createDialogProperties(doc, rootElement, generationConfig.getOptions().getTabProperties(), 62 | generationConfig.getOptions().getProperties()); 63 | } else if (dialogType.equalsIgnoreCase(Constants.DIALOG_TYPE_GLOBAL)) { 64 | createDialogProperties(doc, rootElement, generationConfig.getOptions().getGlobalTabProperties(), 65 | generationConfig.getOptions().getGlobalProperties()); 66 | } else if (dialogType.equalsIgnoreCase(Constants.DIALOG_TYPE_SHARED)) { 67 | createDialogProperties(doc, rootElement, generationConfig.getOptions().getSharedTabProperties(), 68 | generationConfig.getOptions().getSharedProperties()); 69 | } 70 | 71 | doc.appendChild(rootElement); 72 | XMLUtils.transformDomToFile(doc, dialogPath + "/" + Constants.FILENAME_CONTENT_XML); 73 | } catch (Exception e) { 74 | throw new GeneratorException("Exception while creating Dialog xml : " + dialogPath, e); 75 | } 76 | } 77 | 78 | /** 79 | * Creates the tab dialog properties. 80 | * 81 | * @param doc The {@link Document} object 82 | * @param rootElement the root element 83 | * @param tabs The {@link Tab} object 84 | */ 85 | private static void createDialogProperties(Document doc, Element rootElement, List tabs, 86 | List properties) { 87 | 88 | if (null != properties && !properties.isEmpty()) { 89 | if (null != tabs && !tabs.isEmpty()) { 90 | Element currentNode = createTabsParentNodeStructure(doc, rootElement); 91 | 92 | Map propertiesMap = properties.stream() 93 | .collect(Collectors.toMap(Property::getField, Function.identity())); 94 | 95 | for (Tab tab : tabs) { 96 | Element tabNode = createTabStructure(doc, tab, currentNode); 97 | List sortedProperties = tab.getFields().stream().map(propertiesMap::get) 98 | .collect(Collectors.toList()); 99 | createNodeStructure(doc, sortedProperties, tabNode); 100 | } 101 | } else { 102 | Element currentNode = updateDefaultNodeStructure(doc, rootElement); 103 | createNodeStructure(doc, properties, currentNode); 104 | } 105 | } 106 | 107 | } 108 | 109 | /** 110 | * Creates the node structure. 111 | * 112 | * @param doc The {@link Document} object 113 | * @param properties {@link Property} 114 | * @param currentNode the current node 115 | */ 116 | private static void createNodeStructure(Document doc, List properties, Element currentNode) { 117 | properties.stream().filter(Objects::nonNull) 118 | .forEach(property -> createPropertyNode(doc, currentNode, property)); 119 | } 120 | 121 | /** 122 | * Generates the root elements of what will be the _cq_dialog/.content.xml. 123 | * 124 | * @param document The {@link Document} object 125 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 126 | * @param dialogType The type of dialog to create (regular, shared or global) 127 | * @return Element 128 | */ 129 | protected static Element createDialogRoot(Document document, GenerationConfig generationConfig, String dialogType) { 130 | Element rootElement = XMLUtils.createRootElement(document, generationConfig); 131 | 132 | rootElement.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 133 | rootElement.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_DIALOG); 134 | 135 | if (dialogType.equalsIgnoreCase(Constants.DIALOG_TYPE_GLOBAL)) { 136 | rootElement.setAttribute(Constants.PROPERTY_JCR_TITLE, 137 | generationConfig.getTitle() + " (Global Properties)"); 138 | } else if (dialogType.equalsIgnoreCase(Constants.DIALOG_TYPE_SHARED)) { 139 | rootElement.setAttribute(Constants.PROPERTY_JCR_TITLE, 140 | generationConfig.getTitle() + " (Shared Properties)"); 141 | } else if (dialogType.equalsIgnoreCase(Constants.DIALOG_TYPE_DESIGN_DIALOG)) { 142 | rootElement.setAttribute(Constants.PROPERTY_JCR_TITLE, generationConfig.getTitle() + " Design Dialog"); 143 | } else { 144 | rootElement.setAttribute(Constants.PROPERTY_JCR_TITLE, generationConfig.getTitle()); 145 | } 146 | 147 | return rootElement; 148 | } 149 | 150 | /** 151 | * Adds a dialog property xml node with all input attr under the document. 152 | * 153 | * @param document The {@link Document} object 154 | * @param currentNode the current {@link Element} object 155 | * @param property The {@link Property} object contains attributes 156 | * @return Element 157 | */ 158 | private static void createPropertyNode(Document document, final Element currentNode, Property property) { 159 | Element propertyNode = document.createElement(property.getField()); 160 | 161 | propertyNode.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 162 | Optional.ofNullable(getSlingResourceType(property.getType())).ifPresent(resType -> { 163 | propertyNode.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, resType); 164 | }); 165 | 166 | // Some of the properties are optional based on the different types available. 167 | addBasicProperties(propertyNode, property); 168 | 169 | getPrimaryFieldName(property).ifPresent(name -> { 170 | propertyNode.setAttribute(Constants.PROPERTY_NAME, name); 171 | propertyNode.setAttribute(Constants.PROPERTY_CQ_MSM_LOCKABLE, name); 172 | }); 173 | 174 | if ("image".equalsIgnoreCase(property.getType())) { 175 | addImageHiddenProperyValues(document, currentNode, property); 176 | // add these default image attributes BEFORE generic attribute handling, to allow for 177 | // individual overrides 178 | addImagePropertyValues(propertyNode, property); 179 | } 180 | 181 | processAttributes(propertyNode, property); 182 | 183 | if (property.getItems() != null && !property.getItems().isEmpty()) { 184 | if (!"multifield".equalsIgnoreCase(property.getType())) { 185 | Element items = createUnStructuredNode(document, "items"); 186 | propertyNode.appendChild(items); 187 | processItems(document, items, property); 188 | } else { 189 | Element field = document.createElement("field"); 190 | field.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 191 | 192 | field.setAttribute(Constants.PROPERTY_NAME, "./" + property.getField()); 193 | field.setAttribute(Constants.PROPERTY_CQ_MSM_LOCKABLE, "./" + property.getField()); 194 | 195 | field.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_FIELDSET); 196 | 197 | if (property.getItems().size() == 1) { 198 | Element layout = document.createElement("layout"); 199 | layout.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 200 | layout.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_FIXEDCOLUMNS); 201 | layout.setAttribute("method", "absolute"); 202 | field.appendChild(layout); 203 | 204 | Node items = field.appendChild(createUnStructuredNode(document, "items")); 205 | Element column = document.createElement("column"); 206 | column.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 207 | column.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_CONTAINER); 208 | items.appendChild(column); 209 | 210 | items = column.appendChild(createUnStructuredNode(document, "items")); 211 | 212 | Element actualField = document.createElement("field"); 213 | actualField.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 214 | actualField.setAttribute(Constants.PROPERTY_NAME, "./" + property.getField()); 215 | actualField.setAttribute(Constants.PROPERTY_CQ_MSM_LOCKABLE, "./" + property.getField()); 216 | 217 | Property prop = property.getItems().get(0); 218 | Optional.ofNullable(getSlingResourceType(prop.getType())).ifPresent(resType -> { 219 | actualField.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, resType); 220 | }); 221 | addBasicProperties(actualField, prop); 222 | processAttributes(actualField, prop); 223 | items.appendChild(actualField); 224 | } else { 225 | propertyNode.setAttribute(Constants.PROPERTY_COMPOSITE, "{Boolean}true"); 226 | Element items = createUnStructuredNode(document, "items"); 227 | field.appendChild(items); 228 | processItems(document, items, property); 229 | } 230 | 231 | propertyNode.appendChild(field); 232 | } 233 | } 234 | 235 | // only append the primary property field after successfully constructing its members 236 | currentNode.appendChild(propertyNode); 237 | } 238 | 239 | /** 240 | * Processes the attributes for a propertyNode. 241 | * 242 | * @param propertyNode The node to add property attributes 243 | * @param property The {@link Property} object contains attributes 244 | */ 245 | private static void processAttributes(Element propertyNode, Property property) { 246 | if (property.getAttributes() != null && property.getAttributes().size() > 0) { 247 | property.getAttributes().entrySet().stream() 248 | .forEach(entry -> propertyNode.setAttribute(entry.getKey(), entry.getValue())); 249 | } 250 | } 251 | 252 | /** 253 | * Process the dialog node item by setting property attributes on it. 254 | * 255 | * @param document The {@link Document} object 256 | * @param itemsNode The parent {@link Element} object 257 | * @param property The {@link Property} object contains attributes 258 | */ 259 | private static void processItems(Document document, Element itemsNode, Property property) { 260 | for (Property item : property.getItems()) { 261 | Element optionNode = document.createElement(item.getField()); 262 | optionNode.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 263 | 264 | addBasicProperties(optionNode, item); 265 | 266 | String resourceType = getSlingResourceType(item.getType()); 267 | if (StringUtils.isNotEmpty(resourceType)) { 268 | optionNode.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, resourceType); 269 | } 270 | 271 | if (StringUtils.equalsIgnoreCase("multifield", property.getType())) { 272 | getPrimaryFieldName(item).ifPresent(name -> { 273 | optionNode.setAttribute(Constants.PROPERTY_NAME, name); 274 | }); 275 | 276 | if ("image".equalsIgnoreCase(item.getType())) { 277 | addImageHiddenProperyValues(document, itemsNode, item); 278 | 279 | // add these default image attributes BEFORE generic attribute handling, to allow for 280 | // individual overrides 281 | addImagePropertyValues(optionNode, item); 282 | } 283 | } 284 | 285 | processAttributes(optionNode, item); 286 | itemsNode.appendChild(optionNode); 287 | } 288 | } 289 | 290 | /** 291 | * Adds the field label and field description attributes to the node. 292 | * 293 | * @param propertyNode The node to add property attributes 294 | * @param property The {@link Property} object contains attributes 295 | */ 296 | private static void addBasicProperties(Element propertyNode, Property property) { 297 | if (StringUtils.isNotEmpty(property.getLabel())) { 298 | if (property.getType().equalsIgnoreCase(Constants.TYPE_HEADING)) { 299 | propertyNode.setAttribute(Constants.PROPERTY_TEXT, property.getLabel()); 300 | } else { 301 | propertyNode.setAttribute(Constants.PROPERTY_FIELDLABEL, property.getLabel()); 302 | } 303 | } 304 | if (StringUtils.isNotEmpty(property.getDescription())) { 305 | propertyNode.setAttribute(Constants.PROPERTY_FIELDDESC, property.getDescription()); 306 | } 307 | } 308 | 309 | /** 310 | * Adds the properties specific to the image node. These could all have been 311 | * included as attributes in the configuration json file, but they never/rarely 312 | * change, so hardcoding them here seems safe to do. 313 | * 314 | * @param imageNode The {@link Node} object 315 | * @param property The {@link Property} object contains attributes 316 | */ 317 | private static void addImagePropertyValues(Element imageNode, Property property) { 318 | imageNode.setAttribute("allowUpload", "{Boolean}false"); 319 | imageNode.setAttribute("autoStart", "{Boolean}false"); 320 | imageNode.setAttribute("class", "cq-droptarget"); 321 | imageNode.setAttribute("fileNameParameter", "./" + property.getField() + "/fileName"); 322 | imageNode.setAttribute("fileReferenceParameter", "./" + property.getField() + "/fileReference"); 323 | imageNode.setAttribute("mimeTypes", "[image/gif,image/jpeg,image/png,image/webp,image/tiff,image/svg+xml]"); 324 | imageNode.setAttribute("multiple", "{Boolean}false"); 325 | imageNode.setAttribute("title", "Drag to select image"); 326 | imageNode.setAttribute("uploadUrl", "${suffix.path}"); 327 | imageNode.setAttribute("useHTML5", "{Boolean}true"); 328 | } 329 | 330 | /** 331 | * Adds the properties specific to the hidden image node that allows the image 332 | * dropzone to operate properly on dialogs. 333 | * 334 | * @param document the host document 335 | * @param parentElement An {@link Element} object that an image's 336 | * hidden node should be added as a child to 337 | * @param property The {@link Property} object contains attributes 338 | */ 339 | private static void addImageHiddenProperyValues(Document document, Element parentElement, Property property) { 340 | Element hiddenImageNode = document.createElement(property.getField() + "ResType"); 341 | hiddenImageNode.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 342 | hiddenImageNode.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_HIDDEN); 343 | hiddenImageNode.setAttribute("name", "./" + property.getField() + "/" + Constants.PROPERTY_SLING_RESOURCETYPE); 344 | hiddenImageNode.setAttribute("value", Constants.RESOURCE_TYPE_IMAGE_HIDDEN_TYPE); 345 | // add this hidden field to the parent 346 | parentElement.appendChild(hiddenImageNode); 347 | } 348 | 349 | /** 350 | * Builds default node structure of dialog xml in the document passed in based 351 | * on dialogType. 352 | * 353 | * @param document The {@link Document} object 354 | * @param root The root node to append children nodes to 355 | * @return Node 356 | */ 357 | private static Element updateDefaultNodeStructure(Document document, Element root) { 358 | Element containerElement = createNode(document, "content", Constants.RESOURCE_TYPE_CONTAINER); 359 | 360 | Element layoutElement = createNode(document, "layout", Constants.RESOURCE_TYPE_FIXEDCOLUMNS); 361 | layoutElement.setAttribute("margin", "{Boolean}false"); 362 | 363 | Element columnElement = createNode(document, "column", Constants.RESOURCE_TYPE_CONTAINER); 364 | 365 | root.appendChild(containerElement); 366 | 367 | containerElement.appendChild(layoutElement); 368 | Element topItemsElement = createUnStructuredNode(document, "items"); 369 | Element bottomItemsElement = createUnStructuredNode(document, "items"); 370 | containerElement.appendChild(topItemsElement).appendChild(columnElement).appendChild(bottomItemsElement); 371 | return bottomItemsElement; 372 | } 373 | 374 | /** 375 | * Creates the default tab node structure. 376 | * 377 | * @param document The {@link Document} object 378 | * @param root The {@link Element} object 379 | * @return the node 380 | */ 381 | private static Element createTabsParentNodeStructure(Document document, Element root) { 382 | Element containerElement = createNode(document, "content", Constants.RESOURCE_TYPE_CONTAINER); 383 | root.appendChild(containerElement); 384 | Element topItemsElement = createUnStructuredNode(document, "items"); 385 | Element tabsElement = createNode(document, "tabs", Constants.RESOURCE_TYPE_TABS); 386 | Element bottomItemsElement = createUnStructuredNode(document, "items"); 387 | containerElement.appendChild(topItemsElement).appendChild(tabsElement).appendChild(bottomItemsElement); 388 | 389 | return bottomItemsElement; 390 | } 391 | 392 | /** 393 | * Creates the tab structure. 394 | * 395 | * @param document The {@link Document} object 396 | * @param tab the tab 397 | * @param parentElement The parent {@link Element} object 398 | * @return the node {@link Node} object 399 | */ 400 | private static Element createTabStructure(Document document, Tab tab, Element parentElement) { 401 | Element tabElement = createNode(document, tab.getId(), Constants.RESOURCE_TYPE_CONTAINER); 402 | String label = tab.getLabel(); 403 | if (StringUtils.isBlank(label)) { 404 | label = CaseUtils.toCamelCase(tab.getId(), true); 405 | } 406 | tabElement.setAttribute(Constants.PROPERTY_JCR_TITLE, label); 407 | Element columnElement = createNode(document, "column", Constants.RESOURCE_TYPE_CONTAINER); 408 | 409 | Element layoutElement = document.createElement("layout"); 410 | layoutElement.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 411 | layoutElement.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, Constants.RESOURCE_TYPE_CORAL_FIXEDCOLUMNS); 412 | 413 | tabElement.appendChild(layoutElement); 414 | 415 | Element topItemsElement = createUnStructuredNode(document, "items"); 416 | Element bottomItemsElement = createUnStructuredNode(document, "items"); 417 | parentElement.appendChild(tabElement).appendChild(topItemsElement) 418 | .appendChild(columnElement).appendChild(bottomItemsElement); 419 | return bottomItemsElement; 420 | } 421 | 422 | /** 423 | * Creates a node with the jcr:primaryType set to nt:unstructured. 424 | * 425 | * @param document The {@link Document} object 426 | * @param nodeName The name of the node being created 427 | * @return Node 428 | */ 429 | protected static Element createUnStructuredNode(Document document, String nodeName) { 430 | Element element = document.createElement(nodeName); 431 | element.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 432 | return element; 433 | } 434 | 435 | /** 436 | * Creates the node. 437 | * 438 | * @param document The {@link Document} object 439 | * @param fieldName the field name 440 | * @param resourceType the resource type 441 | * @return An {@link Element} object 442 | */ 443 | private static Element createNode(Document document, String fieldName, String resourceType) { 444 | Element containerElement = document.createElement(fieldName); 445 | containerElement.setAttribute(Constants.JCR_PRIMARY_TYPE, Constants.NT_UNSTRUCTURED); 446 | containerElement.setAttribute(Constants.PROPERTY_SLING_RESOURCETYPE, resourceType); 447 | return containerElement; 448 | } 449 | 450 | /** 451 | * Return the submittable, lockable, primary field name for the given property. Fields that 452 | * aren't defined or directly submittable will return empty. Image type wraps a fileupload field, 453 | * which is expected to post to a "file" subresource relative to Image base resource. 454 | * 455 | * @param property the current property 456 | * @return the appropriate field relative path name or empty 457 | */ 458 | private static Optional getPrimaryFieldName(Property property) { 459 | final String type = property.getType(); 460 | if (StringUtils.isBlank(type) 461 | || Constants.TYPE_HEADING.equalsIgnoreCase(type) 462 | || "multifield".equalsIgnoreCase(type)) { 463 | return Optional.empty(); 464 | } 465 | if ("image".equalsIgnoreCase(type)) { 466 | return Optional.of("./" + property.getField() + "/file"); 467 | } else { 468 | return Optional.of("./" + property.getField()); 469 | } 470 | } 471 | 472 | /** 473 | * Determine the proper sling:resourceType. 474 | * 475 | * @param type The sling:resourceType 476 | * @return String 477 | */ 478 | private static String getSlingResourceType(String type) { 479 | if (StringUtils.isNotBlank(type)) { 480 | if (StringUtils.equalsIgnoreCase("textfield", type)) { 481 | return Constants.RESOURCE_TYPE_TEXTFIELD; 482 | } else if (StringUtils.equalsIgnoreCase("numberfield", type)) { 483 | return Constants.RESOURCE_TYPE_NUMBER; 484 | } else if (StringUtils.equalsIgnoreCase("checkbox", type)) { 485 | return Constants.RESOURCE_TYPE_CHECKBOX; 486 | } else if (StringUtils.equalsIgnoreCase("pagefield", type)) { 487 | return Constants.RESOURCE_TYPE_PAGEFIELD; 488 | } else if (StringUtils.equalsIgnoreCase("pathfield", type)) { 489 | return Constants.RESOURCE_TYPE_PATHFIELD; 490 | } else if (StringUtils.equalsIgnoreCase("textarea", type)) { 491 | return Constants.RESOURCE_TYPE_TEXTAREA; 492 | } else if (StringUtils.equalsIgnoreCase("hidden", type)) { 493 | return Constants.RESOURCE_TYPE_HIDDEN; 494 | } else if (StringUtils.equalsIgnoreCase("datepicker", type)) { 495 | return Constants.RESOURCE_TYPE_DATEPICKER; 496 | } else if (StringUtils.equalsIgnoreCase("select", type)) { 497 | return Constants.RESOURCE_TYPE_SELECT; 498 | } else if (StringUtils.equalsIgnoreCase("radiogroup", type)) { 499 | return Constants.RESOURCE_TYPE_RADIOGROUP; 500 | } else if (StringUtils.equalsIgnoreCase("image", type)) { 501 | return Constants.RESOURCE_TYPE_IMAGE; 502 | } else if (StringUtils.equalsIgnoreCase("multifield", type)) { 503 | return Constants.RESOURCE_TYPE_MULTIFIELD; 504 | } else if (StringUtils.equalsIgnoreCase("tagfield", type)) { 505 | return Constants.RESOURCE_TYPE_TAGFIELD; 506 | } else if (StringUtils.equalsIgnoreCase(Constants.TYPE_HEADING, type)) { 507 | return Constants.RESOURCE_TYPE_HEADING; 508 | } 509 | } 510 | return null; 511 | } 512 | } 513 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/HTMLUtils.java: -------------------------------------------------------------------------------- 1 | package com.adobe.aem.compgenerator.utils; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.stream.Collectors; 7 | import java.util.stream.Stream; 8 | 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | import com.adobe.aem.compgenerator.Constants; 12 | import com.adobe.aem.compgenerator.models.GenerationConfig; 13 | import com.adobe.aem.compgenerator.models.Property; 14 | 15 | public class HTMLUtils { 16 | 17 | public static String renderHtml(GenerationConfig generationConfig) { 18 | List props = getAllProperties(generationConfig); 19 | String slingModel = StringUtils.uncapitalize(generationConfig.getJavaFormatedName()) + "Model"; 20 | StringBuilder renderedHtml = new StringBuilder(); 21 | 22 | for (Property prop : props) { 23 | String label = getLabel(prop); 24 | 25 | if (Constants.TYPE_CHECKBOX.equals(prop.getType())) { 26 | renderedHtml.append(generateParagraphHtml(label, 27 | prop.getField(), 28 | slingModel, 29 | " ? 'checked' : 'unchecked'" 30 | )); 31 | } else if (Constants.TYPE_DATEPICKER.equals(prop.getType())) { 32 | renderedHtml.append(generateParagraphHtml(label, 33 | prop.getField(), 34 | slingModel, 35 | ".time.toGMTString" 36 | )); 37 | } else if (Constants.TYPE_IMAGE.equals(prop.getType())) { 38 | renderedHtml.append(generateImageHtml(label, prop.getField(), slingModel)); 39 | } else if (Constants.TYPE_MULTIFIELD.equals(prop.getType()) 40 | || Constants.TYPE_TAGFIELD.equals(prop.getType())) { 41 | renderedHtml.append(generateListHtml(prop, slingModel)); 42 | } else if (!Constants.TYPE_HEADING.equals(prop.getType())) { 43 | renderedHtml.append(generateParagraphHtml(label, 44 | prop.getField(), 45 | slingModel, 46 | StringUtils.EMPTY)); 47 | } 48 | } 49 | return renderedHtml.toString(); 50 | } 51 | 52 | private static String getLabel(Property prop) { 53 | if (Constants.TYPE_CHECKBOX.equals(prop.getType())) { 54 | return prop.getAttributes().get("text"); 55 | } 56 | if (Constants.TYPE_HIDDEN.equals(prop.getType())) { 57 | return "Hidden Field (" + prop.getField() + ")"; 58 | } 59 | String label = prop.getLabel(); 60 | if (label == null) { 61 | label = prop.getAttributes().get("value"); 62 | if (label == null) { 63 | label = prop.getField(); 64 | } 65 | } 66 | 67 | return label; 68 | } 69 | 70 | private static String generateImageHtml(String label, String field, String slingModel) { 71 | return "

" + 72 | label + 73 | ":

\n"; 78 | } 79 | 80 | private static String generateListHtml(Property prop, String slingModel) { 81 | String initialListHtml = "
\n

"; 86 | if (prop.getItems() != null && prop.getItems().size() > 1) { 87 | StringBuilder items = new StringBuilder(initialListHtml); 88 | int index = 1; 89 | for (Property property : prop.getItems()) { 90 | if ("image".equalsIgnoreCase(property.getType())) { 91 | items.append(property.getLabel()) 92 | .append(": ${item.") 93 | .append(property.getField()).append(".src") 94 | .append(prop.getItems().size() == index ? "}" : "} | "); 95 | } else { 96 | items.append(property.getLabel()) 97 | .append(": ${item.") 98 | .append(property.getField()) 99 | .append(prop.getItems().size() == index ? "}" : "} | "); 100 | } 101 | index++; 102 | } 103 | return items + "

\n
\n"; 104 | } else { 105 | return initialListHtml + 106 | prop.getLabel() + 107 | ": ${item}

\n \n"; 108 | } 109 | } 110 | 111 | private static String generateParagraphHtml(String label, String field, String slingModel, String additional) { 112 | return "

" + 113 | label + 114 | ": ${" + 115 | slingModel + 116 | "." + 117 | field + 118 | additional + 119 | "}

\n"; 120 | } 121 | 122 | private static List getAllProperties(GenerationConfig generationConfig) { 123 | List globalProperties = CommonUtils.getSortedPropertiesBasedOnTabs( 124 | generationConfig.getOptions().getGlobalProperties(), 125 | generationConfig.getOptions().getGlobalTabProperties()); 126 | List sharedProperties = CommonUtils.getSortedPropertiesBasedOnTabs( 127 | generationConfig.getOptions().getSharedProperties(), 128 | generationConfig.getOptions().getSharedTabProperties()); 129 | List localProperties = CommonUtils.getSortedPropertiesBasedOnTabs( 130 | generationConfig.getOptions().getProperties(), generationConfig.getOptions().getTabProperties()); 131 | return Stream.of(globalProperties, sharedProperties, localProperties).filter(Objects::nonNull) 132 | .flatMap(Collection::stream) 133 | .collect(Collectors.toList()); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/XMLUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | package com.adobe.aem.compgenerator.utils; 21 | 22 | import com.adobe.aem.compgenerator.Constants; 23 | import com.adobe.aem.compgenerator.exceptions.GeneratorException; 24 | import com.adobe.aem.compgenerator.models.GenerationConfig; 25 | import org.w3c.dom.Document; 26 | import org.w3c.dom.Element; 27 | 28 | import javax.xml.transform.OutputKeys; 29 | import javax.xml.transform.Transformer; 30 | import javax.xml.transform.TransformerFactory; 31 | import javax.xml.transform.dom.DOMSource; 32 | import javax.xml.transform.stream.StreamResult; 33 | import java.io.File; 34 | 35 | public class XMLUtils { 36 | 37 | /** 38 | * Creates root node with of dialog xml with required name spaces as attr. 39 | * 40 | * @param document The {@link Document} object 41 | * @param generationConfig The {@link GenerationConfig} object with all the populated values 42 | * @return Element 43 | */ 44 | public static Element createRootElement(Document document, GenerationConfig generationConfig) { 45 | if (document == null) { 46 | return null; 47 | } 48 | 49 | String templateString = CommonUtils.getTemplateFileAsString(Constants.TEMPLATE_COPYRIGHT_XML, generationConfig); 50 | document.appendChild(document.createComment(templateString)); 51 | Element rootElement = document.createElement(Constants.JCR_ROOT_NODE); 52 | rootElement.setAttribute("xmlns:sling", "http://sling.apache.org/jcr/sling/1.0"); 53 | rootElement.setAttribute("xmlns:granite", "http://www.adobe.com/jcr/granite/1.0"); 54 | rootElement.setAttribute("xmlns:cq", "http://www.day.com/jcr/cq/1.0"); 55 | rootElement.setAttribute("xmlns:jcr", "http://www.jcp.org/jcr/1.0"); 56 | rootElement.setAttribute("xmlns:nt", "http://www.jcp.org/jcr/nt/1.0"); 57 | 58 | return rootElement; 59 | } 60 | 61 | /** 62 | * Method will transform Document structure by prettify xml elements to file. 63 | * 64 | * @param document The {@link Document} object 65 | * @param filePath The path to the file 66 | */ 67 | public static void transformDomToFile(Document document, String filePath) { 68 | try { 69 | TransformerFactory tf = TransformerFactory.newInstance(); 70 | Transformer tr = tf.newTransformer(); 71 | 72 | //config for beautify/prettify xml content. 73 | tr.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); 74 | tr.setOutputProperty(OutputKeys.INDENT, "yes"); 75 | tr.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 76 | 77 | DOMSource source = new DOMSource(document); 78 | File file = CommonUtils.getNewFileAtPathAndRenameExisting(filePath); 79 | StreamResult result = new StreamResult(file); 80 | 81 | //transform your DOM source to the given file location. 82 | tr.transform(source, result); 83 | 84 | } catch (Exception e) { 85 | throw new GeneratorException("Exception while DOM conversion to file : " + filePath, e); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/adobe/aem/compgenerator/utils/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * #%L 3 | * AEM Component Generator 4 | * %% 5 | * Copyright (C) 2019 Adobe 6 | * %% 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * #L% 19 | */ 20 | /** 21 | * Package contains utility classes used by the application. 22 | * @since 1.0 23 | */ 24 | package com.adobe.aem.compgenerator.utils; -------------------------------------------------------------------------------- /src/main/resources/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.adobe.aem.compgenerator.AemCompGenerator 3 | 4 | -------------------------------------------------------------------------------- /src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/template-copyright-css.txt: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | ${CODEOWNER} CONFIDENTIAL 3 | ___________________ 4 | 5 | Copyright ${YEAR} ${CODEOWNER} 6 | All Rights Reserved. 7 | 8 | NOTICE: All information contained herein is, and remains the property 9 | of ${CODEOWNER} and its suppliers, if any. The intellectual and 10 | technical concepts contained herein are proprietary to ${CODEOWNER} 11 | and its suppliers and are protected by trade secret or copyright law. 12 | Dissemination of this information or reproduction of this material 13 | is strictly forbidden unless prior written permission is obtained 14 | from ${CODEOWNER}. 15 | ========================================================================== */ 16 | -------------------------------------------------------------------------------- /src/main/resources/template-copyright-text.txt: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # ${CODEOWNER} CONFIDENTIAL 3 | # ___________________ 4 | # 5 | # Copyright ${YEAR} ${CODEOWNER} 6 | # All Rights Reserved. 7 | # 8 | # NOTICE: All information contained herein is, and remains the property 9 | # of ${CODEOWNER} and its suppliers, if any. The intellectual and 10 | # technical concepts contained herein are proprietary to ${CODEOWNER} 11 | # and its suppliers and are protected by trade secret or copyright law. 12 | # Dissemination of this information or reproduction of this material 13 | # is strictly forbidden unless prior written permission is obtained 14 | # from ${CODEOWNER}. 15 | ############################################################################### 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/template-copyright-xml.txt: -------------------------------------------------------------------------------- 1 | 2 | | *********************************************************************** 3 | | ${CODEOWNER} CONFIDENTIAL 4 | | ___________________ 5 | | 6 | | Copyright ${YEAR} ${CODEOWNER} 7 | | All Rights Reserved. 8 | | 9 | | NOTICE: All information contained herein is, and remains the property 10 | | of ${CODEOWNER} and its suppliers, if any. The intellectual and 11 | | technical concepts contained herein are proprietary to ${CODEOWNER} 12 | | and its suppliers and are protected by trade secret or copyright law. 13 | | Dissemination of this information or reproduction of this material 14 | | is strictly forbidden unless prior written permission is obtained 15 | | from ${CODEOWNER}. 16 | | *********************************************************************** 17 | 18 | -------------------------------------------------------------------------------- /src/main/resources/template-copyright.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * *********************************************************************** 3 | * ${CODEOWNER} CONFIDENTIAL 4 | * ___________________ 5 | * 6 | * Copyright ${YEAR} ${CODEOWNER}. 7 | * All Rights Reserved. 8 | * 9 | * NOTICE: All information contained herein is, and remains the property 10 | * of ${CODEOWNER} and its suppliers, if any. The intellectual and 11 | * technical concepts contained herein are proprietary to ${CODEOWNER} 12 | * and its suppliers and are protected by trade secret or copyright law. 13 | * Dissemination of this information or reproduction of this material 14 | * is strictly forbidden unless prior written permission is obtained 15 | * from ${CODEOWNER}. 16 | * *********************************************************************** 17 | */ 18 | 19 | -------------------------------------------------------------------------------- /src/main/resources/template-htl.txt: -------------------------------------------------------------------------------- 1 | 18 | 19 | 21 | ${htmlOutput} 22 | 23 | 24 | 26 | --------------------------------------------------------------------------------