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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AEM Component Generator
2 |
3 | [](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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------