This is an automated email from the ASF dual-hosted git repository. radu pushed a commit to branch issue/SLING-8228 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-runtime.git
commit ef2d6e1ef567e3543de3078ec2251463486b100e Author: Radu Cotescu <[email protected]> AuthorDate: Thu Oct 18 13:42:40 2018 +0200 SLING-8012 - Extract an HTL runtime bundle from the existing HTL modules * initial commit --- .gitignore | 17 + CODE_OF_CONDUCT.md | 22 + CONTRIBUTING.md | 24 ++ LICENSE | 202 +++++++++ README.md | 9 + pom.xml | 192 +++++++++ .../org/apache/sling/scripting/sightly/Record.java | 51 +++ .../sling/scripting/sightly/SightlyException.java | 38 ++ .../sightly/extension/RuntimeExtension.java | 170 ++++++++ .../scripting/sightly/extension/package-info.java | 22 + .../sling/scripting/sightly/package-info.java | 21 + .../apache/sling/scripting/sightly/pojo/Use.java | 47 ++ .../sling/scripting/sightly/pojo/package-info.java | 22 + .../sightly/render/AbstractRuntimeObjectModel.java | 143 ++++++ .../scripting/sightly/render/ObjectModel.java | 480 +++++++++++++++++++++ .../scripting/sightly/render/RenderContext.java | 51 +++ .../sling/scripting/sightly/render/RenderUnit.java | 156 +++++++ .../sightly/render/RuntimeObjectModel.java | 121 ++++++ .../scripting/sightly/render/package-info.java | 20 + .../scripting/sightly/use/ProviderOutcome.java | 133 ++++++ .../sling/scripting/sightly/use/UseProvider.java | 46 ++ .../sling/scripting/sightly/use/package-info.java | 22 + .../render/AbstractRuntimeObjectModelTest.java | 168 ++++++++ .../scripting/sightly/render/ObjectModelTest.java | 245 +++++++++++ .../sightly/render/testobjects/Person.java | 29 ++ .../sightly/render/testobjects/TestEnum.java | 25 ++ .../testobjects/internal/AbstractPerson.java | 42 ++ .../sightly/render/testobjects/internal/Adult.java | 32 ++ .../render/testobjects/internal/AdultFactory.java | 26 ++ 29 files changed, 2576 insertions(+) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b783ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +/target +.idea +.classpath +.metadata +.project +.settings +.externalToolBuilders +maven-eclipse.xml +*.swp +*.iml +*.ipr +*.iws +*.bak +.vlt +.DS_Store +jcr.log +atlassian-ide-plugin.xml diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..0fa18e5 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,22 @@ +<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/--> +Apache Software Foundation Code of Conduct +==== + +Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ac82a1a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,24 @@ +<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/--> +Contributing +==== + +Thanks for choosing to contribute! + +You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fa5bc8 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +[<img src="http://sling.apache.org/res/logos/sling.png"/>](http://sling.apache.org) + + [](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-scripting-sightly-runtime-1.8) [](https://builds.apache.org/view/S-Z/view/Sling/job/sling-org-apache-sling-scripting-sightly-runtime-1.8/test_resu [...] + +# Apache Sling Scripting HTL Runtime + +This module is part of the [Apache Sling](https://sling.apache.org) project. + +The Apache Sling Scripting HTL Runtime provides support for executing HTL Java compiled units produced by the [`org.apache.sling.scripting.sightly.compiler.java module`](https://github.com/apache/sling-org-apache-sling-scripting-sightly-runtime). diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..0b404db --- /dev/null +++ b/pom.xml @@ -0,0 +1,192 @@ +<?xml version="1.0"?> +<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You under the Apache License, Version 2.0 + ~ (the "License"); you may not use this file except in compliance with + ~ the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <!-- ======================================================================= --> + <!-- P A R E N T P R O J E C T --> + <!-- ======================================================================= --> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>30</version> + <relativePath /> + </parent> + + <!-- ======================================================================= --> + <!-- P R O J E C T --> + <!-- ======================================================================= --> + <artifactId>org.apache.sling.scripting.sightly.runtime</artifactId> + <!-- + The versioning scheme defined here corresponds to SLING-7406 (<module_version>-<htl_specification_version>). Take care when + releasing to only increase the first part, unless the module provides support for a newer version of the HTL specification. + --> + <version>1.0.0-1.4.0-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Apache Sling Scripting HTL Runtime</name> + + <description> + The Apache Sling Scripting HTL Runtime provides support for executing HTL Java compiled units produced by the org.apache.sling.scripting.sightly.compiler.java module. + </description> + + <scm> + <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-runtime.git</connection> + <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-sightly-runtime.git</developerConnection> + <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-scripting-sightly-runtime.git</url> + <tag>HEAD</tag> + </scm> + + <properties> + <jacoco.maven.plugin.version>0.7.9</jacoco.maven.plugin.version> + <sling.java.version>8</sling.java.version> + </properties> + + <!-- ======================================================================= --> + <!-- B U I L D --> + <!-- ======================================================================= --> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <executions> + <execution> + <id>scr-metadata</id> + <goals> + <goal>manifest</goal> + </goals> + </execution> + </executions> + <configuration> + <exportScr>true</exportScr> + <instructions> + <Provide-Capability> + io.sightly.runtime; version:Version=1.0, + io.sightly.runtime; version:Version=1.1, + io.sightly.runtime; version:Version=1.2, + io.sightly.runtime; version:Version=1.3, + io.sightly.runtime; version:Version=1.3.1, + io.sightly.runtime; version:Version=1.4 + </Provide-Capability> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>findbugs-maven-plugin</artifactId> + <version>3.0.3</version> + <configuration> + <effort>Max</effort> + <xmlOutput>true</xmlOutput> + </configuration> + <executions> + <execution> + <id>find-bugs</id> + <phase>process-classes</phase> + <goals> + <goal>check</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <!-- No javadocs --> + <excludePackageNames> + org.apache.sling.scripting.sightly.java.compiler.impl + </excludePackageNames> + </configuration> + </plugin> + </plugins> + </build> + + <profiles> + <profile> + <id>coverage-report</id> + <activation> + <activeByDefault>false</activeByDefault> + </activation> + <build> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco.maven.plugin.version}</version> + <executions> + <execution> + <id>default-prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>default-report</id> + <goals> + <goal>report</goal> + </goals> + <configuration> + <includes> + <include>org/apache/sling/scripting/sightly/**/*</include> + </includes> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + + <!-- ======================================================================= --> + <!-- D E P E N D E N C I E S --> + <!-- ======================================================================= --> + <dependencies> + + <!-- Logging --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Apache Commons --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.5</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + <version>2.5</version> + <scope>provided</scope> + </dependency> + + <!-- testing --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/src/main/java/org/apache/sling/scripting/sightly/Record.java b/src/main/java/org/apache/sling/scripting/sightly/Record.java new file mode 100644 index 0000000..5c8be4a --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/Record.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ + +package org.apache.sling.scripting.sightly; + +import java.util.Set; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * A {@code Record} is a key-value immutable object understood by the HTL runtime, used for abstracting complex objects like Sightly + * templates (declared with the {@code data-sly-template} block element) or objects that need to be translated from Java to JavaScript + * and back. + * + * @param <T> the type of values for this record + */ +@ConsumerType +public interface Record<T> { + + /** + * Gets the value of a specified property. + * + * @param name the name of the property + * @return the value of the property or {@code null} if this record does not have the specified property + */ + T getProperty(String name); + + /** + * Gets the set of names for this record's properties. + * + * @return this record's properties' names + */ + Set<String> getPropertyNames(); + +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/SightlyException.java b/src/main/java/org/apache/sling/scripting/sightly/SightlyException.java new file mode 100644 index 0000000..16111ee --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/SightlyException.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly; + +/** + * Exceptions caused by the HTL engine. + */ +public class SightlyException extends RuntimeException { + + public SightlyException() { + } + + public SightlyException(String message) { + super(message); + } + + public SightlyException(String message, Throwable cause) { + super(message, cause); + } + + public SightlyException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/extension/RuntimeExtension.java b/src/main/java/org/apache/sling/scripting/sightly/extension/RuntimeExtension.java new file mode 100644 index 0000000..1cfaa0c --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/extension/RuntimeExtension.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.extension; + +import org.apache.sling.scripting.sightly.render.RenderContext; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * A {@code RuntimeExtension} represents a HTL runtime construct that provides some processing capabilities for the various + * {@code data-sly-*} block elements. + */ +@ConsumerType +public interface RuntimeExtension { + + /** + * <p> + * The name of the runtime function that will process string + * formatting. The function will receive the following parameters: + * </p> + * <ol> + * <li>the format String (e.g. 'Hello {0}, welcome to {1}')</li> + * <li>an array of objects that will replace the format placeholders</li> + * </ol> + * <p> + * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#122-format. + * </p> + */ + String FORMAT = "format"; + + /** + * <p> + * The name of the runtime function that will process + * i18n. The function will receive the following parameters: + * </p> + * <ol> + * <li>the String to translate</li> + * <li>optional: locale information</li> + * <li>optional: hint information</li> + * <li>optional (not part of the specification): basename information; for more details see + * {@link java.util.ResourceBundle#getBundle(String, java.util.Locale)}</li> + * </ol> + * <p> + * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#123-i18n. + * </p> + */ + String I18N = "i18n"; + + /** + * <p> + * The name of the runtime function that will process + * join operations on arrays. The function will receive the following parameters: + * </p> + * <ol> + * <li>the array of objects to join (e.g. [1, 2, 3])</li> + * <li>the join string (e.g. ';')</li> + * </ol> + * <p> + * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#124-array-join. + * </p> + */ + String JOIN = "join"; + + /** + * <p> + * The name of the runtime function that will provide + * URI manipulation support. The function will receive the following parameters: + * </p> + * <ol> + * <li>optional: a URI string to process</li> + * <li>optional: a Map containing URI manipulation options</li> + * </ol> + * <p> + * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#125-uri-manipulation. + * </p> + */ + String URI_MANIPULATION = "uriManipulation"; + + /** + * <p> + * The name of the runtime function that will provide + * XSS escaping and filtering support. The function will receive the following parameters: + * </p> + * <ol> + * <li>the original string to escape / filter</li> + * <li>the context to be applied - see {@link org.apache.sling.scripting.sightly.compiler.expression.MarkupContext}</li> + * </ol> + * <p> + * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#121-display-context. + * </p> + */ + String XSS = "xss"; + + /** + * <p> + * The name of the runtime function that will perform + * script execution delegation. The function will receive the following parameters: + * </p> + * <ol> + * <li>optional: the relative or absolute path of the script to execute</li> + * <li>optional: a Map of options to perform script include processing</li> + * </ol> + * <p> + * For more details about the supported options check + * https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#228-include. + * </p> + */ + String INCLUDE = "include"; + + /** + * <p> + * The name of the runtime function that will perform + * resource inclusion in the rendering process. The function will receive the following parameters: + * </p> + * <ol> + * <li>optional: a relative or absolute path of the resource to be included</li> + * <li>optional: a Map containing the resource processing options</li> + * </ol> + * <p> + * For more details about the supported options check + * https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#229-resource. + * </p> + */ + String RESOURCE = "includeResource"; + + /** + * <p> + * The name of the runtime function that will provide + * the support for loading Use-API objects. The function will receive the following parameters: + * </p> + * <ol> + * <li>an identifier that allows to discover the Use-API object that needs to be loaded</li> + * <li>optional: a Map of the arguments that are passed to the Use-API object for initialisation or to provide context</li> + * </ol> + * <p> + * For more details check https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/1.2/SPECIFICATION.md#221-use. + * </p> + */ + String USE = "use"; + + /** + * For OSGi environments this is the name of the service registration property indicating the {@code RuntimeExtension} name. + */ + String NAME = "org.apache.sling.scripting.sightly.extension.name"; + + /** + * Call the {@code RuntimeExtension} + * + * @param renderContext the runtime context + * @param arguments the call arguments + * @return an extension instance + */ + Object call(RenderContext renderContext, Object... arguments); +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/extension/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/extension/package-info.java new file mode 100644 index 0000000..1bbe78a --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/extension/package-info.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +@Version("1.1.0") +package org.apache.sling.scripting.sightly.extension; + +import org.osgi.annotation.versioning.Version; diff --git a/src/main/java/org/apache/sling/scripting/sightly/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/package-info.java new file mode 100644 index 0000000..f2af1f3 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/package-info.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ + +@Version("2.0.0") +package org.apache.sling.scripting.sightly; + +import org.osgi.annotation.versioning.Version; diff --git a/src/main/java/org/apache/sling/scripting/sightly/pojo/Use.java b/src/main/java/org/apache/sling/scripting/sightly/pojo/Use.java new file mode 100644 index 0000000..722b79a --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/pojo/Use.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.pojo; + +import javax.script.Bindings; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * The <code>Use</code> interface can be implemented by Java objects which are instantiated as part of processing {@code data-sly-use} + * attributes. + * + * @see <a href="https://github.com/Adobe-Marketing-Cloud/htl-spec/blob/master/SPECIFICATION.md#221-use">HTL Block Statements - Use</a> + */ +@ConsumerType +public interface Use { + + /** + * <p> + * Called to initialize the Java object with the current Java Scripting API bindings. + * </p> + * <p> + * This method is called only if the object has been instantiated by HTL as part of processing the {@code data-sly-use} + * attribute. The Java Scripting API bindings provide all the global variables known to a script being executed. + * </p> + * + * @param bindings The Java Scripting API bindings. + */ + void init(Bindings bindings); + +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/pojo/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/pojo/package-info.java new file mode 100644 index 0000000..746ab8f --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/pojo/package-info.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +@Version("1.0.1") +package org.apache.sling.scripting.sightly.pojo; + +import org.osgi.annotation.versioning.Version; diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModel.java new file mode 100644 index 0000000..858693e --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModel.java @@ -0,0 +1,143 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.render; + +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.sling.scripting.sightly.Record; + +/** + * Default abstract implementation of {@link RuntimeObjectModel}. + */ +public abstract class AbstractRuntimeObjectModel implements RuntimeObjectModel { + + @Override + public boolean isPrimitive(Object obj) { + return ObjectModel.isPrimitive(obj); + } + + @Override + public boolean isDate(Object target) { + return (target instanceof Date || target instanceof Calendar); + } + + @Override + public boolean isNumber(Object target) { + if (target == null) { + return false; + } + if (target instanceof Number) { + return true; + } + String value = toString(target); + return NumberUtils.isCreatable(value); + } + + @Override + public boolean isCollection(Object target) { + return (target instanceof Collection) || (target instanceof Object[]) || (target instanceof Iterable) || + (target instanceof Iterator); + } + + @Override + public Object resolveProperty(Object target, Object property) { + if (target == null || property == null) { + return null; + } + Object resolved = null; + if (property instanceof Number) { + resolved = ObjectModel.getIndex(target, ((Number) property).intValue()); + } + if (resolved == null) { + resolved = getProperty(target, property); + } + return resolved; + } + + @Override + public boolean toBoolean(Object object) { + return ObjectModel.toBoolean(object); + } + + @Override + public Number toNumber(Object object) { + return ObjectModel.toNumber(object); + } + + @Override + public Date toDate(Object object) { + if (object instanceof Date) { + return (Date)object; + } else if (object instanceof Calendar) { + return ((Calendar)object).getTime(); + } + return null; + } + + @Override + public String toString(Object target) { + return ObjectModel.toString(target); + } + + @Override + public Collection<Object> toCollection(Object object) { + if (object instanceof Record) { + return ((Record) object).getPropertyNames(); + } + return ObjectModel.toCollection(object); + } + + @Override + public Map toMap(Object object) { + if (object instanceof Map) { + return (Map) object; + } else if (object instanceof Record) { + Map<String, Object> map = new HashMap<>(); + Record record = (Record) object; + Set<String> properties = record.getPropertyNames(); + for (String property : properties) { + map.put(property, record.getProperty(property)); + } + return map; + } + return Collections.emptyMap(); + } + + protected Object getProperty(Object target, Object propertyObj) { + if (target == null || propertyObj == null) { + return null; + } + String property = ObjectModel.toString(propertyObj); + Object result = null; + if (target instanceof Record) { + result = ((Record) target).getProperty(property); + } + if (result == null) { + result = ObjectModel.resolveProperty(target, propertyObj); + } + return result; + } + +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/ObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/render/ObjectModel.java new file mode 100644 index 0000000..6b437d7 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/render/ObjectModel.java @@ -0,0 +1,480 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@code ObjectModel} class provides various static models for object conversion and object property resolution. + */ +public final class ObjectModel { + + private static final Logger LOGGER = LoggerFactory.getLogger(ObjectModel.class); + private static final String EMPTY_STRING = ""; + + /** + * A {@link Set} that stores all the supported primitive classes. + */ + private static final Set<Class<?>> PRIMITIVE_CLASSES; + + static { + Set<Class<?>> primitivesBuilder = new HashSet<>(); + primitivesBuilder.add(Boolean.class); + primitivesBuilder.add(Character.class); + primitivesBuilder.add(Byte.class); + primitivesBuilder.add(Short.class); + primitivesBuilder.add(Integer.class); + primitivesBuilder.add(Long.class); + primitivesBuilder.add(Float.class); + primitivesBuilder.add(Double.class); + primitivesBuilder.add(Void.class); + PRIMITIVE_CLASSES = Collections.unmodifiableSet(primitivesBuilder); + } + + + private static final String TO_STRING_METHOD = "toString"; + + private ObjectModel() {} + + /** + * Checks if the provided {@code object} is an instance of a primitive class. + * + * @param object the {@code Object} to check + * @return {@code true} if the {@code object} is a primitive, {@code false} otherwise + */ + public static boolean isPrimitive(Object object) { + return PRIMITIVE_CLASSES.contains(object.getClass()); + } + + /** + * <p> + * Given the {@code target} object, this method attempts to resolve and return the value of the passed {@code property}. + * </p> + * <p> + * The property can be either an index or a name: + * </p> + * <ul> + * <li>index: the property is considered an index if its value is an integer number and in this case the {@code target} + * will be assumed to be either an array or it will be converted to a {@link Collection}; a fallback to {@link Map} will be + * made in case the previous two attempts failed + * </li> + * <li>name: the {@code property} will be converted to a {@link String} (see {@link #toString(Object)}); the {@code target} + * will be assumed to be either a {@link Map} or an object; if the {@link Map} attempt fails, the {@code property} will be + * used to check if the {@code target} has a publicly accessible field with this name or a publicly accessible method with no + * parameters with this name or a combination of the "get" or "is" prefixes plus the capitalised name (see + * {@link #invokeBeanMethod(Object, String)})</li> + * </ul> + * + * @param target the target object + * @param property the property to be resolved + * @return the value of the property or {@code null} + */ + public static Object resolveProperty(Object target, Object property) { + if (target == null || property == null) { + return null; + } + Object resolved = null; + if (property instanceof Number) { + resolved = getIndex(target, ((Number) property).intValue()); + } + if (resolved == null) { + String propertyName = toString(property); + if (StringUtils.isNotEmpty(propertyName)) { + if (target instanceof Map) { + resolved = ((Map) target).get(property); + } + if (resolved == null) { + resolved = getField(target, propertyName); + } + if (resolved == null) { + resolved = invokeBeanMethod(target, propertyName); + } + } + } + return resolved; + } + + /** + * Converts the given {@code object} to a boolean value, applying the following rules: + * + * <ul> + * <li>if the {@code object} is {@code null} the returned value is {@code false}</li> + * <li>if the {@code object} is a {@link Number} the method will return {@code false} only if the number's value is 0</li> + * <li>if the {@link String} representation of the {@code object} is equal irrespective of its casing to "true", the method will + * return {@code true}</li> + * <li>if the {@code object} is a {@link Collection} or a {@link Map}, the method will return {@code true} only if the collection / + * map is not empty</li> + * <li>if the object is an array, the method will return {@code true} only if the array is not empty</li> + * </ul> + * + * @param object the target object + * @return the boolean representation of the {@code object} according to the conversion rules + */ + public static boolean toBoolean(Object object) { + if (object == null) { + return false; + } + + if (object instanceof Number) { + Number number = (Number) object; + return !(number.doubleValue() == 0.0); + } + + String s = object.toString().trim(); + if (EMPTY_STRING.equals(s)) { + return false; + } else if ("true".equalsIgnoreCase(s) || "false".equalsIgnoreCase(s)) { + return Boolean.parseBoolean(s); + } + + if (object instanceof Collection) { + return ((Collection) object).size() > 0; + } + + if (object instanceof Map) { + return ((Map) object).size() > 0; + } + + if (object instanceof Iterable<?>) { + return ((Iterable<?>) object).iterator().hasNext(); + } + + if (object instanceof Iterator<?>) { + return ((Iterator<?>) object).hasNext(); + } + + return !(object instanceof Object[]) || ((Object[]) object).length > 0; + } + + /** + * Coerces the passed {@code object} to a numeric value. If the passed value is a {@link String} the conversion rules are those of + * {@link NumberUtils#createNumber(String)}. + * + * @param object the target object + * @return the numeric representation if one can be determined or {@code null} + * @see NumberUtils#createNumber(String) + */ + public static Number toNumber(Object object) { + if (object == null) { + return null; + } + if (object instanceof Number) { + return (Number) object; + } + String stringValue = toString(object); + try { + return NumberUtils.createNumber(stringValue); + } catch (NumberFormatException e) { + return null; + } + } + + /** + * Converts the passed {@code object} to a {@link String}. The following rules apply: + * + * <ul> + * <li>if the {@code object} is {@code null} an empty string will be returned</li> + * <li>if the {@code object} is an instance of a {@link String} the object itself will be returned</li> + * <li>if the object is a primitive (see {@link #isPrimitive(Object)}), its {@link String} representation will be returned</li> + * <li>if the object is an {@link Enum} its name will be returned (see {@link Enum#name()})</li> + * <li>otherwise an attempt to convert the object to a {@link Collection} will be made and then the output of + * {@link #collectionToString(Collection)} will be returned</li> + * </ul> + * + * @param object the target object + * @return the string representation of the object or an empty string + */ + public static String toString(Object object) { + String output = EMPTY_STRING; + if (object != null) { + if (object instanceof String) { + output = (String) object; + } else if (isPrimitive(object)) { + output = object.toString(); + } else if (object instanceof Enum) { + return ((Enum) object).name(); + } else { + Collection<?> col = toCollection(object); + output = collectionToString(col); + } + } + return output; + } + + /** + * Forces the conversion of the passed {@code object} to a collection, according to the following rules: + * + * <ul> + * <li>if the {@code object} is {@code null} an empty collection will be returned</li> + * <li>if the {@code object} is an array a list transformation of the array will be returned</li> + * <li>if the {@code object} is a {@link Collection} the object itself will be returned</li> + * <li>if the {@code object} is an instance of a {@link Map} the map's key set will be returned (see {@link Map#keySet()})</li> + * <li>if the {@code object} is an instance of an {@link Enumeration} a list transformation will be returned</li> + * <li>if the {@code object} is an instance of an {@link Iterator} or {@link Iterable} the result of {@link #fromIterator(Iterator)} + * will be returned</li> + * <li>if the {@code object} is an instance of a {@link String} or {@link Number} a {@link Collection} containing only this + * object will be returned</li> + * <li>any other case not covered by the previous rules will result in an empty {@link Collection}</li> + * </ul> + * + * @param object the target object + * @return the collection representation of the object + */ + public static Collection<Object> toCollection(Object object) { + if (object == null) { + return Collections.emptyList(); + } + if (object instanceof Object[]) { + return Arrays.asList((Object[]) object); + } + if (object.getClass().isArray()) { + int length = Array.getLength(object); + Collection<Object> list = new ArrayList<>(); + for (int i = 0; i < length; i++) { + list.add(Array.get(object, i)); + } + return list; + } + if (object instanceof Collection) { + return (Collection<Object>) object; + } + if (object instanceof Map) { + return ((Map) object).keySet(); + } + if (object instanceof Enumeration) { + return Collections.list((Enumeration<Object>) object); + } + if (object instanceof Iterator) { + return fromIterator((Iterator<Object>) object); + } + if (object instanceof Iterable) { + Iterable<Object> iterable = (Iterable<Object>) object; + return fromIterator(iterable.iterator()); + } + if (object instanceof String || object instanceof Number) { + Collection<Object> list = new ArrayList<>(); + list.add(object); + return list; + } + return Collections.emptyList(); + } + + /** + * Converts the passed {@code collection} to a comma separated values {@link String} representation. + * + * @param collection the collection to be converted to CSV + * @return the CSV; if the {@code collection} is empty then an empty string will be returned + */ + public static String collectionToString(Collection<?> collection) { + if (collection == null) { + return EMPTY_STRING; + } + StringBuilder builder = new StringBuilder(); + String prefix = EMPTY_STRING; + for (Object o : collection) { + builder.append(prefix).append(toString(o)); + prefix = ","; + } + return builder.toString(); + } + + /** + * Given an {@code iterator}, this method will return a {@link Collection}. + * + * @param iterator the iterator to be transformed into a {@code collection} + * @return a collection with the iterator's elements + */ + public static Collection<Object> fromIterator(Iterator<Object> iterator) { + if (iterator == null) { + return Collections.EMPTY_LIST; + } + ArrayList<Object> result = new ArrayList<>(); + while (iterator.hasNext()) { + result.add(iterator.next()); + } + return result; + } + + /** + * Given an indexable {@code object} (i.e. an array or a collection), this method will return the value available at the {@code + * index} position. + * + * @param object the indexable object + * @param index the index + * @return the value stored at the {@code index} or {@code null} + */ + public static Object getIndex(Object object, int index) { + if (object == null) { + return null; + } + Class<?> cls = object.getClass(); + if (cls.isArray() && index >= 0 && index < Array.getLength(object)) { + return Array.get(object, index); + } + Collection collection = toCollection(object); + if (collection instanceof List && index >= 0 && index < collection.size()) { + return ((List) collection).get(index); + } + return null; + } + + /** + * Given an {@code object}, this method will return the value of the public field identified by {@code fieldName}. + * + * @param object the target object + * @param fieldName the name of the field + * @return the value of the field or {@code null} if the field was not found + */ + public static Object getField(Object object, String fieldName) { + if (object == null || StringUtils.isEmpty(fieldName)) { + return null; + } + Class<?> cls = object.getClass(); + if (cls.isArray() && "length".equals(fieldName)) { + return Array.getLength(object); + } + try { + Field field = cls.getField(fieldName); + return field.get(object); + } catch (Exception e) { + return null; + } + } + + /** + * Given a bean {@code object}, this method will invoke the public method without parameters identified by {@code methodName} and + * return the invocation's result. + * + * @param object the target object + * @param methodName the name of the public method without parameters to invoke + * @return the invocation's result or {@code null} if such a method cannot be found + */ + public static Object invokeBeanMethod(Object object, String methodName) { + if (object == null || StringUtils.isEmpty(methodName)) { + return null; + } + Class<?> cls = object.getClass(); + Method method = findBeanMethod(cls, methodName); + if (method != null) { + try { + method = extractMethodInheritanceChain(cls, method); + return method.invoke(object); + } catch (Exception e) { + LOGGER.error("Cannot access method " + methodName + " on object " + object.toString(), e); + } + } + return null; + } + + /** + * Given a bean class and a base method name, this method will try to find a public method without parameters that is named: + * <ol> + * <li>{@code baseName}</li> + * <li>get + {@code BaseName}</li> + * <li>is + {@code BaseName}</li> + * </ol> + * + * @param cls the class into which to search for the method + * @param baseName the base method name + * @return a method that matches the criteria or {@code null} + */ + public static Method findBeanMethod(Class<?> cls, String baseName) { + if (cls == null || StringUtils.isEmpty(baseName)) { + return null; + } + Method[] publicMethods = cls.getMethods(); + String capitalized = StringUtils.capitalize(baseName); + for (Method method : publicMethods) { + if (method.getParameterTypes().length == 0) { + String methodName = method.getName(); + if (baseName.equals(methodName) || ("get" + capitalized).equals(methodName) || ("is" + capitalized).equals(methodName)) { + if (isMethodAllowed(method)) { + return method; + } + break; + } + } + } + return null; + } + + /** + * Returns {@code true} if the method is not one of the {@link Object}'s class declared methods, with the exception of + * {@link Object#toString()}. + * + * @param method the method to check + * @return {@code true} if the method is not one of the {@link Object}'s class declared methods, with the exception of + * {@link Object#toString()}, {@code false} otherwise + */ + public static boolean isMethodAllowed(Method method) { + if (method == null) { + return false; + } + Class<?> declaringClass = method.getDeclaringClass(); + return declaringClass != Object.class || TO_STRING_METHOD.equals(method.getName()); + } + + private static Method extractMethodInheritanceChain(Class type, Method method) { + if (method == null || Modifier.isPublic(type.getModifiers())) { + return method; + } + Class[] interfaces = type.getInterfaces(); + Method parentMethod; + for (Class<?> iface : interfaces) { + parentMethod = getClassMethod(iface, method); + if (parentMethod != null) { + return parentMethod; + } + } + return getClassMethod(type.getSuperclass(), method); + } + + private static Method getClassMethod(Class<?> type, Method method) { + try { + Method parentMethod = type.getMethod(method.getName(), method.getParameterTypes()); + parentMethod = extractMethodInheritanceChain(parentMethod.getDeclaringClass(), parentMethod); + if (parentMethod != null) { + return parentMethod; + } + } catch (NoSuchMethodException e) { + // ignore - maybe we don't have access to that method or the method does not belong to the current type + } + return null; + } + + +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/RenderContext.java b/src/main/java/org/apache/sling/scripting/sightly/render/RenderContext.java new file mode 100644 index 0000000..d57e4c8 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/render/RenderContext.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.render; + +import javax.script.Bindings; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * The {@code RenderContext} defines the context for executing HTL scripts. + */ +@ProviderType +public interface RenderContext { + + /** + * Provides the {@link RuntimeObjectModel} that will be used for resolving objects' properties or type conversion / coercion. + * + * @return the RuntimeObjectModel + */ + RuntimeObjectModel getObjectModel(); + + /** + * Returns the map of script bindings available to HTL scripts. + * + * @return the global bindings for a script + */ + Bindings getBindings(); + + /** + * Call one of the registered {@link org.apache.sling.scripting.sightly.extension.RuntimeExtension}s. + * + * @param functionName the name under which the extension is registered + * @param arguments the extension's arguments + * @return the {@link org.apache.sling.scripting.sightly.extension.RuntimeExtension}'s result + */ + Object call(String functionName, Object... arguments); +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/RenderUnit.java b/src/main/java/org/apache/sling/scripting/sightly/render/RenderUnit.java new file mode 100644 index 0000000..617565f --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/render/RenderUnit.java @@ -0,0 +1,156 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render; + +import java.io.PrintWriter; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.script.Bindings; +import javax.script.SimpleBindings; + +import org.apache.sling.scripting.sightly.Record; + +/** + * Basic unit of rendering. This also extends the record interface. The properties for a unit are the sub-units. + */ +public abstract class RenderUnit implements Record<RenderUnit> { + + private final Map<String, RenderUnit> subTemplates = new HashMap<>(); + + private Map<String, RenderUnit> siblings; + + /** + * Render the main script template + * + * @param out the {@link PrintWriter} to which the commands are written + * @param renderContext the rendering context + * @param arguments the arguments for this unit + */ + public final void render(PrintWriter out, RenderContext renderContext, Bindings arguments) { + Bindings globalBindings = renderContext.getBindings(); + render(out, buildGlobalScope(globalBindings), new CaseInsensitiveBindings(arguments), renderContext); + } + + @Override + public RenderUnit getProperty(String name) { + return subTemplates.get(name.toLowerCase()); + } + + @Override + public Set<String> getPropertyNames() { + return subTemplates.keySet(); + } + + protected abstract void render(PrintWriter out, + Bindings bindings, + Bindings arguments, + RenderContext renderContext); + + @SuppressWarnings({"unused", "unchecked"}) + protected void callUnit(PrintWriter out, RenderContext renderContext, Object templateObj, Object argsObj) { + if (!(templateObj instanceof RenderUnit)) { + if (templateObj == null) { + throw new RuntimeException("data-sly-call: expression evaluates to null."); + } + if (renderContext.getObjectModel().isPrimitive(templateObj)) { + throw new RuntimeException( + "data-sly-call: primitive \"" + templateObj.toString() + "\" does not represent a HTL template."); + } else if (templateObj instanceof String) { + throw new RuntimeException( + "data-sly-call: String '" + templateObj.toString() + "' does not represent a HTL template."); + } + throw new RuntimeException( + "data-sly-call: " + templateObj.getClass().getName() + " does not represent a HTL template."); + } + RenderUnit unit = (RenderUnit) templateObj; + Map<String, Object> argumentsMap = renderContext.getObjectModel().toMap(argsObj); + Bindings arguments = new SimpleBindings(Collections.unmodifiableMap(argumentsMap)); + unit.render(out, renderContext, arguments); + } + + @SuppressWarnings("UnusedDeclaration") + protected FluentMap obj() { + return new FluentMap(); + } + + @SuppressWarnings("unused") + protected final void addSubTemplate(String name, RenderUnit renderUnit) { + renderUnit.setSiblings(subTemplates); + subTemplates.put(name.toLowerCase(), renderUnit); + } + + private void setSiblings(Map<String, RenderUnit> siblings) { + this.siblings = siblings; + } + + private Bindings buildGlobalScope(Bindings bindings) { + SimpleBindings simpleBindings = new SimpleBindings(bindings); + simpleBindings.putAll(bindings); + if (siblings != null) { + simpleBindings.putAll(siblings); + } + simpleBindings.putAll(subTemplates); + return new CaseInsensitiveBindings(simpleBindings); + } + + protected static class FluentMap extends HashMap<String, Object> { + + /** + * Fluent variant of put. + * + * @param name the name of the property + * @param value the value of the property + * @return this instance + */ + public FluentMap with(String name, Object value) { + put(name, value); + return this; + } + + } + + private static final class CaseInsensitiveBindings extends SimpleBindings { + + private CaseInsensitiveBindings(Map<String, Object> m) { + for (Entry<String, Object> entry : m.entrySet()) { + put(entry.getKey().toLowerCase(), entry.getValue()); + } + } + + @Override + public Object get(Object key) { + if (!(key instanceof String)) { + throw new ClassCastException("key should be a String"); + } + return super.get(((String) key).toLowerCase()); + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof String)) { + throw new ClassCastException("key should be a String"); + } + return super.containsKey(((String) key).toLowerCase()); + } + } + +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/RuntimeObjectModel.java b/src/main/java/org/apache/sling/scripting/sightly/render/RuntimeObjectModel.java new file mode 100644 index 0000000..f386634 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/render/RuntimeObjectModel.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.render; + +import java.util.Collection; +import java.util.Date; +import java.util.Map; + +import org.osgi.annotation.versioning.ProviderType; + +/** + * The {@code RuntimeObjectModel} provides various utility object inspection & conversion methods that can be applied to runtime + * objects when executing HTL scripts. + */ +@ProviderType +public interface RuntimeObjectModel { + + /** + * Checks if the provided object represents a primitive data type or not. + * + * @param obj the target object + * @return {@code true} if the {@code target} is a primitive, {@code false} otherwise + */ + boolean isPrimitive(Object obj); + + /** + * Checks if an object is a {@link Collection} or is backed by one. + * + * @param target the target object + * @return {@code true} if the {@code target} is a collection or is backed by one, {@code false} otherwise + */ + boolean isCollection(Object target); + + /** + * Checks if the provided object represents a number or not. + * + * @param target the target object + * @return {@code true} if the {@code target} is a number, {@code false} otherwise + */ + boolean isNumber(Object target); + + /** + * Checks if the provided object represents a date or calendar. + * + * @param target the target object + * @return {@code true} if the {@code target} is a date or calendar, {@code false} otherwise + */ + boolean isDate(Object target); + + /** + * Resolve a property of a target object and return its value. The property can + * be either an index or a name + * + * @param target the target object + * @param property the property to be resolved + * @return the value of the property + */ + Object resolveProperty(Object target, Object property); + + /** + * Convert the given object to a boolean value + * + * @param object the target object + * @return the boolean representation of that object + */ + boolean toBoolean(Object object); + + /** + * Coerce the object to a numeric value + * + * @param object the target object + * @return the numeric representation + */ + Number toNumber(Object object); + + /** + * Convert the given object to a {@link Date} object + * + * @param object the target object + * @return the date represented by the {@code object} + */ + Date toDate(Object object); + + /** + * Convert the given object to a string. + * + * @param target the target object + * @return the string representation of the object + */ + String toString(Object target); + + /** + * Force the conversion of the object to a collection + * + * @param object the target object + * @return the collection representation of the object + */ + Collection<Object> toCollection(Object object); + + /** + * Force the conversion of the target object to a map + * + * @param object the target object + * @return a map representation of the object. Default is an empty map + */ + Map toMap(Object object); +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/render/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/render/package-info.java new file mode 100644 index 0000000..3afbfb9 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/render/package-info.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +@Version("3.0.0") +package org.apache.sling.scripting.sightly.render; + +import org.osgi.annotation.versioning.Version; diff --git a/src/main/java/org/apache/sling/scripting/sightly/use/ProviderOutcome.java b/src/main/java/org/apache/sling/scripting/sightly/use/ProviderOutcome.java new file mode 100644 index 0000000..83fb379 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/use/ProviderOutcome.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.use; + +/** + * Result returned by a {@link UseProvider}. + */ +public final class ProviderOutcome { + + // a generic failure without a cause returned by #failure() + private static final ProviderOutcome GENERIC_FAILURE = new ProviderOutcome(false, null, null); + + // whether this is a success or failure + private final boolean success; + + // the result value in case of success (may be null) + private final Object result; + + // the reason for failure in case of failure (may be null) + private final Throwable cause; + + /** + * Creates an outcome instance + * + * @param success {@code true} to indicate success or {@code false} to indicate failure + * @param result optional result value in case of success, may be {@code null} + * @param cause optional cause in case of failure, may be {@code null} + */ + private ProviderOutcome(boolean success, Object result, Throwable cause) { + this.success = success; + this.result = result; + this.cause = cause; + } + + /** + * Create a successful outcome + * + * @param result the result + * @return a successful result + */ + public static ProviderOutcome success(Object result) { + return new ProviderOutcome(true, result, null); + } + + /** + * Create a failed outcome without a specific {@link #getCause() cause}. This method must be used for creating outcomes that don't + * signal an error but rather the fact that the {@link UseProvider} is not capable of fulfilling the request. + * + * @return a failed outcome + */ + public static ProviderOutcome failure() { + return GENERIC_FAILURE; + } + + /** + * Create a failed outcome with the given {@link #getCause() cause}. This method must be used when the {@link UseProvider} is + * capable of fulfilling the request but an error condition prevents the provider from doing so. + * + * @param cause The reason for this failure, which may be {@code null} + * @return a failed outcome + */ + public static ProviderOutcome failure(Throwable cause) { + return new ProviderOutcome(false, null, cause); + } + + /** + * If the given obj is not {@code null} return a {@link #success(Object) successful outcome}, with the given result. Otherwise, return + * {@link #failure()}. + * + * @param obj the result + * @return an outcome based on whether the parameter is null or not + */ + public static ProviderOutcome notNullOrFailure(Object obj) { + return (obj == null) ? failure() : success(obj); + } + + /** + * Check if the outcome has been successful + * + * @return the outcome success status + */ + public boolean isSuccess() { + return success; + } + + /** + * Check whether the outcome is a failure + * + * @return the outcome failure status + */ + public boolean isFailure() { + return !isSuccess(); + } + + /** + * Get the result in this outcome. + * + * @return the result of the container + * @throws IllegalStateException if the outcome is a failure + */ + public Object getResult() { + if (!success) { + throw new IllegalStateException("Outcome has not been successful"); + } + return result; + } + + /** + * Returns the cause for this failure outcome or {@code null} if this outcome is a success or no cause has been defined with the + * {@link #failure(Throwable)} method. + * + * @return the cause for this failure outcome. + */ + public Throwable getCause() { + return cause; + } +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/use/UseProvider.java b/src/main/java/org/apache/sling/scripting/sightly/use/UseProvider.java new file mode 100644 index 0000000..14a78ba --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/use/UseProvider.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ + +package org.apache.sling.scripting.sightly.use; + +import javax.script.Bindings; + +import org.apache.sling.scripting.sightly.render.RenderContext; + +import org.osgi.annotation.versioning.ConsumerType; + +/** + * <p> + * A {@code UseProvider} instantiates objects for the Use-API. + * </p> + */ +@ConsumerType +public interface UseProvider { + + /** + * Provide an instance based on the given identifier + * + * @param identifier the identifier of the dependency + * @param renderContext the current rendering context + * @param arguments specific arguments provided by the use plugin + * @return a container with the instance that corresponds to the identifier; if the identifier cannot be + * handled by this provider, a failed outcome is returned. + */ + ProviderOutcome provide(String identifier, RenderContext renderContext, Bindings arguments); +} diff --git a/src/main/java/org/apache/sling/scripting/sightly/use/package-info.java b/src/main/java/org/apache/sling/scripting/sightly/use/package-info.java new file mode 100644 index 0000000..0dfe431 --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/sightly/use/package-info.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + ******************************************************************************/ +@Version("1.0.1") +package org.apache.sling.scripting.sightly.use; + +import org.osgi.annotation.versioning.Version; diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModelTest.java b/src/test/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModelTest.java new file mode 100644 index 0000000..ab9c07b --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/AbstractRuntimeObjectModelTest.java @@ -0,0 +1,168 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ******************************************************************************/ +package org.apache.sling.scripting.sightly.render; + +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.sling.scripting.sightly.Record; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class AbstractRuntimeObjectModelTest { + + private AbstractRuntimeObjectModel runtimeObjectModel = new AbstractRuntimeObjectModel() {}; + + @Test + public void testResolveProperty() { + assertNull(runtimeObjectModel.resolveProperty(null, null)); + assertNull(runtimeObjectModel.resolveProperty(this, null)); + assertNull(runtimeObjectModel.resolveProperty(this, "")); + assertEquals(0, runtimeObjectModel.resolveProperty(Collections.EMPTY_LIST, "size")); + assertNull(runtimeObjectModel.resolveProperty(null, null)); + int[] ints = new int[] {1, 2, 3}; + assertEquals(ints.length, runtimeObjectModel.resolveProperty(ints, "length")); + Integer[] testArray = new Integer[] {1, 2, 3}; + assertEquals(testArray.length, runtimeObjectModel.resolveProperty(testArray, "length")); + assertEquals(2, runtimeObjectModel.resolveProperty(testArray, 1)); + assertNull(runtimeObjectModel.resolveProperty(testArray, 3)); + assertNull(runtimeObjectModel.resolveProperty(testArray, -1)); + List<Integer> testList = Arrays.asList(testArray); + assertEquals(2, runtimeObjectModel.resolveProperty(testList, 1)); + assertNull(runtimeObjectModel.resolveProperty(testList, 3)); + assertNull(runtimeObjectModel.resolveProperty(testList, -1)); + Map<String, Integer> map = new HashMap<String, Integer>() {{ + put("one", 1); + put("two", 2); + }}; + assertEquals(1, runtimeObjectModel.resolveProperty(map, "one")); + assertNull(runtimeObjectModel.resolveProperty(map, null)); + assertNull(runtimeObjectModel.resolveProperty(map, "")); + Map<Integer, String> stringMap = new HashMap<Integer, String>(){{ + put(1, "one"); + put(2, "two"); + }}; + assertEquals("one", runtimeObjectModel.resolveProperty(stringMap, 1)); + assertEquals("two", runtimeObjectModel.resolveProperty(stringMap, 2)); + Map<String, String> strings = new HashMap<String, String>(){{ + put("a", "one"); + put("b", "two"); + }}; + Record<String> record = new Record<String>() { + @Override + public String getProperty(String name) { + return strings.get(name); + } + + @Override + public Set<String> getPropertyNames() { + return strings.keySet(); + } + }; + assertEquals("one", runtimeObjectModel.resolveProperty(record, "a")); + } + + @Test + public void testToDate() { + assertNull(runtimeObjectModel.toDate(null)); + Date testDate = new Date(); + assertEquals(testDate, runtimeObjectModel.toDate(testDate)); + Calendar testCalendar = Calendar.getInstance(); + assertEquals(testCalendar.getTime(), runtimeObjectModel.toDate(testCalendar)); + } + + @Test + public void testGetPropertyNullChecks() { + assertNull(runtimeObjectModel.getProperty(null, null)); + assertNull(runtimeObjectModel.getProperty(this, null)); + assertNull(runtimeObjectModel.getProperty(this, "")); + } + + @Test + public void testIsDate() { + assertFalse(runtimeObjectModel.isDate(null)); + assertTrue(runtimeObjectModel.isDate(new Date())); + assertTrue(runtimeObjectModel.isDate(Calendar.getInstance())); + } + + @Test + public void testIsNumber() { + assertFalse(runtimeObjectModel.isNumber(null)); + assertFalse(runtimeObjectModel.isNumber("")); + assertTrue(runtimeObjectModel.isNumber(0)); + assertTrue(runtimeObjectModel.isNumber(0.5)); + assertTrue(runtimeObjectModel.isNumber("0")); + assertTrue(runtimeObjectModel.isNumber("0.5")); + } + + @Test + public void testToCollection() { + assertTrue(runtimeObjectModel.toCollection(null).isEmpty()); + Record<String> record = new Record<String>() { + + private Map<String, String> properties = new HashMap<String, String>() {{ + put("a", "1"); + put("b", "2"); + }}; + + @Override + public String getProperty(String name) { + return properties.get(name); + } + + @Override + public Set<String> getPropertyNames() { + return properties.keySet(); + } + }; + Collection testCollection = runtimeObjectModel.toCollection(record); + assertEquals(2, testCollection.size()); + assertTrue(testCollection.contains("a")); + assertTrue(testCollection.contains("b")); + } + + @Test + public void testToMap() { + final Map<String, String> properties = new HashMap<String, String>() {{ + put("a", "1"); + put("b", "2"); + }}; + assertEquals(properties, runtimeObjectModel.toMap(properties)); + Record<String> record = new Record<String>() { + @Override + public String getProperty(String name) { + return properties.get(name); + } + + @Override + public Set<String> getPropertyNames() { + return properties.keySet(); + } + }; + assertEquals(properties, runtimeObjectModel.toMap(record)); + assertTrue(runtimeObjectModel.toMap(null).isEmpty()); + } + +} diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/ObjectModelTest.java b/src/test/java/org/apache/sling/scripting/sightly/render/ObjectModelTest.java new file mode 100644 index 0000000..28f88bd --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/ObjectModelTest.java @@ -0,0 +1,245 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.apache.sling.scripting.sightly.render.testobjects.Person; +import org.apache.sling.scripting.sightly.render.testobjects.TestEnum; +import org.apache.sling.scripting.sightly.render.testobjects.internal.AdultFactory; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class ObjectModelTest { + + @Test + public void testToBoolean() { + assertFalse(ObjectModel.toBoolean(null)); + assertFalse(ObjectModel.toBoolean(0)); + assertTrue(ObjectModel.toBoolean(123456)); + assertFalse(ObjectModel.toBoolean("")); + assertFalse(ObjectModel.toBoolean("FalSe")); + assertFalse(ObjectModel.toBoolean("false")); + assertFalse(ObjectModel.toBoolean("FALSE")); + assertTrue(ObjectModel.toBoolean("true")); + assertTrue(ObjectModel.toBoolean("TRUE")); + assertTrue(ObjectModel.toBoolean("TrUE")); + Integer[] testArray = new Integer[] {1, 2, 3}; + int[] testPrimitiveArray = new int[] {1, 2, 3}; + List testList = Arrays.asList(testArray); + assertTrue(ObjectModel.toBoolean(testArray)); + assertTrue(ObjectModel.toBoolean(testPrimitiveArray)); + assertFalse(ObjectModel.toBoolean(new Integer[]{})); + assertTrue(ObjectModel.toBoolean(testList)); + assertFalse(ObjectModel.toBoolean(Collections.emptyList())); + Map<String, Integer> map = new HashMap<String, Integer>() {{ + put("one", 1); + put("two", 2); + }}; + assertTrue(ObjectModel.toBoolean(map)); + assertFalse(ObjectModel.toBoolean(Collections.EMPTY_MAP)); + assertTrue(ObjectModel.toBoolean(testList.iterator())); + assertFalse(ObjectModel.toBoolean(Collections.EMPTY_LIST.iterator())); + assertTrue(ObjectModel.toBoolean(new Bag<>(testArray))); + assertFalse(ObjectModel.toBoolean(new Bag<>(new Integer[]{}))); + assertTrue(ObjectModel.toBoolean(new Date())); + } + + @Test + public void testToNumber() { + assertEquals(1, ObjectModel.toNumber(1)); + assertEquals(1, ObjectModel.toNumber("1")); + assertNull(ObjectModel.toNumber(null)); + assertNull(ObjectModel.toNumber("1-2")); + } + + @Test + public void testToString() { + assertEquals("", ObjectModel.toString(null)); + assertEquals("1", ObjectModel.toString("1")); + assertEquals("1", ObjectModel.toString(1)); + assertEquals("CONSTANT", ObjectModel.toString(TestEnum.CONSTANT)); + Integer[] testArray = new Integer[] {1, 2, 3}; + int[] testPrimitiveArray = new int[] {1, 2, 3}; + List testList = Arrays.asList(testArray); + assertEquals("1,2,3", ObjectModel.toString(testList)); + assertEquals("1,2,3", ObjectModel.toString(testArray)); + assertEquals("1,2,3", ObjectModel.toString(testPrimitiveArray)); + } + + @Test + public void testToCollection() { + assertTrue(ObjectModel.toCollection(null).isEmpty()); + assertTrue(ObjectModel.toCollection(new StringBuilder()).isEmpty()); + Integer[] testArray = new Integer[] {1, 2, 3}; + int[] testPrimitiveArray = new int[]{1, 2, 3}; + List<Integer> testList = Arrays.asList(testArray); + Map<String, Integer> map = new HashMap<String, Integer>() {{ + put("one", 1); + put("two", 2); + }}; + assertEquals(testList, ObjectModel.toCollection(testArray)); + assertEquals(testList, ObjectModel.toCollection(testPrimitiveArray)); + assertEquals(testList, ObjectModel.toCollection(testList)); + assertEquals(map.keySet(), ObjectModel.toCollection(map)); + Vector<Integer> vector = new Vector<>(testList); + assertEquals(testList, ObjectModel.toCollection(vector.elements())); + assertEquals(testList, ObjectModel.toCollection(testList.iterator())); + assertEquals(testList, ObjectModel.toCollection(new Bag<>(testArray))); + String stringObject = "test"; + Integer numberObject = 1; + Collection stringCollection = ObjectModel.toCollection(stringObject); + assertTrue(stringCollection.size() == 1 && stringCollection.contains(stringObject)); + Collection numberCollection = ObjectModel.toCollection(numberObject); + assertTrue(numberCollection.size() == 1 && numberCollection.contains(numberObject)); + } + + @Test + public void testCollectionToString() { + assertEquals("", ObjectModel.collectionToString(null)); + Integer[] testArray = new Integer[] {1, 2, 3}; + List testList = Arrays.asList(testArray); + assertEquals("1,2,3", ObjectModel.collectionToString(testList)); + } + + @Test + public void testFromIterator() { + assertTrue(ObjectModel.fromIterator(null).isEmpty()); + Integer[] testArray = new Integer[] {1, 2, 3}; + List testList = Arrays.asList(testArray); + assertEquals(testList, ObjectModel.fromIterator(testList.iterator())); + } + + @Test + public void testResolveProperty() { + assertNull(ObjectModel.resolveProperty(null, 0)); + assertNull(ObjectModel.resolveProperty(this, null)); + assertNull(ObjectModel.resolveProperty(null, null)); + assertEquals(0, ObjectModel.resolveProperty(Collections.EMPTY_LIST, "size")); + Integer[] testArray = new Integer[] {1, 2, 3}; + assertEquals(2, ObjectModel.resolveProperty(testArray, 1)); + assertNull(ObjectModel.resolveProperty(testArray, 3)); + assertNull(ObjectModel.resolveProperty(testArray, -1)); + List<Integer> testList = Arrays.asList(testArray); + assertEquals(2, ObjectModel.resolveProperty(testList, 1)); + assertNull(ObjectModel.resolveProperty(testList, 3)); + assertNull(ObjectModel.resolveProperty(testList, -1)); + Map<String, Integer> map = new HashMap<String, Integer>() {{ + put("one", 1); + put("two", 2); + }}; + assertEquals(1, ObjectModel.resolveProperty(map, "one")); + assertNull(ObjectModel.resolveProperty(map, null)); + assertNull(ObjectModel.resolveProperty(map, "")); + Map<Integer, String> stringMap = new HashMap<Integer, String>(){{ + put(1, "one"); + put(2, "two"); + }}; + assertEquals("one", ObjectModel.resolveProperty(stringMap, 1)); + assertEquals("two", ObjectModel.resolveProperty(stringMap, 2)); + Person johnDoe = AdultFactory.createAdult("John", "Doe"); + assertEquals("Expected to be able to access public static final constants.", 1l, ObjectModel.resolveProperty(johnDoe, "CONSTANT")); + assertNull("Did not expect to be able to access public fields from package protected classes.", ObjectModel.resolveProperty(johnDoe, + "TODAY")); + assertEquals("Expected to be able to access an array's length property.", 3, ObjectModel.resolveProperty(testArray, "length")); + assertNotNull("Expected not null result for invocation of interface method on implementation class.", + ObjectModel.resolveProperty(johnDoe, "lastName")); + assertNull("Expected null result for public method available on implementation but not exposed by interface.", ObjectModel + .resolveProperty(johnDoe, "fullName")); + assertNull("Expected null result for inexistent method.", ObjectModel.resolveProperty(johnDoe, "nomethod")); + } + + @Test + public void testGetIndex() { + assertNull(ObjectModel.getIndex(null, 0)); + Integer[] testArray = new Integer[] {1, 2, 3}; + assertEquals(2, ObjectModel.getIndex(testArray, 1)); + assertNull(ObjectModel.getIndex(testArray, 3)); + assertNull(ObjectModel.getIndex(testArray, -1)); + List<Integer> testList = Arrays.asList(testArray); + assertEquals(2, ObjectModel.getIndex(testList, 1)); + assertNull(ObjectModel.getIndex(testList, 3)); + assertNull(ObjectModel.getIndex(testList, -1)); + Map<Integer, String> stringMap = new HashMap<Integer, String>(){{ + put(1, "one"); + put(2, "two"); + }}; + assertNull(ObjectModel.getIndex(stringMap, 1)); + assertNull(ObjectModel.getIndex(stringMap, 2)); + } + + @Test + public void testClassBasedMethodsForNulls() { + assertNull(ObjectModel.getField(null, null)); + assertNull(ObjectModel.getField("", null)); + assertNull(ObjectModel.getField(this, "")); + assertNull(ObjectModel.findBeanMethod(null, null)); + assertNull(ObjectModel.findBeanMethod(this.getClass(), null)); + assertNull(ObjectModel.findBeanMethod(this.getClass(), "")); + assertNull(ObjectModel.invokeBeanMethod(null, null)); + assertNull(ObjectModel.invokeBeanMethod(this, null)); + assertNull(ObjectModel.invokeBeanMethod(this, "")); + } + + + private class Bag<T> implements Iterable<T> { + + private T[] backingArray; + + public Bag(T[] array) { + this.backingArray = array; + } + + @Override + public Iterator<T> iterator() { + return new Iterator<T>() { + + int index = 0; + + @Override + public boolean hasNext() { + return index < backingArray.length; + } + + @Override + public T next() { + return backingArray[index++]; + } + + @Override + public void remove() { + + } + }; + } + } +} diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/Person.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/Person.java new file mode 100644 index 0000000..fc0c3f8 --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/Person.java @@ -0,0 +1,29 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render.testobjects; + +public interface Person { + + long CONSTANT = 1; + + String getFirstName(); + + String getLastName(); + +} diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/TestEnum.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/TestEnum.java new file mode 100644 index 0000000..49c4072 --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/TestEnum.java @@ -0,0 +1,25 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render.testobjects; + +public enum TestEnum { + + CONSTANT + +} diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AbstractPerson.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AbstractPerson.java new file mode 100644 index 0000000..121cd8e --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AbstractPerson.java @@ -0,0 +1,42 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render.testobjects.internal; + +import org.apache.sling.scripting.sightly.render.testobjects.Person; + +abstract class AbstractPerson implements Person { + + private String firstName; + private String lastName; + + public AbstractPerson(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + @Override + public String getFirstName() { + return firstName; + } + + @Override + public String getLastName() { + return lastName; + } +} diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/Adult.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/Adult.java new file mode 100644 index 0000000..10df9b0 --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/Adult.java @@ -0,0 +1,32 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render.testobjects.internal; + +class Adult extends AbstractPerson { + + public static final long TODAY = System.currentTimeMillis(); + + Adult(String firstName, String lastName) { + super(firstName, lastName); + } + + public String getFullName() { + return getFirstName() + ", " + getLastName(); + } +} diff --git a/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AdultFactory.java b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AdultFactory.java new file mode 100644 index 0000000..b2f879b --- /dev/null +++ b/src/test/java/org/apache/sling/scripting/sightly/render/testobjects/internal/AdultFactory.java @@ -0,0 +1,26 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.scripting.sightly.render.testobjects.internal; + +public class AdultFactory { + + public static Adult createAdult(String firstName, String lastName) { + return new Adult(firstName, lastName); + } +}
