diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc
new file mode 100644
index 00000000..2ae60f7d
--- /dev/null
+++ b/CONTRIBUTING.adoc
@@ -0,0 +1,9 @@
+= CONTRIBUTING
+
+To create or resove issues or to contribute code or documentation, please visit the following WIKI pages:
+
+* for https://github.com/oasp-forge/oasp4j-wiki/wiki/oasp-issue-work[Issue creation and resolution]
+* for https://github.com/oasp-forge/oasp4j-wiki/wiki/oasp-code-contributions[Contributions of code]
+* for https://github.com/oasp-forge/oasp4j-wiki/wiki/oasp-documentation[Contributions to documentation]
+
+Thanks for your contribution!
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 00000000..fce5e4c5
--- /dev/null
+++ b/LICENSE.txt
@@ -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 2015-2018 Capgemini SE.
+
+ 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/boms/bom/pom.xml b/boms/bom/pom.xml
new file mode 100644
index 00000000..5f07d8a1
--- /dev/null
+++ b/boms/bom/pom.xml
@@ -0,0 +1,263 @@
+
+
+ 4.0.0
+
+ com.devonfw.java.dev
+ devon4j-boms
+ dev-SNAPSHOT
+
+ com.devonfw.java.boms
+ devon4j-bom
+ ${devon4j.version}
+ pom
+ ${project.artifactId}
+ Dependencies (BOM) of the Open Application Standard Platform for Java (devon4j) based on spring boot.
+ https://devon4j.github.io/
+ 2014
+
+
+ UTF-8
+ UTF-8
+ 3.2.5
+ 7.5.1
+ bom
+
+
+
+
+
+
+ com.devonfw.java.boms
+ devon4j-minimal-bom
+ ${devon4j.version}
+ pom
+ import
+
+
+
+
+ net.sf.m-m-m
+ mmm-util-core
+ ${mmm.util.version}
+
+
+ net.sf.m-m-m
+ mmm-util-validation
+ ${mmm.util.version}
+
+
+ net.sf.m-m-m
+ mmm-util-search
+ ${mmm.util.version}
+
+
+ net.sf.m-m-m
+ mmm-util-entity
+ ${mmm.util.version}
+
+
+
+ javax.annotation
+ jsr250-api
+ 1.0
+
+
+
+ javax.inject
+ javax.inject
+ 1
+
+
+
+ javax.annotation
+ javax.annotation-api
+ 1.2
+
+
+
+ com.google.guava
+ guava
+ 17.0
+
+
+
+ org.apache.commons
+ commons-collections4
+ 4.1
+
+
+
+ org.javamoney
+ moneta
+ 0.8
+
+
+
+ org.hibernate.javax.persistence
+ hibernate-jpa-2.1-api
+ 1.0.0.Final
+
+
+
+ cglib
+ cglib
+ 3.2.5
+
+
+
+ org.apache.commons
+ commons-dbcp2
+ 2.0.1
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.7
+
+
+
+ javax.servlet
+ javax.servlet-api
+ 3.1.0
+ provided
+
+
+
+ javax.servlet
+ jstl
+ 1.2
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.1
+
+
+
+ org.apache.cxf
+ cxf-core
+ ${cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-frontend-jaxws
+ ${cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-frontend-jaxrs
+ ${cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-rs-service-description
+ ${cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-transports-http
+ ${cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-transports-local
+ ${cxf.version}
+
+
+ org.apache.cxf
+ cxf-rt-rs-client
+ ${cxf.version}
+
+
+
+ org.json
+ json
+ 20180130
+
+
+
+ com.fasterxml.jackson.jaxrs
+ jackson-jaxrs-json-provider
+ 2.4.2
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.3.3
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.3.3
+
+
+
+ net.sf.dozer
+ dozer
+ 5.5.1
+
+
+
+ ma.glasnost.orika
+ orika-core
+ 1.4.6
+
+
+
+ javax.validation
+ validation-api
+ 1.1.0.Final
+
+
+
+
+
+ junit
+ junit
+ 4.12
+
+
+
+ org.mockito
+ mockito-core
+ 1.9.5
+
+
+
+ com.github.tomakehurst
+ wiremock
+ 1.54
+
+
+
+
+ javax.el
+ javax.el-api
+ 3.0.0
+
+
+ org.glassfish.web
+ javax.el
+ 2.2.6
+
+
+
+
+ org.owasp
+ security-logging-logback
+ 1.1.3
+
+
+
+
+
+
diff --git a/boms/minimal/pom.xml b/boms/minimal/pom.xml
new file mode 100644
index 00000000..da9dc7ff
--- /dev/null
+++ b/boms/minimal/pom.xml
@@ -0,0 +1,177 @@
+
+
+ 4.0.0
+
+ com.devonfw.java.dev
+ devon4j-boms
+ dev-SNAPSHOT
+
+ com.devonfw.java.boms
+ devon4j-minimal-bom
+ ${devon4j.version}
+ pom
+ ${project.artifactId}
+ Dependencies (BOM) of the Open Application Standard Platform for Java (devon4j) without any thirdparty.
+ https://devon4j.github.io/
+ 2014
+
+
+ UTF-8
+ UTF-8
+ bom
+
+
+
+
+
+
+ com.devonfw.java.modules
+ devon4j-test
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-logging
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-basic
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-batch
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-beanmapping
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-configuration
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-security
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-service
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-json
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-rest
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-cxf-client
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-cxf-client-rest
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-cxf-client-ws
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-cxf-server
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-cxf-server-rest
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-cxf-server-ws
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-jpa
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-jpa-basic
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-jpa-spring-data
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-jpa-dao
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-jpa-envers
+ ${project.version}
+
+
+ com.devonfw.java.modules
+ devon4j-web
+ ${project.version}
+
+
+
+ com.devonfw.java.starters
+ devon4j-starter-cxf-client
+ ${project.version}
+
+
+ com.devonfw.java.starters
+ devon4j-starter-cxf-client-rest
+ ${project.version}
+
+
+ com.devonfw.java.starters
+ devon4j-starter-cxf-client-ws
+ ${project.version}
+
+
+ com.devonfw.java.starters
+ devon4j-starter-cxf-server
+ ${project.version}
+
+
+ com.devonfw.java.starters
+ devon4j-starter-cxf-server-rest
+ ${project.version}
+
+
+ com.devonfw.java.starters
+ devon4j-starter-cxf-server-ws
+ ${project.version}
+
+
+ com.devonfw.java.starters
+ devon4j-starter-spring-data-jpa
+ ${project.version}
+
+
+
+
+
diff --git a/boms/pom.xml b/boms/pom.xml
new file mode 100644
index 00000000..86186823
--- /dev/null
+++ b/boms/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+ com.devonfw.java.dev
+ devon4j
+ dev-SNAPSHOT
+
+ devon4j-boms
+ pom
+ ${project.artifactId}
+ Bill of Materials (BOM) for the Open Application Standard Platform for Java (devon4j).
+
+
+ minimal
+ bom
+
+
+
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..28881951
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,3 @@
+# IMPORTANT
+
+The content of this folder and it's subfolders is **public** under https://oasp.github.io/oasp4j
\ No newline at end of file
diff --git a/docs/oomph/projects/OASP4J.setup b/docs/oomph/projects/OASP4J.setup
new file mode 100644
index 00000000..588fdc01
--- /dev/null
+++ b/docs/oomph/projects/OASP4J.setup
@@ -0,0 +1,148 @@
+
+
+
+
+
+ The Github user ID
+
+
+
+
+ Choose from the available Github URIs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The OASP4J github project
+
diff --git a/modules/basic/pom.xml b/modules/basic/pom.xml
new file mode 100644
index 00000000..74f5bf04
--- /dev/null
+++ b/modules/basic/pom.xml
@@ -0,0 +1,28 @@
+
+ 4.0.0
+
+ com.devonfw.java.dev
+ devon4j-modules
+ dev-SNAPSHOT
+
+
+ com.devonfw.java.modules
+ devon4j-basic
+ ${devon4j.version}
+ jar
+ ${project.artifactId}
+ Basic code for common usage (such as base classes for transfer objects) of the Open Application Standard Platform for Java (devon4j).
+
+
+
+ net.sf.m-m-m
+ mmm-util-entity
+
+
+ ${project.groupId}
+ devon4j-test
+ test
+
+
+
\ No newline at end of file
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/ConfigProperties.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/ConfigProperties.java
new file mode 100644
index 00000000..5f1a945a
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/ConfigProperties.java
@@ -0,0 +1,119 @@
+package com.devonfw.module.basic.common.api.config;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Simple abstraction interface for generic access to configuration properties (from spring-boot
+ * {@code application.properties}).
+ *
+ * @since 3.0.0
+ */
+public interface ConfigProperties {
+
+ /** The separator charactor '.' for hierarchical keys such as "spring.datasource.url". */
+ char KEY_SEPARATOR = '.';
+
+ /** An immutable instance of {@link ConfigProperties} that is always {@link #isEmpty() empty} */
+ ConfigProperties EMPTY = new EmptyConfigProperties();
+
+ /**
+ * @return the {@link Set} of the {@link #getChild(String) direct child keys} available in the
+ * {@link ConfigProperties}-node.
+ */
+ Set getChildKeys();
+
+ /**
+ * @param key the {@link #getChildKeys() child key} of the requested configuration value.
+ * @return the child {@link ConfigProperties}. Will be an {@link #isEmpty() empty} child if undefined.
+ */
+ ConfigProperties getChild(String key);
+
+ /**
+ * Recursive variant of {@link #getChild(String)} such that
+ * {@link ConfigProperties config}.{@link #getChild(String...) getChild}(key1, ..., keyN) is the same as
+ * {@link ConfigProperties config}.{@link #getChild(String) getChild}(key1)...{@link #getChild(String) getChild}(keyN).
+ *
+ * @param keys the keys to traverse recursively.
+ * @return the descendant {@link #getChild(String) child} reached from recursively traversing the given {@code keys}.
+ */
+ ConfigProperties getChild(String... keys);
+
+ /**
+ * Shortcut for {@link #getChild(String) getChild(key)}.{@link #getValue()}.
+ *
+ * @param key the {@link #getChild(String) key of the child}
+ * @return the value of this {@link ConfigProperties}-node. May be {@code null}.
+ */
+ String getChildValue(String key);
+
+ /**
+ * Shortcut for {@link #getChild(String...) getChild(keys)}.{@link #getValue()}.
+ *
+ * @param keys the keys to traverse recursively.
+ * @return the {@link #getValue() value} of the {@link #getChild(String...) descendant child} traversed by
+ * {@code keys}. May be {@code null}.
+ */
+ String getChildValue(String... keys);
+
+ /**
+ * @return the value of this {@link ConfigProperties}-node. May be {@code null}.
+ */
+ String getValue();
+
+ /**
+ * @param the requested {@code type}
+ * @param type the {@link Class} reflecting the requested result type.
+ * @return the value of this {@link ConfigProperties}-node converted to the given {@code type}. Will be {@code null}
+ * if undefined.
+ */
+ T getValue(Class type);
+
+ /**
+ * @param the requested {@code type}
+ * @param type the {@link Class} reflecting the requested result type.
+ * @param defaultValue the value returned as default if the actual {@link #getValue() value} is undefined.
+ * @return the value of this {@link ConfigProperties}-node converted to the given {@code type}. Will be
+ * {@code defaultValue} if undefined.
+ */
+ T getValue(Class type, T defaultValue);
+
+ /**
+ * @return the {@link #getValue(Class, Object)} as {@code boolean} with {@code false} as default.
+ */
+ boolean getValueAsBoolean();
+
+ /**
+ * @return {@code true} if this is an empty {@link ConfigProperties}-node that neither has a {@link #getValue() value}
+ * nor {@link #getChildKeys() any} {@link #getChild(String) child}.
+ */
+ boolean isEmpty();
+
+ /**
+ * @return this {@link ConfigProperties} converted to a {@link ConfigProperties#toFlatMap() flat} {@link Map}.
+ */
+ Map toFlatMap();
+
+ /**
+ * @param rootKey the root key used as prefix for the {@link java.util.Map.Entry#getKey() keys} separated with a dot
+ * if not {@link String#isEmpty() empty}. Typically the empty {@link String}.
+ * @return this {@link ConfigProperties} converted to a {@link ConfigProperties#toFlatMap() flat} {@link Map}.
+ */
+ Map toFlatMap(String rootKey);
+
+ /**
+ * @return this {@link ConfigProperties} converted to a {@link ConfigProperties#toHierarchicalMap() hierarchical}
+ * {@link Map}.
+ */
+ Map toHierarchicalMap();
+
+ /**
+ * @param parent the parent {@link ConfigProperties} to extend.
+ * @return a new instance of {@link ConfigProperties} with all {@link #getChild(String) children} and
+ * {@link #getValue() value}(s) from this {@link ConfigProperties}-tree and all {@link #getChild(String)
+ * children} and {@link #getValue() value}(s) inherited from the given {@code parent}
+ * {@link ConfigProperties}-tree if they are undefined in this tree.
+ */
+ MutableConfigProperties inherit(ConfigProperties parent);
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/ConfigValueUtil.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/ConfigValueUtil.java
new file mode 100644
index 00000000..5431846b
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/ConfigValueUtil.java
@@ -0,0 +1,72 @@
+package com.devonfw.module.basic.common.api.config;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Currency;
+
+/**
+ * Utility that helps to deal with configuration values.
+ */
+public final class ConfigValueUtil {
+
+ private ConfigValueUtil() {
+ }
+
+ /**
+ * @param the generic {@code type}.
+ * @param object the {@link Object} to convert. Will not be {@code null}.
+ * @param type the {@link Class} reflecting the requested type.
+ * @return the given {@link Object} converted to the given {@code type}.
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static T convertValue(Object object, Class type) {
+
+ try {
+ Object result;
+ if (type.isInstance(object)) {
+ result = object;
+ } else if (type.isEnum()) {
+ result = Enum.valueOf((Class extends Enum>) type, object.toString());
+ } else if (type.isAssignableFrom(String.class)) {
+ result = object.toString();
+ } else if ((type == boolean.class) || (type == Boolean.class)) {
+ result = Boolean.valueOf(object.toString());
+ } else if ((type == int.class) || (type == Integer.class)) {
+ result = Integer.valueOf(object.toString());
+ } else if ((type == long.class) || (type == Long.class)) {
+ result = Long.valueOf(object.toString());
+ } else if ((type == double.class) || (type == Double.class)) {
+ result = Double.valueOf(object.toString());
+ } else if (type == Class.class) {
+ result = Class.forName(object.toString());
+ } else if ((type == float.class) || (type == Float.class)) {
+ result = Float.valueOf(object.toString());
+ } else if ((type == short.class) || (type == Short.class)) {
+ result = Short.valueOf(object.toString());
+ } else if ((type == byte.class) || (type == Byte.class)) {
+ result = Byte.valueOf(object.toString());
+ } else if (type == BigDecimal.class) {
+ result = new BigDecimal(object.toString());
+ } else if (type == BigInteger.class) {
+ result = new BigInteger(object.toString());
+ } else if (type == Number.class) {
+ result = Double.parseDouble(object.toString());
+ } else if ((type == Character.class) || ((type == char.class))) {
+ String value = object.toString();
+ if (value.length() == 1) {
+ result = Character.valueOf(value.charAt(0));
+ } else {
+ throw new IllegalArgumentException(value);
+ }
+ } else if (type == Currency.class) {
+ result = Currency.getInstance(object.toString());
+ } else {
+ throw new IllegalArgumentException(object.toString());
+ }
+ return (T) result;
+ } catch (NumberFormatException | ClassNotFoundException e) {
+ throw new IllegalArgumentException(object.toString(), e);
+ }
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/EmptyConfigProperties.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/EmptyConfigProperties.java
new file mode 100644
index 00000000..7f3e81fe
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/EmptyConfigProperties.java
@@ -0,0 +1,110 @@
+package com.devonfw.module.basic.common.api.config;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of {@link ConfigProperties} that always is {@link ConfigProperties#isEmpty() empty}.
+ *
+ * @since 3.0.0
+ */
+class EmptyConfigProperties implements ConfigProperties {
+
+ @Override
+ public Set getChildKeys() {
+
+ return Collections.emptySet();
+ }
+
+ @Override
+ public ConfigProperties getChild(String key) {
+
+ return this;
+ }
+
+ @Override
+ public ConfigProperties getChild(String... keys) {
+
+ return this;
+ }
+
+ @Override
+ public String getChildValue(String key) {
+
+ return null;
+ }
+
+ @Override
+ public String getChildValue(String... keys) {
+
+ return null;
+ }
+
+ @Override
+ public String getValue() {
+
+ return null;
+ }
+
+ @Override
+ public T getValue(Class type) {
+
+ return null;
+ }
+
+ @Override
+ public T getValue(Class type, T defaultValue) {
+
+ return defaultValue;
+ }
+
+ @Override
+ public boolean getValueAsBoolean() {
+
+ return false;
+ }
+
+ @Override
+ public boolean isEmpty() {
+
+ return true;
+ }
+
+ @Override
+ public Map toFlatMap() {
+
+ return toFlatMap("");
+ }
+
+ @Override
+ public Map toFlatMap(String rootKey) {
+
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public Map toHierarchicalMap() {
+
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public MutableConfigProperties inherit(ConfigProperties parent) {
+
+ if (parent == null || parent.isEmpty()) {
+ return new MutableConfigPropertiesImpl("");
+ }
+ if (parent instanceof MutableConfigProperties) {
+ return (MutableConfigProperties) parent;
+ } else
+ return parent.inherit(EMPTY);
+ }
+
+ @Override
+ public String toString() {
+
+ return "";
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/MutableConfigProperties.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/MutableConfigProperties.java
new file mode 100644
index 00000000..6d768f68
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/MutableConfigProperties.java
@@ -0,0 +1,20 @@
+package com.devonfw.module.basic.common.api.config;
+
+/**
+ * Extends {@link ConfigProperties} with ability to modify.
+ */
+public interface MutableConfigProperties extends ConfigProperties {
+
+ /**
+ * @param key the key of the {@link #getChild(String) child} where to {@link #setValue(String) set the value}. All
+ * such children will be created if they do not yet exist.
+ * @param value the new value to {@link #setValue(String) set}.
+ */
+ void setChildValue(String key, String value);
+
+ /**
+ * @param value the new value of {@link #getValue()}.
+ */
+ void setValue(String value);
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/MutableConfigPropertiesImpl.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/MutableConfigPropertiesImpl.java
new file mode 100644
index 00000000..afcb6d90
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/MutableConfigPropertiesImpl.java
@@ -0,0 +1,120 @@
+package com.devonfw.module.basic.common.api.config;
+
+/**
+ * The implementation of {@link MutableConfigProperties}.
+ */
+class MutableConfigPropertiesImpl extends SimpleConfigProperties implements MutableConfigProperties {
+
+ private final SimpleConfigProperties parent;
+
+ private final SimpleConfigProperties copy;
+
+ private int copyModifications;
+
+ private int parentModifications;
+
+ /**
+ * The constructor.
+ *
+ * @param key the hierarchical key of this {@link ConfigProperties}-node.
+ */
+ protected MutableConfigPropertiesImpl(String key) {
+ this(key, null, null);
+ }
+
+ /**
+ * The constructor.
+ *
+ * @param key the hierarchical key of this {@link ConfigProperties}-node.
+ * @param copy the {@link ConfigProperties}-node to copy with {@link ConfigProperties#inherit(ConfigProperties)
+ * inheritance} from the given {@code parent}.
+ * @param parent the parent {@link ConfigProperties} to {@link ConfigProperties#inherit(ConfigProperties) inherit
+ * from} or {@code null} for none.
+ */
+ protected MutableConfigPropertiesImpl(String key, ConfigProperties copy, ConfigProperties parent) {
+ super(key);
+ this.copy = asSimple(copy);
+ this.parent = asSimple(parent);
+ this.copyModifications = -1;
+ this.parentModifications = -1;
+ }
+
+ @Override
+ protected void updateChildren() {
+
+ super.updateChildren();
+ if (this.copy != null) {
+ int copyMod = this.copy.getNodeModifications();
+ if (this.copyModifications != copyMod) {
+ for (String key : this.copy.getChildKeys()) {
+ getChild(key);
+ }
+ this.copyModifications = copyMod;
+ }
+ }
+ if (this.parent != null) {
+ int parentMod = this.parent.getNodeModifications();
+ if (this.parentModifications != parentMod) {
+ for (String key : this.parent.getChildKeys()) {
+ getChild(key);
+ }
+ this.parentModifications = parentMod;
+ }
+ }
+ }
+
+ @Override
+ public MutableConfigProperties inherit(ConfigProperties parentNode) {
+
+ if ((parentNode == null) || (parentNode.isEmpty())) {
+ return this;
+ }
+ return super.inherit(parentNode);
+ }
+
+ @Override
+ public String getValue() {
+
+ String result = super.getValue();
+ if (result == null) {
+ if (this.copy != null) {
+ result = this.copy.getValue();
+ }
+ if ((result == null) && (this.parent != null)) {
+ result = this.parent.getValue();
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void setValue(String value) {
+
+ super.setValue(value);
+ }
+
+ @Override
+ public void setChildValue(String key, String value) {
+
+ MutableConfigProperties child = (MutableConfigProperties) getChild(key, true);
+ child.setValue(value);
+ }
+
+ @Override
+ protected SimpleConfigProperties createChild(String key, boolean create) {
+
+ ConfigProperties copyNode = EMPTY;
+ if (this.copy != null) {
+ copyNode = this.copy.getChild(key);
+ }
+ ConfigProperties parentNode = EMPTY;
+ if (this.parent != null) {
+ parentNode = this.parent.getChild(key);
+ }
+ if (!create && copyNode.isEmpty() && parentNode.isEmpty()) {
+ return null;
+ }
+ return new MutableConfigPropertiesImpl(key, copyNode, parentNode);
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/SimpleConfigProperties.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/SimpleConfigProperties.java
new file mode 100644
index 00000000..28353cc1
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/SimpleConfigProperties.java
@@ -0,0 +1,395 @@
+package com.devonfw.module.basic.common.api.config;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+/**
+ * Simple implementation of {@link ConfigProperties}.
+ *
+ * @since 3.0.0
+ */
+public class SimpleConfigProperties implements ConfigProperties {
+
+ private final String nodeKey;
+
+ private final Map childMap;
+
+ private final Set childKeys;
+
+ private String value;
+
+ private int nodeModifications;
+
+ /**
+ * The constructor.
+ *
+ * @param key the hierarchical key of this {@link ConfigProperties}-node.
+ */
+ protected SimpleConfigProperties(String key) {
+ super();
+ this.nodeKey = key;
+ this.childMap = new HashMap<>();
+ this.childKeys = Collections.unmodifiableSet(this.childMap.keySet());
+ this.nodeModifications = 0;
+ }
+
+ @Override
+ public boolean isEmpty() {
+
+ return false;
+ }
+
+ /**
+ * @return the absolute key of this {@link ConfigProperties}-node.
+ */
+ protected String getNodeKey() {
+
+ return this.nodeKey;
+ }
+
+ /**
+ * @return the modification counter that gets incremented whenever a child node is added.
+ */
+ protected int getNodeModifications() {
+
+ return this.nodeModifications;
+ }
+
+ @Override
+ public Set getChildKeys() {
+
+ updateChildren();
+ return this.childKeys;
+ }
+
+ /**
+ * Updates the child-nodes in case of a mutable copy node.
+ */
+ protected void updateChildren() {
+
+ // nothing by default
+ }
+
+ @Override
+ public ConfigProperties getChild(String key) {
+
+ return getChild(key, false);
+ }
+
+ /**
+ * @see #getChild(String)
+ *
+ * @param key the key of the requested child.
+ * @param create - {@code true} to create if not exits, {@code false} otherwise.
+ * @return the requested child.
+ */
+ protected ConfigProperties getChild(String key, boolean create) {
+
+ if ((key == null) || (key.isEmpty())) {
+ return this;
+ }
+ if (key.indexOf(KEY_SEPARATOR) > 0) {
+ String[] segments = key.split("\\.");
+ return getChild(create, segments);
+ }
+ SimpleConfigProperties result = this.childMap.get(key);
+ if (result == null) {
+ result = createChild(key, create);
+ if (result == null) {
+ return EMPTY;
+ } else {
+ this.childMap.put(key, result);
+ this.nodeModifications++;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @param childKey the key segment of the child to create.
+ * @param create - {@code true} to force creation, {@code false} otherwise.
+ * @return the new child or {@code null}.
+ */
+ protected SimpleConfigProperties createChild(String childKey, boolean create) {
+
+ if (!create) {
+ return null;
+ }
+ return new SimpleConfigProperties(composeKey(this.nodeKey, childKey));
+ }
+
+ @Override
+ public ConfigProperties getChild(String... keys) {
+
+ return getChild(false, keys);
+ }
+
+ /**
+ * @param create - {@code true} to create if not exits, {@code false} otherwise.
+ * @param keys the key segments of the requested child.
+ * @return the requested child.
+ */
+ protected ConfigProperties getChild(boolean create, String... keys) {
+
+ if ((keys == null) || (keys.length == 0)) {
+ return this;
+ }
+ SimpleConfigProperties result = this;
+ for (String key : keys) {
+ ConfigProperties child = result.getChild(key, create);
+ if (child.isEmpty()) {
+ return child;
+ }
+ result = (SimpleConfigProperties) child;
+ }
+ return result;
+ }
+
+ @Override
+ public String getValue() {
+
+ return this.value;
+ }
+
+ @Override
+ public T getValue(Class type) {
+
+ return getValue(type, null);
+ }
+
+ @Override
+ public T getValue(Class type, T defaultValue) {
+
+ String result = getValue();
+ if (result == null) {
+ return defaultValue;
+ } else {
+ return ConfigValueUtil.convertValue(result, type);
+ }
+ }
+
+ @Override
+ public boolean getValueAsBoolean() {
+
+ return "true".equalsIgnoreCase(getValue());
+ }
+
+ /**
+ * @param value new value of {@link #getValue()}.
+ */
+ protected void setValue(String value) {
+
+ this.value = value;
+ }
+
+ @Override
+ public String getChildValue(String key) {
+
+ return getChild(key).getValue();
+ }
+
+ @Override
+ public String getChildValue(String... keys) {
+
+ return getChild(keys).getValue();
+ }
+
+ @Override
+ public Map toFlatMap() {
+
+ return toFlatMap("");
+ }
+
+ @Override
+ public Map toFlatMap(String rootKey) {
+
+ Map map = new HashMap<>();
+ toFlatMap(rootKey, map);
+ return map;
+ }
+
+ private void toFlatMap(String key, Map map) {
+
+ updateChildren();
+ String nodeValue = getValue();
+ if (nodeValue != null) {
+ map.put(key, nodeValue);
+ }
+ for (Entry entry : this.childMap.entrySet()) {
+ String childKey = entry.getKey();
+ String subKey = composeKey(key, childKey);
+ entry.getValue().toFlatMap(subKey, map);
+ }
+ }
+
+ @Override
+ public Map toHierarchicalMap() {
+
+ Map map = new HashMap<>();
+ toHierarchicalMap(map);
+ return map;
+ }
+
+ private void toHierarchicalMap(Map map) {
+
+ String nodeValue = getValue();
+ if (nodeValue != null) {
+ map.put("", nodeValue);
+ }
+ updateChildren();
+ for (Entry entry : this.childMap.entrySet()) {
+ String childKey = entry.getKey();
+ Map subMap = new HashMap<>();
+ entry.getValue().toHierarchicalMap(subMap);
+ map.put(childKey, subMap);
+ }
+ }
+
+ /**
+ * @see SimpleConfigProperties#ofFlatMap(String, Map)
+ *
+ * @param map the flat {@link Map} of the configuration values.
+ */
+ protected void fromFlatMap(Map map) {
+
+ for (Entry entry : map.entrySet()) {
+ SimpleConfigProperties child;
+ child = (SimpleConfigProperties) getChild(entry.getKey(), true);
+ child.value = entry.getValue();
+ }
+ }
+
+ /**
+ * @see SimpleConfigProperties#ofHierarchicalMap(String, Map)
+ *
+ * @param map the hierarchical {@link Map} of the configuration values.
+ */
+ @SuppressWarnings("unchecked")
+ protected void fromHierarchicalMap(Map map) {
+
+ for (Entry entry : map.entrySet()) {
+ SimpleConfigProperties child = (SimpleConfigProperties) getChild(entry.getKey(), true);
+ Object childObject = entry.getValue();
+ if (childObject instanceof Map) {
+ child.fromHierarchicalMap((Map) childObject);
+ } else {
+ child.value = childObject.toString();
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+
+ StringBuilder buffer = new StringBuilder(this.nodeKey);
+ buffer.append('=');
+ String nodeValue = getValue();
+ if (nodeValue != null) {
+ buffer.append(nodeValue);
+ }
+ return buffer.toString();
+ }
+
+ @Override
+ public MutableConfigProperties inherit(ConfigProperties parentNode) {
+
+ return new MutableConfigPropertiesImpl(getNodeKey(), this, parentNode);
+ }
+
+ /**
+ * @see #ofFlatMap(String, Map)
+ *
+ * @param map the flat {@link Map} of the configuration values.
+ * @return the root {@link ConfigProperties}-node of the given flat {@link Map} converted to hierarchical
+ * {@link ConfigProperties}.
+ */
+ public static ConfigProperties ofFlatMap(Map map) {
+
+ return ofFlatMap("", map);
+ }
+
+ /**
+ * Converts a flat {@link Map} of configuration values to hierarchical {@link ConfigProperties}. E.g. the flat map
+ * {"foo.bar.some"="some-value", "foo.bar.other"="other-value"} would result in {@link ConfigProperties}
+ * {@code myRoot} such that
+ * myRoot.{@link #getChild(String...) getChild}("foo", "bar").{@link #getChildKeys()} returns the
+ * {@link Collection} {"some", "other"} and
+ * myRoot.{@link #getChildValue(String) getValue}("foo.bar.some") returns "my-value".
+ *
+ * @param key the top-level key of the returned root {@link ConfigProperties}-node. Typically the empty string ("")
+ * for root.
+ * @param map the flat {@link Map} of the configuration values.
+ * @return the root {@link ConfigProperties}-node of the given flat {@link Map} converted to hierarchical
+ * {@link ConfigProperties}.
+ */
+ public static ConfigProperties ofFlatMap(String key, Map map) {
+
+ SimpleConfigProperties root = new SimpleConfigProperties(key);
+ root.fromFlatMap(map);
+ return root;
+ }
+
+ /**
+ * @see #ofHierarchicalMap(String, Map)
+ *
+ * @param map the hierarchical {@link Map} of the configuration values.
+ * @return the root {@link ConfigProperties}-node of the given hierarchical {@link Map} converted to
+ * {@link ConfigProperties}.
+ */
+ public static ConfigProperties ofHierarchicalMap(Map map) {
+
+ return ofHierarchicalMap("", map);
+ }
+
+ /**
+ * Converts a hierarchical {@link Map} of configuration values to {@link ConfigProperties}. E.g. the hierarchical map
+ * {"foo"={"bar"={"some"="my-value", "other"="magic-value"}}} would result in {@link ConfigProperties}
+ * {@code myRoot} such that
+ * myRoot.{@link #getChild(String...) getChild}("foo", "bar").{@link #getChildKeys()} returns the
+ * {@link Collection} {"some", "other"} and
+ * myRoot.{@link #getChildValue(String) getValue}("foo.bar.some") returns "my-value".
+ *
+ * @param key the top-level key of the returned root {@link ConfigProperties}-node. Typically the empty string ("")
+ * for root.
+ * @param map the hierarchical {@link Map} of the configuration values.
+ * @return the root {@link ConfigProperties}-node of the given hierarchical {@link Map} converted to
+ * {@link ConfigProperties}.
+ */
+ public static ConfigProperties ofHierarchicalMap(String key, Map map) {
+
+ SimpleConfigProperties root = new SimpleConfigProperties(key);
+ root.fromHierarchicalMap(map);
+ return root;
+ }
+
+ /**
+ * @param parentKey the parent key.
+ * @param childKey the child key.
+ * @return the composed key.
+ */
+ protected static String composeKey(String parentKey, String childKey) {
+
+ if (parentKey.isEmpty()) {
+ return childKey;
+ } else {
+ return parentKey + KEY_SEPARATOR + childKey;
+ }
+ }
+
+ /**
+ * @param configProperties the {@link ConfigProperties}.
+ * @return the given {@link ConfigProperties} as {@link SimpleConfigProperties} or {@code null} if no such instance.
+ */
+ protected static SimpleConfigProperties asSimple(ConfigProperties configProperties) {
+
+ if (configProperties instanceof SimpleConfigProperties) {
+ return (SimpleConfigProperties) configProperties;
+ }
+ return null;
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/SpringProfileConstants.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/SpringProfileConstants.java
new file mode 100644
index 00000000..5b55bd5c
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/config/SpringProfileConstants.java
@@ -0,0 +1,43 @@
+package com.devonfw.module.basic.common.api.config;
+
+/**
+ * This class provides {@code String} constants which allow to distinguish several bean definition profiles. The
+ * constants should be used in {@code @Profile} annotations to avoid multiple points of failure (e.g., through typos
+ * within annotations).
+ * In test scenarios, these constants should be used in conjunction with the {@code @ActiveProfile} annotation.
+ *
+ * @since 2.2.0
+ */
+public class SpringProfileConstants {
+
+ /**
+ * This constant applies to all tests.
+ */
+ public static final String JUNIT = "junit";
+
+ /**
+ * This constant denotes a live profile.
+ */
+ public static final String NOT_JUNIT = "!" + JUNIT;
+
+ /**
+ * This constant should be used in conjunction with component tests.
+ */
+ public static final String COMPONENT_TEST = "component-test";
+
+ /**
+ * This constant should be used in conjunction with module tests.
+ */
+ public static final String MODULE_TEST = "module-test";
+
+ /**
+ * This constant should be used in conjunction with subsystem tests.
+ */
+ public static final String SUBSYSTEM_TEST = "subsystem-test";
+
+ /**
+ * This constant should be used in conjunction with system tests.
+ */
+ public static final String SYSTEM_TEST = "system-test";
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/LikePatternSyntax.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/LikePatternSyntax.java
new file mode 100644
index 00000000..792c92b3
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/LikePatternSyntax.java
@@ -0,0 +1,140 @@
+package com.devonfw.module.basic.common.api.query;
+
+/**
+ * Enum defining available syntaxes for a match pattern in a LIKE-clause. While databases typically require {@link #SQL}
+ * syntax, human user expect {@link #GLOB} syntax in search forms. Therefore this enum also supports
+ * {@link #convert(String, LikePatternSyntax, boolean) conversion} from one syntax to another.
+ *
+ * @since 3.0.0
+ */
+public enum LikePatternSyntax {
+
+ /**
+ * Glob syntax that is typically expected by end-users and supported by typical search forms. It uses asterisk ('*')
+ * for {@link #getAny() any wildcard} and question-mark ('?') for {@link #getSingle() single wildcard}.
+ */
+ GLOB('*', '?'),
+
+ /**
+ * SQL syntax that is typically required by databases. It uses percent ('%') for {@link #getAny() any wildcard} and
+ * underscore ('_') for {@link #getSingle() single wildcard}.
+ */
+ SQL('%', '_');
+
+ /** The escape character. */
+ public static final char ESCAPE = '\\';
+
+ private final char any;
+
+ private final char single;
+
+ private LikePatternSyntax(char any, char single) {
+
+ this.any = any;
+ this.single = single;
+ }
+
+ /**
+ * @return the wildcard character that matches any string including the {@link String#isEmpty() empty} string.
+ */
+ public char getAny() {
+
+ return this.any;
+ }
+
+ /**
+ * @return the wildcard character that matches exactly one single character.
+ */
+ public char getSingle() {
+
+ return this.single;
+ }
+
+ /**
+ * @param pattern the LIKE pattern in the given {@link LikePatternSyntax}.
+ * @param syntax the {@link LikePatternSyntax} of the given {@code pattern}.
+ * @return the given {@code pattern} converted to this {@link LikePatternSyntax}.
+ */
+ public String convert(String pattern, LikePatternSyntax syntax) {
+
+ return convert(pattern, syntax, false);
+ }
+
+ /**
+ * @param pattern the LIKE pattern in the given {@link LikePatternSyntax}.
+ * @param syntax the {@link LikePatternSyntax} of the given {@code pattern}.
+ * @param matchSubstring - {@code true} if the given {@code pattern} shall also match substrings, {@code false}
+ * otherwise.
+ * @return the given {@code pattern} converted to this {@link LikePatternSyntax}.
+ */
+ public String convert(String pattern, LikePatternSyntax syntax, boolean matchSubstring) {
+
+ if ((pattern == null) || pattern.isEmpty()) {
+ if (matchSubstring) {
+ return Character.toString(this.any);
+ } else {
+ return pattern;
+ }
+ }
+ if (this == syntax) {
+ String result = pattern;
+ if (matchSubstring) {
+ if (pattern.charAt(0) != this.any) {
+ result = this.any + result;
+ }
+ int lastIndex = pattern.length() - 1;
+ if ((pattern.charAt(lastIndex) != this.any) || ((lastIndex > 0) && (pattern.charAt(lastIndex - 1) == ESCAPE))) {
+ result = result + this.any;
+ }
+ }
+ return result;
+ }
+ int length = pattern.length();
+ StringBuilder sb = new StringBuilder(length + 8);
+ boolean lastWildcardAny = false;
+ for (int i = 0; i < length; i++) {
+ lastWildcardAny = false;
+ char c = pattern.charAt(i);
+ if (c == syntax.any) {
+ c = this.any;
+ lastWildcardAny = true;
+ } else if (c == syntax.single) {
+ c = this.single;
+ } else if ((c == this.any) || (c == this.single) || (c == ESCAPE)) {
+ if ((i == 0) && matchSubstring) {
+ sb.append(this.any);
+ }
+ sb.append(ESCAPE);
+ }
+ if (matchSubstring && (i == 0) && !lastWildcardAny) {
+ sb.append(this.any);
+ }
+ sb.append(c);
+ }
+ if (matchSubstring && !lastWildcardAny) {
+ sb.append(this.any);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @param pattern the string value that may be a pattern.
+ * @return the {@link LikePatternSyntax} for the given {@code pattern} or {@code null} if the given {@code pattern}
+ * does not contain any wildcards.
+ */
+ public static LikePatternSyntax autoDetect(String pattern) {
+
+ if ((pattern == null) || pattern.isEmpty()) {
+ return null;
+ }
+ for (LikePatternSyntax syntax : values()) {
+ if (pattern.indexOf(syntax.any) > 0) {
+ return syntax;
+ } else if (pattern.indexOf(syntax.single) > 0) {
+ return syntax;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/StringSearchConfigTo.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/StringSearchConfigTo.java
new file mode 100644
index 00000000..d3f43227
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/StringSearchConfigTo.java
@@ -0,0 +1,113 @@
+package com.devonfw.module.basic.common.api.query;
+
+import com.devonfw.module.basic.common.api.to.AbstractTo;
+
+/**
+ * {@link AbstractTo TO} for the options to search for a string value.
+ *
+ * @since 3.0.0
+ */
+public class StringSearchConfigTo extends AbstractTo {
+
+ private static final long serialVersionUID = 1L;
+
+ private boolean ignoreCase;
+
+ private boolean matchSubstring;
+
+ private LikePatternSyntax likeSyntax;
+
+ private StringSearchOperator operator;
+
+ /**
+ * @return {@code true} to ignore the case, {@code false} otherwise (to search case-sensitive).
+ */
+ public boolean isIgnoreCase() {
+
+ return this.ignoreCase;
+ }
+
+ /**
+ * @param ignoreCase new value of {@link #isIgnoreCase()}.
+ */
+ public void setIgnoreCase(boolean ignoreCase) {
+
+ this.ignoreCase = ignoreCase;
+ }
+
+ /**
+ * @return matchSubstring {@code true} if search string shall also match substrings of the string values to search on.
+ */
+ public boolean isMatchSubstring() {
+
+ return this.matchSubstring;
+ }
+
+ /**
+ * @param matchSubstring new value of {@link #isMatchSubstring()}.
+ */
+ public void setMatchSubstring(boolean matchSubstring) {
+
+ this.matchSubstring = matchSubstring;
+ }
+
+ /**
+ * @return the {@link LikePatternSyntax} of the search string used to do a LIKE-search, {@code null} for no
+ * LIKE-search. Shall be {@code null} if {@link #getOperator() operator} is neither {@code null} nor
+ * {@link StringSearchOperator#LIKE}.
+ */
+ public LikePatternSyntax getLikeSyntax() {
+
+ return this.likeSyntax;
+ }
+
+ /**
+ * @param likeSyntax new value of {@link #getLikeSyntax()}.
+ */
+ public void setLikeSyntax(LikePatternSyntax likeSyntax) {
+
+ this.likeSyntax = likeSyntax;
+ }
+
+ /**
+ * @return operator the {@link StringSearchOperator} used to search. If {@code null} a "magic auto mode" is used where
+ * {@link StringSearchOperator#LIKE} is used in case the search string contains wildcards and
+ * {@link StringSearchOperator#EQ} is used otherwise.
+ */
+ public StringSearchOperator getOperator() {
+
+ return this.operator;
+ }
+
+ /**
+ * @param operator new value of {@link #getOperator()}.
+ */
+ public void setOperator(StringSearchOperator operator) {
+
+ this.operator = operator;
+ }
+
+ /**
+ * @param operator the {@link StringSearchOperator}.
+ * @return a new {@link StringSearchConfigTo} with the given config.
+ */
+ public static StringSearchConfigTo of(StringSearchOperator operator) {
+
+ StringSearchConfigTo result = new StringSearchConfigTo();
+ result.setOperator(operator);
+ return result;
+ }
+
+ /**
+ * @param syntax the {@link LikePatternSyntax}.
+ * @return a new {@link StringSearchConfigTo} with the given config.
+ */
+ public static StringSearchConfigTo of(LikePatternSyntax syntax) {
+
+ StringSearchConfigTo result = new StringSearchConfigTo();
+ result.setOperator(StringSearchOperator.LIKE);
+ result.setLikeSyntax(syntax);
+ return result;
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/StringSearchOperator.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/StringSearchOperator.java
new file mode 100644
index 00000000..59e237fb
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/query/StringSearchOperator.java
@@ -0,0 +1,63 @@
+package com.devonfw.module.basic.common.api.query;
+
+/**
+ * Enum defining available operators for a string search or string comparison.
+ *
+ * @since 3.0.0
+ */
+public enum StringSearchOperator {
+
+ /** Matches if strings are {@link String#equals(Object) equal}. */
+ EQ("=="),
+
+ /** Matches if strings are NOT {@link String#equals(Object) equal}. */
+ NE("!="),
+
+ /** Matches if search value is less than search hit(s) in {@link String#compareTo(String) lexicographical order}. */
+ LT("<"),
+
+ /**
+ * Matches if search value is less or equal to search hit(s) in {@link String#compareTo(String) lexicographical
+ * order}.
+ */
+ LE("<="),
+
+ /**
+ * Matches if search value is greater than search hit(s) in {@link String#compareTo(String) lexicographical order}.
+ */
+ GT(">"),
+
+ /**
+ * Matches if search value is greater or equal to search hit(s) in {@link String#compareTo(String) lexicographical
+ * order}.
+ */
+ GE(">="),
+
+ /**
+ * Matches if search value as pattern matches search hit(s) in LIKE search.
+ *
+ * @see LikePatternSyntax
+ */
+ LIKE("LIKE"),
+
+ /**
+ * Matches if search value as pattern does not match search hit(s) in LIKE search.
+ *
+ * @see LikePatternSyntax
+ */
+ NOT_LIKE("NOT LIKE");
+
+ private final String operator;
+
+ private StringSearchOperator(String operator) {
+
+ this.operator = operator;
+ }
+
+ @Override
+ public String toString() {
+
+ return this.operator;
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/GenericIdRef.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/GenericIdRef.java
new file mode 100644
index 00000000..d88e2bbd
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/GenericIdRef.java
@@ -0,0 +1,60 @@
+package com.devonfw.module.basic.common.api.reference;
+
+import java.util.Objects;
+
+/**
+ * Generic implementation of {@link Ref}.
+ *
+ * @param generic type of {@link #getId() ID}.
+ * @param generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}.
+ */
+public class GenericIdRef implements Ref {
+
+ private static final long serialVersionUID = 1L;
+
+ private final ID id;
+
+ /**
+ * The constructor.
+ *
+ * @param id the {@link #getId() ID}.
+ */
+ public GenericIdRef(ID id) {
+
+ super();
+ Objects.requireNonNull(id, "id");
+ this.id = id;
+ }
+
+ @Override
+ public ID getId() {
+
+ return this.id;
+ }
+
+ @Override
+ public int hashCode() {
+
+ return this.id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (this == obj) {
+ return true;
+ }
+ if ((obj == null) || (getClass() != obj.getClass())) {
+ return false;
+ }
+ GenericIdRef, ?> other = (GenericIdRef, ?>) obj;
+ return Objects.equals(this.id, other.id);
+ }
+
+ @Override
+ public String toString() {
+
+ return this.id.toString();
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/IdRef.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/IdRef.java
new file mode 100644
index 00000000..40bc1e65
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/IdRef.java
@@ -0,0 +1,62 @@
+package com.devonfw.module.basic.common.api.reference;
+
+import net.sf.mmm.util.entity.api.GenericEntity;
+
+/**
+ * A {@link Ref} using {@link Long} values as {@link #getId() ID}.
+ *
+ * @param generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}.
+ */
+public class IdRef extends GenericIdRef {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The constructor.
+ *
+ * @param id the {@link #getId() ID}.
+ */
+ public IdRef(Long id) {
+
+ super(id);
+ }
+
+ /**
+ * @param generic type of the referenced {@link GenericEntity}.
+ * @param entity the {@link GenericEntity} to reference.
+ * @return the {@link IdRef} pointing to the given {@link GenericEntity} or {@code null} if the {@link GenericEntity}
+ * or its {@link GenericEntity#getId() ID} is {@code null}.
+ */
+ public static > IdRef of(E entity) {
+
+ if (entity == null) {
+ return null;
+ }
+ return of(entity.getId());
+ }
+
+ /**
+ * @param generic type of the referenced {@link GenericEntity}.
+ * @param id the {@link #getId() ID} to wrap.
+ * @return the {@link IdRef} pointing to an entity with the specified {@link #getId() ID} or {@code null} if the given
+ * {@code ID} was {@code null}.
+ */
+ public static IdRef of(Long id) {
+
+ if (id == null) {
+ return null;
+ }
+ return new IdRef<>(id);
+ }
+
+ /**
+ * @param generic type of the referenced {@link GenericEntity}.
+ * @param id the {@link #getId() ID} to wrap.
+ * @return the {@link IdRef} pointing to an entity with the specified {@link #getId() ID}.
+ */
+ public static IdRef of(long id) {
+
+ return new IdRef<>(Long.valueOf(id));
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/Ref.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/Ref.java
new file mode 100644
index 00000000..5d7d2a0a
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reference/Ref.java
@@ -0,0 +1,22 @@
+package com.devonfw.module.basic.common.api.reference;
+
+import net.sf.mmm.util.lang.api.Datatype;
+
+/**
+ * Interface for a reference to an {@link net.sf.mmm.util.entity.api.GenericEntity entity} via its {@link #getId() ID}.
+ * In most cases you want to use {@link IdRef}.
+ *
+ * @param generic type of {@link #getId() ID}.
+ * @param generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}. For flexibility not technically
+ * bound to {@link net.sf.mmm.util.entity.api.Entity} so it can also be used for an external entity not
+ * satisfying any requirements.
+ */
+public interface Ref extends Datatype {
+
+ /**
+ * @return the ({@link net.sf.mmm.util.entity.api.GenericEntity#getId() ID} of the referenced
+ * {@link net.sf.mmm.util.entity.api.Entity}.
+ */
+ ID getId();
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reflect/Devon4jPackage.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reflect/Devon4jPackage.java
new file mode 100644
index 00000000..0b90fc2c
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/reflect/Devon4jPackage.java
@@ -0,0 +1,452 @@
+package com.devonfw.module.basic.common.api.reflect;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class represents a {@link Package} following the
+ * OASP coding convetion.
+ * After parsing a {@link Package} as {@link Devon4jPackage} you can get individual parts/segments such as
+ * {@link #getComponent() compoent}, {@link #getLayer() layer}, {@link #getScope() scope}, etc.
+ * This is useful for advanced features and tools such as service clients and exception facades, code-generators,
+ * static-code-analyzers (SonarQube plugin), etc.
+ *
+ * @see #of(String)
+ * @see #of(Package)
+ * @see #of(Class)
+ *
+ * @author hohwille
+ */
+public final class Devon4jPackage {
+
+ /**
+ * The common "layer" for cross-cutting
+ * code.
+ */
+ public static final String LAYER_COMMON = "common";
+
+ /** The data-access layer. */
+ public static final String LAYER_DATA_ACCESS = "dataaccess";
+
+ /** The logic layer. */
+ public static final String LAYER_LOGIC = "logic";
+
+ /** The service layer. */
+ public static final String LAYER_SERVICE = "service";
+
+ /** The batch layer. */
+ public static final String LAYER_BATCH = "batch";
+
+ /**
+ * The client layer. Please note that OASP does
+ * not recommend to implement the client layer in Java.
+ */
+ public static final String LAYER_CLIENT = "client";
+
+ /** The scope for APIs. */
+ public static final String SCOPE_API = "api";
+
+ /**
+ * The scope for reusable base
+ * implementations.
+ */
+ public static final String SCOPE_BASE = "base";
+
+ /** The scope for implementations. */
+ public static final String SCOPE_IMPL = "impl";
+
+ private static final Set LAYERS = new HashSet<>(
+ Arrays.asList(LAYER_BATCH, LAYER_CLIENT, LAYER_COMMON, LAYER_DATA_ACCESS, LAYER_LOGIC, LAYER_SERVICE));
+
+ private static final Set SCOPES = new HashSet<>(Arrays.asList(SCOPE_API, SCOPE_BASE, SCOPE_IMPL));
+
+ private static final String REGEX_PKG_SEPARATOR = "\\.";
+
+ private final String[] segments;
+
+ private final int scopeIndex;
+
+ private Boolean valid;
+
+ private transient String root;
+
+ private transient String detail;
+
+ private transient String pkg;
+
+ /**
+ * Der Konstruktor.
+ *
+ * @param segments - see {@link #getSegment(int)}.
+ * @param scope - see {@link #getScope()}.
+ */
+ private Devon4jPackage(String pkg, String[] segments, String root, String detail, int scope) {
+ super();
+ Objects.requireNonNull(segments, "segments");
+ this.pkg = pkg;
+ for (int i = 0; i < segments.length; i++) {
+ if (!isValidSegment(segments[i])) {
+ throw new IllegalArgumentException("segments[" + i + "] = " + segments[i]);
+ }
+ }
+ this.root = root;
+ this.detail = detail;
+ this.segments = segments;
+ this.scopeIndex = scope;
+ }
+
+ private static boolean isValidSegment(String segment) {
+
+ if (segment == null) {
+ return false;
+ }
+ if (segment.isEmpty()) {
+ return false;
+ }
+ if (segment.indexOf('.') >= 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return the number of {@link #getSegment(int) package segments}.
+ */
+ public int getSegmentCount() {
+
+ return this.segments.length;
+ }
+
+ /**
+ * @param index the position of the requested segment. A valid index is in the range from {@code 0} to
+ * {@link #getSegmentCount()}-1.
+ * @return the {@link Package} segment at the given index or {@code null} if the given index is invalid.
+ */
+ public String getSegment(int index) {
+
+ if ((index >= 0) && (index < this.segments.length)) {
+ return this.segments[index];
+ }
+ return null;
+ }
+
+ /**
+ * @return {@code true} if this {@link Devon4jPackage} is a valid according to OASP
+ * package conventions",
+ * {@code false} otherwise.
+ */
+ public boolean isValid() {
+
+ if (this.valid == null) {
+ this.valid = Boolean.valueOf(isValidInternal());
+ }
+ return this.valid;
+ }
+
+ private boolean isValidInternal() {
+
+ if (this.segments.length < 4) {
+ return false;
+ }
+ if (!isValidLayer()) {
+ return false;
+ }
+ if (!isValidScope()) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @return {@code true} if the {@link #getScope() scope} is valid (one of the predefined scopes {@link #isScopeApi()
+ * api}, {@link #isScopeBase() base}, or {@link #isScopeImpl() impl}).
+ */
+ public boolean isValidScope() {
+
+ return SCOPES.contains(getScope());
+ }
+
+ /**
+ * @return {@code true} if the {@link #getLayer() layer} is valid (one of the predefined scopes {@link #isLayerBatch()
+ * batch}, {@link #isLayerClient() client}, {@link #isLayerCommon() common}, {@link #isLayerDataAccess()
+ * dataaccess}, {@link #isLayerLogic() logic}, or {@link #isLayerService() service}).
+ */
+ public boolean isValidLayer() {
+
+ return LAYERS.contains(getLayer());
+ }
+
+ /**
+ * @return the root-{@link Package} of the organization or IT project owning the code.
+ */
+ public String getRoot() {
+
+ if (this.root == null) {
+ if (this.scopeIndex == -1) {
+ return this.pkg;
+ }
+ int appIndex = this.scopeIndex - 3;
+ if (appIndex <= 0) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < appIndex; i++) {
+ if (i > 0) {
+ sb.append('.');
+ }
+ sb.append(this.segments[i]);
+ }
+ this.root = sb.toString();
+ }
+ return this.root;
+ }
+
+ /**
+ * @return the technical name of the application or (micro-)service.
+ */
+ public String getApplication() {
+
+ return getSegment(this.scopeIndex - 3);
+ }
+
+ /**
+ * @return the business component the code belongs to.
+ */
+ public String getComponent() {
+
+ return getSegment(this.scopeIndex - 2);
+ }
+
+ /**
+ * @return the layer the code is assigned to.
+ */
+ public String getLayer() {
+
+ return getSegment(this.scopeIndex - 1);
+ }
+
+ /**
+ * @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_COMMON}.
+ */
+ public boolean isLayerCommon() {
+
+ return LAYER_COMMON.equals(getLayer());
+ }
+
+ /**
+ * @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_DATA_ACCESS}.
+ */
+ public boolean isLayerDataAccess() {
+
+ return LAYER_DATA_ACCESS.equals(getLayer());
+ }
+
+ /**
+ * @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_LOGIC}.
+ */
+ public boolean isLayerLogic() {
+
+ return LAYER_LOGIC.equals(getLayer());
+ }
+
+ /**
+ * @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_SERVICE}.
+ */
+ public boolean isLayerService() {
+
+ return LAYER_SERVICE.equals(getLayer());
+ }
+
+ /**
+ * @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_BATCH}.
+ */
+ public boolean isLayerBatch() {
+
+ return LAYER_BATCH.equals(getLayer());
+ }
+
+ /**
+ * @return {@code true} if {@link #getLayer() layer} is {@link #LAYER_CLIENT}.
+ */
+ public boolean isLayerClient() {
+
+ return LAYER_CLIENT.equals(getLayer());
+ }
+
+ /**
+ * @return scope the scope the code is assigned to.
+ */
+ public String getScope() {
+
+ return getSegment(this.scopeIndex);
+ }
+
+ /**
+ * @return {@code true} if {@link #getScope() scope} is {@link #SCOPE_API}.
+ */
+ public boolean isScopeApi() {
+
+ return SCOPE_API.equals(getScope());
+ }
+
+ /**
+ * @return {@code true} if {@link #getScope() scope} is {@link #SCOPE_BASE}.
+ */
+ public boolean isScopeBase() {
+
+ return SCOPE_BASE.equals(getScope());
+ }
+
+ /**
+ * @return {@code true} if {@link #getScope() scope} is {@link #SCOPE_IMPL}.
+ */
+ public boolean isScopeImpl() {
+
+ return SCOPE_IMPL.equals(getScope());
+ }
+
+ /**
+ * @return the optional detail. Can be a single segment or multiple segments separated with dot. May be {@code null}.
+ */
+ public String getDetail() {
+
+ if (this.detail == null) {
+ if (this.scopeIndex < 3) {
+ return null;
+ }
+ this.detail = joinPackage(this.scopeIndex + 1);
+ }
+ return this.detail;
+ }
+
+ @Override
+ public int hashCode() {
+
+ return Objects.hash(this.segments, this.scopeIndex);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ if (obj == this) {
+ return true;
+ }
+ if ((obj == null) || (obj.getClass() != Devon4jPackage.class)) {
+ return false;
+ }
+ Devon4jPackage other = (Devon4jPackage) obj;
+ if (!Arrays.deepEquals(this.segments, other.segments)) {
+ return false;
+ }
+ if (this.scopeIndex != other.scopeIndex) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+
+ if (this.pkg == null) {
+ this.pkg = joinPackage(0);
+ }
+ return this.pkg;
+ }
+
+ private String joinPackage(int start) {
+
+ return joinPackage(start, this.segments.length);
+ }
+
+ private String joinPackage(int start, int end) {
+
+ if (start >= end) {
+ return null;
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int i = start; i < end; i++) {
+ if (i > start) {
+ sb.append('.');
+ }
+ sb.append(this.segments[i]);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * @param root - see {@link #getRoot()}.
+ * @param application - see {@link #getApplication()}.
+ * @param component - see {@link #getComponent()}.
+ * @param layer - see {@link #getLayer()}.
+ * @param scope - see {@link #getScope()}.
+ * @param detail - see {@link #getDetail()}.
+ * @return the {@link Devon4jPackage} for the given parameters.
+ */
+ public static Devon4jPackage of(String root, String application, String component, String layer, String scope,
+ String detail) {
+
+ String[] roots;
+ if (root == null) {
+ roots = new String[0];
+ } else {
+ roots = root.split(REGEX_PKG_SEPARATOR);
+ }
+ String[] details;
+ if (detail == null) {
+ details = new String[0];
+ } else {
+ details = detail.split(REGEX_PKG_SEPARATOR);
+ }
+ String[] segments = new String[roots.length + details.length + 4];
+ System.arraycopy(roots, 0, segments, 0, roots.length);
+ int i = roots.length;
+ segments[i++] = application;
+ segments[i++] = component;
+ segments[i++] = layer;
+ segments[i++] = scope;
+ System.arraycopy(details, 0, segments, i, details.length);
+ return new Devon4jPackage(null, segments, root, detail, (i - 1));
+ }
+
+ /**
+ * @param packageName the {@link Package#getName() package name} to parse.
+ * @return the parsed {@link Devon4jPackage} corresponding to the given package.
+ */
+ public static Devon4jPackage of(String packageName) {
+
+ String[] segments = packageName.split(REGEX_PKG_SEPARATOR);
+ int scopeIndex = -1;
+ for (int i = 2; i < segments.length; i++) {
+ if (SCOPES.contains(segments[i])) {
+ scopeIndex = i;
+ break;
+ }
+ if (LAYERS.contains(segments[i])) {
+ scopeIndex = i + 1;
+ }
+ }
+ return new Devon4jPackage(packageName, segments, null, null, scopeIndex);
+ }
+
+ /**
+ * @param javaPackage the {@link Package} to parse.
+ * @return the parsed {@link Devon4jPackage} corresponding to the given package.
+ */
+ public static Devon4jPackage of(Package javaPackage) {
+
+ return of(javaPackage.getName());
+ }
+
+ /**
+ * @param type the {@link Class} {@link Class#getPackage() located} in the {@link Package} to parse.
+ * @return the parsed {@link Devon4jPackage} corresponding to the {@link Package} {@link Class#getPackage() of} the given
+ * {@link Class}.
+ */
+ public static Devon4jPackage of(Class> type) {
+
+ return of(type.getPackage());
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractCto.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractCto.java
new file mode 100644
index 00000000..ec9a20c4
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractCto.java
@@ -0,0 +1,26 @@
+package com.devonfw.module.basic.common.api.to;
+
+import net.sf.mmm.util.transferobject.api.AbstractTransferObject;
+import net.sf.mmm.util.transferobject.api.TransferObject;
+
+/**
+ * This is the abstract base class for a composite {@link AbstractTo transfer-object}. Such object should contain
+ * (aggregate) other {@link AbstractTransferObject}s but no atomic data. This means it has properties that contain a
+ * {@link TransferObject} or a {@link java.util.Collection} of those but no {@link net.sf.mmm.util.lang.api.Datatype
+ * values}.
+ * Classes extending this class should carry the suffix Cto.
+ *
+ */
+public abstract class AbstractCto extends AbstractTo {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The constructor.
+ */
+ public AbstractCto() {
+
+ super();
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractEto.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractEto.java
new file mode 100644
index 00000000..b60d5f63
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractEto.java
@@ -0,0 +1,146 @@
+package com.devonfw.module.basic.common.api.to;
+
+import net.sf.mmm.util.entity.api.GenericEntity;
+import net.sf.mmm.util.entity.api.MutableRevisionedEntity;
+import net.sf.mmm.util.entity.api.PersistenceEntity;
+import net.sf.mmm.util.transferobject.api.TransferObject;
+
+/**
+ * This is the abstract base class for an {@link TransferObject} that only contains data without relations. This is
+ * called DTO (data transfer object). Here data means properties that typically represent a
+ * {@link net.sf.mmm.util.lang.api.Datatype} and potentially for relations the ID (as {@link Long}). For actual
+ * relations you will use {@link AbstractCto CTO}s to express what set of entities to transfer, load, save, update, etc.
+ * without redundancies. It typically corresponds to an {@link net.sf.mmm.util.entity.api.GenericEntity entity}. For
+ * additional details and an example consult the.
+ *
+ */
+public abstract class AbstractEto extends AbstractTo implements MutableRevisionedEntity {
+
+ private static final long serialVersionUID = 1L;
+
+ /** @see #getId() */
+ private Long id;
+
+ /** @see #getModificationCounter() */
+ private int modificationCounter;
+
+ /** @see #getRevision() */
+ private Number revision;
+
+ /**
+ * @see #getModificationCounter()
+ */
+ private transient GenericEntity persistentEntity;
+
+ /**
+ * The constructor.
+ */
+ public AbstractEto() {
+
+ super();
+ this.revision = LATEST_REVISION;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Long getId() {
+
+ return this.id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setId(Long id) {
+
+ this.id = id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getModificationCounter() {
+
+ if (this.persistentEntity != null) {
+ // JPA implementations will update modification counter only after the transaction has been committed.
+ // Conversion will typically happen before and would result in the wrong (old) modification counter.
+ // Therefore we update the modification counter here (that has to be called before serialization takes
+ // place).
+ this.modificationCounter = this.persistentEntity.getModificationCounter();
+ }
+ return this.modificationCounter;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setModificationCounter(int version) {
+
+ this.modificationCounter = version;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Number getRevision() {
+
+ return this.revision;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void setRevision(Number revision) {
+
+ this.revision = revision;
+ }
+
+ /**
+ * Method to extend {@link #toString()} logic.
+ *
+ * @param buffer is the {@link StringBuilder} where to {@link StringBuilder#append(Object) append} the string
+ * representation.
+ */
+ @Override
+ protected void toString(StringBuilder buffer) {
+
+ super.toString(buffer);
+ if (this.id != null) {
+ buffer.append("[id=");
+ buffer.append(this.id);
+ buffer.append("]");
+ }
+ if (this.revision != null) {
+ buffer.append("[rev=");
+ buffer.append(this.revision);
+ buffer.append("]");
+ }
+ }
+
+ /**
+ * Inner class to grant access to internal {@link PersistenceEntity} reference of an {@link AbstractEto}. Shall only
+ * be used internally and never be external users.
+ */
+ public static class PersistentEntityAccess {
+
+ /**
+ * Sets the internal {@link PersistenceEntity} reference of the given {@link AbstractEto}.
+ *
+ * @param is the generic type of the {@link GenericEntity#getId() ID}.
+ * @param eto is the {@link AbstractEto}.
+ * @param persistentEntity is the {@link PersistenceEntity}.
+ */
+ protected void setPersistentEntity(AbstractEto eto, PersistenceEntity persistentEntity) {
+
+ assert ((eto.persistentEntity == null) || (persistentEntity == null));
+ eto.persistentEntity = persistentEntity;
+ }
+ }
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractTo.java b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractTo.java
new file mode 100644
index 00000000..272780d4
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/common/api/to/AbstractTo.java
@@ -0,0 +1,46 @@
+package com.devonfw.module.basic.common.api.to;
+
+import net.sf.mmm.util.transferobject.api.TransferObject;
+
+/**
+ * Abstract class for a plain {@link net.sf.mmm.util.transferobject.api.TransferObject} that is neither a
+ * {@link AbstractEto ETO} nor a {@link AbstractCto CTO}. Classes extending this class should carry the suffix
+ * Cto.
+ *
+ */
+public abstract class AbstractTo implements TransferObject {
+
+ /** UID for serialization. */
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * The constructor.
+ */
+ public AbstractTo() {
+
+ super();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public final String toString() {
+
+ StringBuilder buffer = new StringBuilder();
+ toString(buffer);
+ return buffer.toString();
+ }
+
+ /**
+ * Method to extend {@link #toString()} logic. Override to add additional information.
+ *
+ * @param buffer is the {@link StringBuilder} where to {@link StringBuilder#append(Object) append} the string
+ * representation.
+ */
+ protected void toString(StringBuilder buffer) {
+
+ buffer.append(getClass().getSimpleName());
+ }
+
+}
diff --git a/modules/basic/src/main/java/com/devonfw/module/basic/configuration/SpringProfileConstants.java b/modules/basic/src/main/java/com/devonfw/module/basic/configuration/SpringProfileConstants.java
new file mode 100644
index 00000000..e096eadc
--- /dev/null
+++ b/modules/basic/src/main/java/com/devonfw/module/basic/configuration/SpringProfileConstants.java
@@ -0,0 +1,45 @@
+package com.devonfw.module.basic.configuration;
+
+/**
+ * This class provides {@code String} constants which allow to distinguish several bean definition profiles. The
+ * constants should be used in {@code @Profile} annotations to avoid multiple points of failure (e.g., through typos
+ * within annotations).
+ * In test scenarios, these constants should be used in conjunction with the {@code @ActiveProfile} annotation.
+ *
+ * @since 2.1.0
+ * @deprecated please use {@link com.devonfw.module.basic.common.api.config.SpringProfileConstants} instead.
+ */
+@Deprecated
+public class SpringProfileConstants {
+
+ /**
+ * This constant applies to all tests.
+ */
+ public static final String JUNIT = "junit";
+
+ /**
+ * This constant denotes a live profile.
+ */
+ public static final String NOT_JUNIT = "!" + JUNIT;
+
+ /**
+ * This constant should be used in conjunction with component tests.
+ */
+ public static final String COMPONENT_TEST = "component-test";
+
+ /**
+ * This constant should be used in conjunction with module tests.
+ */
+ public static final String MODULE_TEST = "module-test";
+
+ /**
+ * This constant should be used in conjunction with subsystem tests.
+ */
+ public static final String SUBSYSTEM_TEST = "subsystem-test";
+
+ /**
+ * This constant should be used in conjunction with system tests.
+ */
+ public static final String SYSTEM_TEST = "system-test";
+
+}
diff --git a/modules/basic/src/test/java/com/devonfw/module/basic/common/api/config/SimplConfigPropertiesTest.java b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/config/SimplConfigPropertiesTest.java
new file mode 100644
index 00000000..983e5456
--- /dev/null
+++ b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/config/SimplConfigPropertiesTest.java
@@ -0,0 +1,136 @@
+package com.devonfw.module.basic.common.api.config;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.assertj.core.data.MapEntry;
+import org.junit.Test;
+
+import com.devonfw.module.basic.common.api.config.ConfigProperties;
+import com.devonfw.module.basic.common.api.config.EmptyConfigProperties;
+import com.devonfw.module.basic.common.api.config.SimpleConfigProperties;
+import com.devonfw.module.test.common.base.ModuleTest;
+
+/**
+ * Test of {@link SimpleConfigProperties}.
+ */
+public class SimplConfigPropertiesTest extends ModuleTest {
+
+ /**
+ * Test of {@link SimpleConfigProperties#ofFlatMap(String, Map)}.
+ */
+ @Test
+ public void testOfFlatMap() {
+
+ // given
+ Map map = new HashMap<>();
+ map.put("foo.bar.some", "some-value");
+ map.put("foo.bar.other", "other-value");
+ map.put("foo", "foo-value");
+ map.put("bar", "bar-value");
+ map.put("", "value");
+
+ // when
+ ConfigProperties rootConfig = SimpleConfigProperties.ofFlatMap("root", map);
+
+ // then
+ assertThat(rootConfig).isNotNull();
+ assertThat(rootConfig.isEmpty()).isFalse();
+ assertThat(rootConfig.getValue()).isEqualTo("value");
+ assertThat(rootConfig.toString()).isEqualTo("root=value");
+ assertThat(rootConfig.getChildKeys()).containsOnly("foo", "bar");
+ assertThat(rootConfig.getChild("foo", "bar").getChildKeys()).containsOnly("some", "other");
+ assertThat(rootConfig.getChild("undefined")).isNotNull().isInstanceOf(EmptyConfigProperties.class);
+ for (Entry entry : map.entrySet()) {
+ String key = entry.getKey();
+ assertThat(rootConfig.getChildValue(key)).as(key).isEqualTo(entry.getValue());
+ }
+ assertThat(rootConfig.toFlatMap()).isEqualTo(map);
+ }
+
+ /**
+ * Test of {@link SimpleConfigProperties#ofHierarchicalMap(String, Map)}.
+ */
+ @Test
+ public void testOfHierarchicalMap() {
+
+ // given
+ Map fooMap = createMap("foo-value");
+ Map fooBarMap = new HashMap<>();
+ fooMap.put("bar", fooBarMap);
+ fooBarMap.put("some", createMap("some-value"));
+ fooBarMap.put("other", createMap("other-value"));
+ Map barMap = createMap("bar-value");
+
+ Map map = createMap("value");
+ map.put("foo", fooMap);
+ map.put("bar", barMap);
+
+ // when
+ ConfigProperties rootConfig = SimpleConfigProperties.ofHierarchicalMap("root", map);
+
+ // then
+ assertThat(rootConfig).isNotNull();
+ assertThat(rootConfig.isEmpty()).isFalse();
+ assertThat(rootConfig.getValue()).isEqualTo("value");
+ assertThat(rootConfig.toString()).isEqualTo("root=value");
+ assertThat(rootConfig.getChildKeys()).containsOnly("foo", "bar");
+ assertThat(rootConfig.getChild("foo", "bar").getChildKeys()).containsOnly("some", "other");
+ assertThat(rootConfig.getChild("undefined")).isNotNull().isInstanceOf(EmptyConfigProperties.class);
+ assertThat(rootConfig.toHierarchicalMap()).isEqualTo(map);
+ for (Entry entry : rootConfig.toFlatMap().entrySet()) {
+ String key = entry.getKey();
+ assertThat(rootConfig.getChildValue(key)).as(key).isEqualTo(entry.getValue());
+ }
+ }
+
+ /**
+ * Test of {@link SimpleConfigProperties#inherit(ConfigProperties)}.
+ */
+ @Test
+ public void testInherit() {
+
+ // given
+ Map map = new HashMap<>();
+ map.put("app.foo.url", "http://foo.domain.com");
+ map.put("app.bar.url", "http://bar.domain.com");
+ map.put("app.bar.user.login", "bar-user");
+ map.put("default.url", "http://api.domain.com");
+ map.put("default.user.login", "api");
+
+ // when
+ ConfigProperties rootConfig = SimpleConfigProperties.ofFlatMap(map);
+ ConfigProperties defaultConfig = rootConfig.getChild("default");
+ ConfigProperties fooAppConfig = rootConfig.getChild("app", "foo");
+ ConfigProperties fooConfig = fooAppConfig.inherit(defaultConfig);
+ ConfigProperties barAppConfig = rootConfig.getChild("app", "bar");
+ ConfigProperties barConfig = barAppConfig.inherit(defaultConfig);
+
+ // then
+ assertThat(fooConfig).isNotNull();
+ assertThat(fooConfig.toFlatMap()).containsOnly(MapEntry.entry("url", "http://foo.domain.com"),
+ MapEntry.entry("user.login", "api"));
+ fooConfig = fooAppConfig.inherit(defaultConfig); // test lazy init
+ assertThat(fooConfig.getChildKeys()).containsOnly("url", "user");
+ fooConfig = fooAppConfig.inherit(defaultConfig); // test lazy init
+ assertThat(fooConfig.getChildValue("url")).isEqualTo("http://foo.domain.com");
+ assertThat(fooConfig.getChildValue("user", "login")).isEqualTo("api");
+ assertThat(barConfig).isNotNull();
+ assertThat(barConfig.toFlatMap()).containsOnly(MapEntry.entry("url", "http://bar.domain.com"),
+ MapEntry.entry("user.login", "bar-user"));
+ barConfig = barAppConfig.inherit(defaultConfig); // test lazy init
+ assertThat(barConfig.getChildKeys()).containsOnly("url", "user");
+ barConfig = barAppConfig.inherit(defaultConfig); // test lazy init
+ assertThat(barConfig.getChildValue("url")).isEqualTo("http://bar.domain.com");
+ assertThat(barConfig.getChildValue("user", "login")).isEqualTo("bar-user");
+ }
+
+ private static Map createMap(String value) {
+
+ Map map = new HashMap<>();
+ map.put("", value);
+ return map;
+ }
+
+}
diff --git a/modules/basic/src/test/java/com/devonfw/module/basic/common/api/query/LikePatternSyntaxTest.java b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/query/LikePatternSyntaxTest.java
new file mode 100644
index 00000000..3805cb1a
--- /dev/null
+++ b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/query/LikePatternSyntaxTest.java
@@ -0,0 +1,83 @@
+package com.devonfw.module.basic.common.api.query;
+
+import org.junit.Test;
+
+import com.devonfw.module.basic.common.api.query.LikePatternSyntax;
+import com.devonfw.module.test.common.base.ModuleTest;
+
+/**
+ * Test of {@link LikePatternSyntax}.
+ */
+public class LikePatternSyntaxTest extends ModuleTest {
+
+ /** Basic test of {@link LikePatternSyntax#GLOB}. */
+ @Test
+ public void testGlob() {
+
+ LikePatternSyntax syntax = LikePatternSyntax.GLOB;
+ assertThat(syntax.getAny()).isEqualTo('*');
+ assertThat(syntax.getSingle()).isEqualTo('?');
+ }
+
+ /** Basic test of {@link LikePatternSyntax#SQL}. */
+ @Test
+ public void testSql() {
+
+ LikePatternSyntax syntax = LikePatternSyntax.SQL;
+ assertThat(syntax.getAny()).isEqualTo('%');
+ assertThat(syntax.getSingle()).isEqualTo('_');
+ }
+
+ /**
+ * Test of {@link LikePatternSyntax#convert(String, LikePatternSyntax, boolean)} from {@link LikePatternSyntax#GLOB}
+ * to {@link LikePatternSyntax#SQL}.
+ */
+ @Test
+ public void testGlob2Sql() {
+
+ LikePatternSyntax syntax = LikePatternSyntax.GLOB;
+ assertThat(LikePatternSyntax.SQL.convert("", syntax)).isEqualTo("");
+ assertThat(LikePatternSyntax.SQL.convert("*", syntax)).isEqualTo("%");
+ assertThat(LikePatternSyntax.SQL.convert("?", syntax)).isEqualTo("_");
+ assertThat(LikePatternSyntax.SQL.convert("a*b?c", syntax)).isEqualTo("a%b_c");
+ assertThat(LikePatternSyntax.SQL.convert("*10% key_loss*", syntax)).isEqualTo("%10\\% key\\_loss%");
+
+ assertThat(LikePatternSyntax.SQL.convert("a", syntax, true)).isEqualTo("%a%");
+ assertThat(LikePatternSyntax.SQL.convert("*", syntax, true)).isEqualTo("%");
+ assertThat(LikePatternSyntax.SQL.convert("a*b?c", syntax, true)).isEqualTo("%a%b_c%");
+ }
+
+ /**
+ * Test of {@link LikePatternSyntax#convert(String, LikePatternSyntax, boolean)} from {@link LikePatternSyntax#SQL} to
+ * {@link LikePatternSyntax#GLOB}.
+ */
+ @Test
+ public void testSql2Glob() {
+
+ LikePatternSyntax syntax = LikePatternSyntax.SQL;
+ assertThat(LikePatternSyntax.GLOB.convert("", syntax)).isEqualTo("");
+ assertThat(LikePatternSyntax.GLOB.convert("%", syntax)).isEqualTo("*");
+ assertThat(LikePatternSyntax.GLOB.convert("_", syntax)).isEqualTo("?");
+ assertThat(LikePatternSyntax.GLOB.convert("a%b_c", syntax)).isEqualTo("a*b?c");
+ assertThat(LikePatternSyntax.GLOB.convert("%10* key?loss%", syntax)).isEqualTo("*10\\* key\\?loss*");
+
+ assertThat(LikePatternSyntax.GLOB.convert("a", syntax, true)).isEqualTo("*a*");
+ assertThat(LikePatternSyntax.GLOB.convert("%", syntax, true)).isEqualTo("*");
+ assertThat(LikePatternSyntax.GLOB.convert("a%b_c", syntax, true)).isEqualTo("*a*b?c*");
+ }
+
+ /** Test of {@link LikePatternSyntax#autoDetect(String)}. */
+ @Test
+ public void testAutoDetect() {
+
+ assertThat(LikePatternSyntax.autoDetect(null)).isEqualTo(null);
+ assertThat(LikePatternSyntax.autoDetect("")).isEqualTo(null);
+ assertThat(LikePatternSyntax.autoDetect("a")).isEqualTo(null);
+ assertThat(LikePatternSyntax.autoDetect("aBc")).isEqualTo(null);
+ assertThat(LikePatternSyntax.autoDetect("a*b")).isEqualTo(LikePatternSyntax.GLOB);
+ assertThat(LikePatternSyntax.autoDetect("a?b")).isEqualTo(LikePatternSyntax.GLOB);
+ assertThat(LikePatternSyntax.autoDetect("a%b")).isEqualTo(LikePatternSyntax.SQL);
+ assertThat(LikePatternSyntax.autoDetect("a_b")).isEqualTo(LikePatternSyntax.SQL);
+ }
+
+}
diff --git a/modules/basic/src/test/java/com/devonfw/module/basic/common/api/reference/IdRefTest.java b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/reference/IdRefTest.java
new file mode 100644
index 00000000..ab5c3a02
--- /dev/null
+++ b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/reference/IdRefTest.java
@@ -0,0 +1,80 @@
+package com.devonfw.module.basic.common.api.reference;
+
+import net.sf.mmm.util.entity.api.MutableGenericEntity;
+
+import org.junit.Test;
+
+import com.devonfw.module.basic.common.api.reference.IdRef;
+import com.devonfw.module.basic.common.api.to.AbstractEto;
+import com.devonfw.module.test.common.base.ModuleTest;
+
+/**
+ * Test of {@link IdRef}.
+ */
+public class IdRefTest extends ModuleTest {
+
+ /** Test of {@link IdRef#of(net.sf.mmm.util.entity.api.GenericEntity)} */
+ @Test
+ public void testOfEntity() {
+
+ // given
+ long id = 4711L;
+ FooEto foo = new FooEto();
+ foo.setId(id);
+
+ // when
+ IdRef fooId = IdRef. of(foo); // with Java8 type-inference the additional is not required
+
+ // then
+ assertThat(fooId).isNotNull();
+ assertThat(fooId.getId()).isEqualTo(foo.getId()).isEqualTo(id);
+ assertThat(fooId.toString()).isEqualTo(Long.toString(id));
+ assertThat(IdRef.of((Foo) null)).isNull();
+
+ Bar bar = new Bar();
+ bar.setFooId(fooId); // just a syntax/compilation check.
+ // bar.setBarId(fooId); // will produce compiler error what is desired in such case
+ IdRef barId = IdRef.of(1234L);
+ bar.setBarId(barId); // this again will compile
+ }
+
+ /** Test of {@link IdRef#of(Long)} */
+ @Test
+ public void testOfLong() {
+
+ // given
+ long id = 4711L;
+
+ // when
+ IdRef fooId = IdRef.of(id); // not type-safe but required in some cases
+
+ // then
+ assertThat(fooId).isNotNull();
+ assertThat(fooId.getId()).isEqualTo(id);
+ assertThat(fooId.toString()).isEqualTo(Long.toString(id));
+ assertThat(IdRef.of(Long.valueOf(id))).isEqualTo(fooId).isNotSameAs(fooId);
+ assertThat(IdRef.of((Long) null)).isNull();
+ }
+
+ private interface Foo extends MutableGenericEntity {
+
+ }
+
+ private class FooEto extends AbstractEto implements Foo {
+
+ private static final long serialVersionUID = 1L;
+
+ }
+
+ private class Bar {
+
+ void setFooId(IdRef fooId) {
+
+ }
+
+ void setBarId(IdRef fooId) {
+
+ }
+ }
+
+}
diff --git a/modules/basic/src/test/java/com/devonfw/module/basic/common/api/reflect/Devon4jPackageTest.java b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/reflect/Devon4jPackageTest.java
new file mode 100644
index 00000000..987c00b6
--- /dev/null
+++ b/modules/basic/src/test/java/com/devonfw/module/basic/common/api/reflect/Devon4jPackageTest.java
@@ -0,0 +1,194 @@
+package com.devonfw.module.basic.common.api.reflect;
+
+import org.junit.Test;
+
+import com.devonfw.module.test.common.base.ModuleTest;
+
+/**
+ * Test-case for {@link Devon4jPackage}.
+ *
+ * @author hohwille
+ */
+public class Devon4jPackageTest extends ModuleTest {
+
+ /** Test of {@link Devon4jPackage#of(Class)} with {@link Devon4jPackage}. */
+ @Test
+ public void testOfClass() {
+
+ Class type = Devon4jPackage.class;
+ Devon4jPackage pkg = Devon4jPackage.of(type);
+ assertThat(pkg.getRoot()).isEqualTo("com.devonfw");
+ assertThat(pkg.getApplication()).isEqualTo("module");
+ assertThat(pkg.getComponent()).isEqualTo("basic");
+ assertThat(pkg.getLayer()).isEqualTo("common");
+ assertThat(pkg.isLayerBatch()).isFalse();
+ assertThat(pkg.isLayerClient()).isFalse();
+ assertThat(pkg.isLayerCommon()).isTrue();
+ assertThat(pkg.isLayerDataAccess()).isFalse();
+ assertThat(pkg.isLayerLogic()).isFalse();
+ assertThat(pkg.isLayerService()).isFalse();
+ assertThat(pkg.getScope()).isEqualTo("api");
+ assertThat(pkg.isScopeApi()).isTrue();
+ assertThat(pkg.isScopeBase()).isFalse();
+ assertThat(pkg.isScopeImpl()).isFalse();
+ assertThat(pkg.getDetail()).isEqualTo("reflect");
+ assertThat(pkg.toString()).isEqualTo(type.getPackage().getName());
+ assertThat(pkg.isValid()).isTrue();
+ }
+
+ /** Test of {@link Devon4jPackage#of(String)} with {@code com.devonfw.module.rest.service.impl.json}. */
+ @Test
+ public void testOfStringDevon4jModule() {
+
+ String packageName = "com.devonfw.module.rest.service.impl.json";
+ Devon4jPackage pkg = Devon4jPackage.of(packageName);
+ assertThat(pkg.getRoot()).isEqualTo("com.devonfw");
+ assertThat(pkg.getApplication()).isEqualTo("module");
+ assertThat(pkg.getComponent()).isEqualTo("rest");
+ assertThat(pkg.getLayer()).isEqualTo("service");
+ assertThat(pkg.isLayerBatch()).isFalse();
+ assertThat(pkg.isLayerClient()).isFalse();
+ assertThat(pkg.isLayerCommon()).isFalse();
+ assertThat(pkg.isLayerDataAccess()).isFalse();
+ assertThat(pkg.isLayerLogic()).isFalse();
+ assertThat(pkg.isLayerService()).isTrue();
+ assertThat(pkg.getScope()).isEqualTo("impl");
+ assertThat(pkg.isScopeApi()).isFalse();
+ assertThat(pkg.isScopeBase()).isFalse();
+ assertThat(pkg.isScopeImpl()).isTrue();
+ assertThat(pkg.getDetail()).isEqualTo("json");
+ assertThat(pkg.toString()).isEqualTo(packageName);
+ assertThat(pkg.isValid()).isTrue();
+ }
+
+ /**
+ * Test of {@link Devon4jPackage#of(String)} with
+ * {@code com.devonfw.gastronomy.restaurant.offermanagement.dataaccess.impl.dao}.
+ */
+ @Test
+ public void testOfStringSampleApp() {
+
+ String packageName = "com.devonfw.gastronomy.restaurant.offermanagement.dataaccess.base";
+ Devon4jPackage pkg = Devon4jPackage.of(packageName);
+ assertThat(pkg.getRoot()).isEqualTo("com.devonfw.gastronomy");
+ assertThat(pkg.getApplication()).isEqualTo("restaurant");
+ assertThat(pkg.getComponent()).isEqualTo("offermanagement");
+ assertThat(pkg.getLayer()).isEqualTo("dataaccess");
+ assertThat(pkg.isLayerBatch()).isFalse();
+ assertThat(pkg.isLayerClient()).isFalse();
+ assertThat(pkg.isLayerCommon()).isFalse();
+ assertThat(pkg.isLayerDataAccess()).isTrue();
+ assertThat(pkg.isLayerLogic()).isFalse();
+ assertThat(pkg.isLayerService()).isFalse();
+ assertThat(pkg.getScope()).isEqualTo("base");
+ assertThat(pkg.isScopeApi()).isFalse();
+ assertThat(pkg.isScopeBase()).isTrue();
+ assertThat(pkg.isScopeImpl()).isFalse();
+ assertThat(pkg.isScopeBase()).isTrue();
+ assertThat(pkg.getDetail()).isNull();
+ assertThat(pkg.toString()).isEqualTo(packageName);
+ assertThat(pkg.isValid()).isTrue();
+ }
+
+ /**
+ * Test of {@link Devon4jPackage#of(String, String, String, String, String, String)} with
+ * {@code com.devonfw.gastronomy.restaurant.offermanagement.dataaccess.impl.dao}.
+ */
+ @Test
+ public void testOfSegmentsSampleApp() {
+
+ String root = "com.devonfw.gastronomy";
+ String app = "restaurant";
+ String component = "offermanagement";
+ String layer = "dataaccess";
+ String scope = "base";
+ Devon4jPackage pkg = Devon4jPackage.of(root, app, component, layer, scope, null);
+ assertThat(pkg.getRoot()).isEqualTo(root);
+ assertThat(pkg.getApplication()).isEqualTo(app);
+ assertThat(pkg.getComponent()).isEqualTo(component);
+ assertThat(pkg.getLayer()).isEqualTo(layer);
+ assertThat(pkg.isLayerBatch()).isFalse();
+ assertThat(pkg.isLayerClient()).isFalse();
+ assertThat(pkg.isLayerCommon()).isFalse();
+ assertThat(pkg.isLayerDataAccess()).isTrue();
+ assertThat(pkg.isLayerLogic()).isFalse();
+ assertThat(pkg.isLayerService()).isFalse();
+ assertThat(pkg.getScope()).isEqualTo(scope);
+ assertThat(pkg.isScopeApi()).isFalse();
+ assertThat(pkg.isScopeBase()).isTrue();
+ assertThat(pkg.isScopeImpl()).isFalse();
+ assertThat(pkg.getDetail()).isNull();
+ assertThat(pkg.toString()).isEqualTo("com.devonfw.gastronomy.restaurant.offermanagement.dataaccess.base");
+ assertThat(pkg.isValid()).isTrue();
+ }
+
+ /**
+ * Test of {@link Devon4jPackage#of(String)} with a package-name not strictly following the conventions.
+ */
+ @Test
+ public void testOfStringFallback() {
+
+ String packageName = "com.company.sales.shop.offermanagement.data.api.dataaccess";
+ Devon4jPackage pkg = Devon4jPackage.of(packageName);
+ assertThat(pkg.getRoot()).isEqualTo("com.company.sales");
+ assertThat(pkg.getApplication()).isEqualTo("shop");
+ assertThat(pkg.getComponent()).isEqualTo("offermanagement");
+ assertThat(pkg.getLayer()).isEqualTo("data");
+ assertThat(pkg.isLayerDataAccess()).isFalse();
+ assertThat(pkg.getScope()).isEqualTo("api");
+ assertThat(pkg.isScopeApi()).isTrue();
+ assertThat(pkg.getDetail()).isEqualTo("dataaccess");
+ assertThat(pkg.toString()).isEqualTo(packageName);
+ assertThat(pkg.isValid()).isFalse();
+ }
+
+ /**
+ * Test of {@link Devon4jPackage#of(String)} with an invalid package.
+ */
+ @Test
+ public void testOfStringInvalid() {
+
+ String packageName = "java.nio.channels.spi";
+ Devon4jPackage pkg = Devon4jPackage.of(packageName);
+ assertThat(pkg.getRoot()).isEqualTo(packageName);
+ assertThat(pkg.getApplication()).isNull();
+ assertThat(pkg.getComponent()).isNull();
+ assertThat(pkg.getLayer()).isNull();
+ assertThat(pkg.getScope()).isNull();
+ assertThat(pkg.isScopeApi()).isFalse();
+ assertThat(pkg.getDetail()).isNull();
+ assertThat(pkg.toString()).isEqualTo(packageName);
+ assertThat(pkg.isValid()).isFalse();
+ }
+
+ /**
+ * Test of {@link Devon4jPackage#of(String)} with an illegal package.
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testOfStringIllegal() {
+
+ String packageName = "...batch.api.impl";
+ Devon4jPackage.of(packageName);
+ }
+
+ /**
+ * Test of {@link Devon4jPackage#of(Package)} with the {@link Package} of {@link Devon4jPackage} itself.
+ */
+ @Test
+ public void testOfPackage() {
+
+ Package javaPackage = Devon4jPackage.class.getPackage();
+ Devon4jPackage pkg = Devon4jPackage.of(javaPackage);
+ assertThat(pkg.getRoot()).isEqualTo("com.devonfw");
+ assertThat(pkg.getApplication()).isEqualTo("module");
+ assertThat(pkg.getComponent()).isEqualTo("basic");
+ assertThat(pkg.getLayer()).isEqualTo("common");
+ assertThat(pkg.isLayerCommon()).isTrue();
+ assertThat(pkg.getScope()).isEqualTo("api");
+ assertThat(pkg.isScopeApi()).isTrue();
+ assertThat(pkg.getDetail()).isEqualTo("reflect");
+ assertThat(pkg.toString()).isEqualTo(javaPackage.getName());
+ assertThat(pkg.isValid()).isTrue();
+ }
+
+}
diff --git a/modules/batch/pom.xml b/modules/batch/pom.xml
new file mode 100644
index 00000000..4d1643e4
--- /dev/null
+++ b/modules/batch/pom.xml
@@ -0,0 +1,62 @@
+
+ 4.0.0
+
+ com.devonfw.java.dev
+ devon4j-modules
+ dev-SNAPSHOT
+
+
+ com.devonfw.java.modules
+ devon4j-batch
+ ${devon4j.version}
+ jar
+ ${project.artifactId}
+ Batch infrastructure of the Open Application Standard Platform for Java (devon4j).
+
+
+
+
+ com.devonfw.java.modules
+ devon4j-jpa
+
+
+ com.devonfw.java.modules
+ devon4j-logging
+
+
+
+
+
+ org.springframework.batch
+ spring-batch-core
+
+
+ org.springframework.batch
+ spring-batch-infrastructure
+
+
+ org.springframework.batch
+ spring-batch-integration
+
+
+
+
+ org.springframework.boot
+ spring-boot
+ true
+
+
+
+
+ org.springframework.batch
+ spring-batch-test
+ test
+
+
+ com.devonfw.java.modules
+ devon4j-test
+ test
+
+
+
diff --git a/modules/batch/src/main/java/com/devonfw/module/batch/common/base/SpringBootBatchCommandLine.java b/modules/batch/src/main/java/com/devonfw/module/batch/common/base/SpringBootBatchCommandLine.java
new file mode 100644
index 00000000..d3a46152
--- /dev/null
+++ b/modules/batch/src/main/java/com/devonfw/module/batch/common/base/SpringBootBatchCommandLine.java
@@ -0,0 +1,295 @@
+package com.devonfw.module.batch.common.base;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.configuration.JobLocator;
+import org.springframework.batch.core.converter.DefaultJobParametersConverter;
+import org.springframework.batch.core.converter.JobParametersConverter;
+import org.springframework.batch.core.launch.JobExecutionNotRunningException;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.launch.support.CommandLineJobRunner;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.ExitCodeGenerator;
+import org.springframework.boot.SpringApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.util.StringUtils;
+
+/**
+ * Launcher for launching batch jobs from the command line when Spring Boot is used. Similar to the
+ * {@link CommandLineJobRunner}, which does not work very well with Spring Boot.
+ *
+ * Do not use this class if Spring Boot is not used!
+ *
+ * It expects the full class name of the Spring Boot configuration class to be used as first argument, the class/XML
+ * file for configuring the job as second argument and the job name as third.
+ * Moreover parameters can be specified as further arguments (convention: key1=value1 key2=value2 ...).
+ *