You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@empire-db.apache.org by do...@apache.org on 2024/06/12 12:01:21 UTC
(empire-db) branch master updated: EMPIREDB-428 Jakarta Faces Extensions project
This is an automated email from the ASF dual-hosted git repository.
doebele pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/empire-db.git
The following commit(s) were added to refs/heads/master by this push:
new 227ac744 EMPIREDB-428 Jakarta Faces Extensions project
227ac744 is described below
commit 227ac74403e050bb2b5eec1dbcdd44b23d0f7be7
Author: Rainer Döbele <do...@apache.org>
AuthorDate: Wed Jun 12 14:01:16 2024 +0200
EMPIREDB-428
Jakarta Faces Extensions project
---
empire-db-dist/pom.xml | 10 +
.../empire-db-example-jakarta-faces/.gitignore | 8 +
.../src/main/webapp/WEB-INF/faces-config.pageflow | 2 +
.../pom.xml | 36 +-
.../jakarta/custom/controls/FileInputControl.java | 165 ++
.../empire/jakarta/websample/db/SampleDB.java | 269 +++
.../jakarta/websample/db/SampleDBwithMeta.java | 85 +
.../empire/jakarta/websample/db/SampleTable.java | 82 +
.../websample/db/records/DepartmentRecord.java | 33 +
.../websample/db/records/EmployeeRecord.java | 88 +
.../jakarta/websample/db/records/SampleRecord.java | 89 +
.../jakarta/websample/web/AppExceptionHandler.java | 228 +++
.../websample/web/AppExceptionHandlerFactory.java | 47 +
.../websample/web/AuthenticationPhaseListener.java | 154 ++
.../websample/web/SampleAppStartupListener.java | 60 +
.../jakarta/websample/web/SampleApplication.java | 362 ++++
.../empire/jakarta/websample/web/SampleConfig.java | 195 ++
.../jakarta/websample/web/SampleContext.java | 77 +
.../jakarta/websample/web/SampleSession.java | 79 +
.../empire/jakarta/websample/web/SampleUser.java | 67 +
.../empire/jakarta/websample/web/SampleUtils.java | 96 +
.../web/objects/EmployeeSearchFilter.java | 102 +
.../empire/jakarta/websample/web/objects/User.java | 49 +
.../websample/web/pages/EmployeeDetailPage.java | 178 ++
.../websample/web/pages/EmployeeListPage.java | 267 +++
.../jakarta/websample/web/pages/ErrorPage.java | 72 +
.../jakarta/websample/web/pages/LoginPage.java | 62 +
.../jakarta/websample/web/pages/SamplePage.java | 70 +
.../jakarta/websample/web/pages/SamplePages.java | 32 +
.../src/main/resources/lang/messages_de.properties | 151 ++
.../src/main/resources/lang/messages_en.properties | 151 ++
.../src/main/webapp/.gitignore | 2 +
.../src/main/webapp/META-INF/context.xml | 28 +
.../src/main/webapp/WEB-INF/config.xml | 182 ++
.../webapp/WEB-INF/facelets/templates/header.xhtml | 31 +
.../webapp/WEB-INF/facelets/templates/layout.xhtml | 75 +
.../src/main/webapp/WEB-INF/faces-config.xml | 143 ++
.../src/main/webapp/WEB-INF/web.xml | 60 +
.../src/main/webapp/css/common.css | 87 +
.../src/main/webapp/css/content.css | 135 ++
.../src/main/webapp/css/form.css | 206 ++
.../src/main/webapp/css/layout.css | 24 +
.../src/main/webapp/css/root.css | 36 +
.../src/main/webapp/img/boxes.gif | Bin 0 -> 1132 bytes
.../src/main/webapp/index.html | 31 +
.../src/main/webapp/pages/employeeDetailPage.xhtml | 108 ++
.../src/main/webapp/pages/employeeListPage.xhtml | 111 ++
.../src/main/webapp/pages/loginPage.xhtml | 62 +
.../webapp/resources/components/formControl.xhtml | 41 +
.../webapp/resources/components/pageButtons.xhtml | 36 +
.../src/main/webapp/resources/empire/control.xhtml | 53 +
.../main/webapp/resources/empire/formGrid.xhtml | 35 +
.../src/main/webapp/resources/empire/input.xhtml | 43 +
.../src/main/webapp/resources/empire/label.xhtml | 38 +
.../src/main/webapp/resources/empire/link.xhtml | 40 +
.../src/main/webapp/resources/empire/mitem.xhtml | 37 +
.../src/main/webapp/resources/empire/mlist.xhtml | 37 +
.../src/main/webapp/resources/empire/record.xhtml | 35 +
.../src/main/webapp/resources/empire/select.xhtml | 39 +
.../src/main/webapp/resources/empire/tabPage.xhtml | 33 +
.../src/main/webapp/resources/empire/tabView.xhtml | 37 +
.../src/main/webapp/resources/empire/title.xhtml | 37 +
.../src/main/webapp/resources/empire/value.xhtml | 41 +
empire-db-examples/empire-db-example-jsf2/pom.xml | 6 +-
empire-db-examples/pom.xml | 1 +
empire-db-jakarta-faces/.gitignore | 7 +
.../pom.xml | 37 +-
.../apache/empire/jakarta/app/DBELResolver.java | 193 ++
.../empire/jakarta/app/FacesConfiguration.java | 615 ++++++
.../jakarta/app/FacesRequestPhaseListener.java | 80 +
.../org/apache/empire/jakarta/app/FacesUtils.java | 544 ++++++
.../apache/empire/jakarta/app/TextResolver.java | 35 +
.../empire/jakarta/app/WebAppStartupListener.java | 175 ++
.../apache/empire/jakarta/app/WebApplication.java | 640 +++++++
.../apache/empire/jakarta/app/WebDBContext.java | 170 ++
.../empire/jakarta/components/ControlTag.java | 820 ++++++++
.../empire/jakarta/components/FormGridTag.java | 169 ++
.../apache/empire/jakarta/components/InputTag.java | 435 +++++
.../apache/empire/jakarta/components/LabelTag.java | 140 ++
.../apache/empire/jakarta/components/LinkTag.java | 418 ++++
.../empire/jakarta/components/MenuItemTag.java | 315 +++
.../empire/jakarta/components/MenuListTag.java | 306 +++
.../apache/empire/jakarta/components/NbspTag.java | 70 +
.../empire/jakarta/components/RecordTag.java | 66 +
.../empire/jakarta/components/SelectTag.java | 472 +++++
.../empire/jakarta/components/TabPageTag.java | 126 ++
.../empire/jakarta/components/TabViewTag.java | 467 +++++
.../apache/empire/jakarta/components/TitleTag.java | 117 ++
.../apache/empire/jakarta/components/UnitTag.java | 80 +
.../apache/empire/jakarta/components/ValueTag.java | 119 ++
.../jakarta/controls/CheckboxInputControl.java | 116 ++
.../controls/InputAttachedObjectsHandler.java | 103 +
.../empire/jakarta/controls/InputControl.java | 881 +++++++++
.../jakarta/controls/InputControlManager.java | 184 ++
.../empire/jakarta/controls/RadioInputControl.java | 330 ++++
.../jakarta/controls/SelectInputControl.java | 449 +++++
.../jakarta/controls/TextAreaInputControl.java | 154 ++
.../empire/jakarta/controls/TextInputControl.java | 689 +++++++
.../empire/jakarta/impl/FacesImplementation.java | 121 ++
.../empire/jakarta/impl/MojarraImplementation.java | 221 +++
.../empire/jakarta/impl/MyFacesImplementation.java | 194 ++
.../empire/jakarta/impl/ResourceTextResolver.java | 124 ++
.../jakarta/pageelements/BeanListPageElement.java | 636 ++++++
.../jakarta/pageelements/ListPageElement.java | 709 +++++++
.../jakarta/pageelements/RecordPageElement.java | 244 +++
.../pageelements/StaticListPageElement.java | 122 ++
.../java/org/apache/empire/jakarta/pages/Page.java | 569 ++++++
.../empire/jakarta/pages/PageDefinition.java | 226 +++
.../empire/jakarta/pages/PageDefinitions.java | 168 ++
.../empire/jakarta/pages/PageELResolver.java | 115 ++
.../apache/empire/jakarta/pages/PageElement.java | 163 ++
.../jakarta/pages/PageNavigationHandler.java | 164 ++
.../apache/empire/jakarta/pages/PageOutcome.java | 64 +
.../empire/jakarta/pages/PagePhaseListener.java | 233 +++
.../empire/jakarta/pages/PagesELResolver.java | 166 ++
.../empire/jakarta/utils/ControlRenderInfo.java | 51 +
.../empire/jakarta/utils/ListColumnFinder.java | 26 +
.../empire/jakarta/utils/ListItemSelection.java | 31 +
.../apache/empire/jakarta/utils/ParameterMap.java | 324 ++++
.../empire/jakarta/utils/ParameterObject.java | 34 +
.../apache/empire/jakarta/utils/SessionMap.java | 66 +
.../empire/jakarta/utils/StringResponseWriter.java | 151 ++
.../empire/jakarta/utils/TagContextInfo.java | 26 +
.../empire/jakarta/utils/TagEncodingHelper.java | 2024 ++++++++++++++++++++
.../jakarta/utils/TagEncodingHelperFactory.java | 54 +
.../apache/empire/jakarta/utils/TagStyleClass.java | 182 ++
.../jakarta/utils/ValueExpressionUnwrapper.java | 125 ++
empire-db-jsf2/pom.xml | 5 +-
pom.xml | 34 +-
129 files changed, 22113 insertions(+), 57 deletions(-)
diff --git a/empire-db-dist/pom.xml b/empire-db-dist/pom.xml
index 75984f2b..bab6e99f 100644
--- a/empire-db-dist/pom.xml
+++ b/empire-db-dist/pom.xml
@@ -73,6 +73,10 @@
<groupId>org.apache.empire-db</groupId>
<artifactId>empire-db-jsf2</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.empire-db</groupId>
+ <artifactId>empire-db-jakarta-faces</artifactId>
+ </dependency>
<!-- javadoc -->
<dependency>
<groupId>org.apache.empire-db</groupId>
@@ -86,6 +90,12 @@
<version>3.1.1-SNAPSHOT</version>
<classifier>javadoc</classifier>
</dependency>
+ <dependency>
+ <groupId>org.apache.empire-db</groupId>
+ <artifactId>empire-db-jakarta-faces</artifactId>
+ <version>3.1.1-SNAPSHOT</version>
+ <classifier>javadoc</classifier>
+ </dependency>
</dependencies>
</project>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/.gitignore b/empire-db-examples/empire-db-example-jakarta-faces/.gitignore
new file mode 100644
index 00000000..22247c22
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/.gitignore
@@ -0,0 +1,8 @@
+/target/
+/.settings/
+/.classpath
+/.project
+/.tomcatplugin
+/.apt_generated/
+/.factorypath
+/.apt_generated_tests/
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/.metadata/src/main/webapp/WEB-INF/faces-config.pageflow b/empire-db-examples/empire-db-example-jakarta-faces/.metadata/src/main/webapp/WEB-INF/faces-config.pageflow
new file mode 100644
index 00000000..0e082772
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/.metadata/src/main/webapp/WEB-INF/faces-config.pageflow
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<pageflow:Pageflow xmlns:pageflow="http://www.sybase.com/suade/pageflow" id="pf17181908672340" configfile="/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/faces-config.xml"/>
diff --git a/empire-db-examples/empire-db-example-jsf2/pom.xml b/empire-db-examples/empire-db-example-jakarta-faces/pom.xml
similarity index 83%
copy from empire-db-examples/empire-db-example-jsf2/pom.xml
copy to empire-db-examples/empire-db-example-jakarta-faces/pom.xml
index 91f9c91c..eedea683 100644
--- a/empire-db-examples/empire-db-example-jsf2/pom.xml
+++ b/empire-db-examples/empire-db-example-jakarta-faces/pom.xml
@@ -21,9 +21,9 @@
<groupId>org.apache.empire-db</groupId>
<version>3.1.1-SNAPSHOT</version>
</parent>
- <artifactId>empire-db-example-jsf2</artifactId>
+ <artifactId>empire-db-example-jakarta-faces</artifactId>
<name>Apache Empire-db JSF2 Example</name>
- <description>Example showing the use of the empire-db-jsf2 extensions</description>
+ <description>Example showing the use of the empire-db-jakarta-faces extensions</description>
<packaging>war</packaging>
<properties>
@@ -38,42 +38,48 @@
<dependency>
<groupId>org.apache.empire-db</groupId>
- <artifactId>empire-db-jsf2</artifactId>
+ <artifactId>empire-db-jakarta-faces</artifactId>
</dependency>
+ <!-- Servlet API -->
<dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <scope>provided</scope>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>5.0.0</version>
+ <scope>provided</scope>
</dependency>
- <!-- Sun Mojarra -->
+ <!-- Mojarra -->
<!--
<dependency>
- <groupId>com.sun.faces</groupId>
- <artifactId>jsf-api</artifactId>
+ <groupId>jakarta.faces</groupId>
+ <artifactId>jakarta.faces-api</artifactId>
+ <version>3.0.0</version>
</dependency>
<dependency>
- <groupId>com.sun.faces</groupId>
- <artifactId>jsf-impl</artifactId>
- </dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>jakarta.faces</artifactId>
+ <version>3.0.5</version>
+ </dependency>
-->
<!-- Apache MyFaces -->
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
+ <version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
+ <version>3.0.2</version>
</dependency>
<!-- EL -->
<dependency>
- <groupId>javax.el</groupId>
- <artifactId>javax.el-api</artifactId>
- <type>jar</type>
+ <groupId>jakarta.el</groupId>
+ <artifactId>jakarta.el-api</artifactId>
+ <version>4.0.0</version>
<scope>provided</scope>
</dependency>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/custom/controls/FileInputControl.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/custom/controls/FileInputControl.java
new file mode 100644
index 00000000..42286a81
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/custom/controls/FileInputControl.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.custom.controls;
+
+import java.util.List;
+
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.html.HtmlInputText;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.event.PhaseId;
+
+import org.apache.empire.exceptions.InternalException;
+import org.apache.empire.exceptions.UnexpectedReturnValueException;
+import org.apache.empire.jakarta.controls.InputControl;
+
+public class FileInputControl extends InputControl
+{
+ public static final String NAME = "file";
+
+ private Class<? extends HtmlInputFile> inputComponentClass = null;
+
+ public FileInputControl(Class<? extends HtmlInputFile> inputComponentClass)
+ {
+ super(NAME);
+ this.inputComponentClass = inputComponentClass;
+ }
+
+ public FileInputControl()
+ {
+ this(HtmlInputFile.class);
+ }
+
+ @Override
+ protected void createInputComponents(UIComponent parent, InputInfo ii, FacesContext context, List<UIComponent> compList)
+ {
+ HtmlInputFile input;
+ try
+ {
+ input = inputComponentClass.newInstance();
+ copyAttributes(parent, ii, input);
+ }
+ catch (InstantiationException e1)
+ {
+ throw new InternalException(e1);
+ }
+ catch (IllegalAccessException e2)
+ {
+ throw new InternalException(e2);
+ }
+ compList.add(input);
+ // update
+ updateInputState(compList, ii, context, context.getCurrentPhaseId());
+ }
+
+ @Override
+ protected void updateInputState(List<UIComponent> compList, InputInfo ii, FacesContext context, PhaseId phaseId)
+ {
+ UIComponent comp = compList.get(0);
+ if (!(comp instanceof HtmlInputFile))
+ {
+ throw new UnexpectedReturnValueException(comp.getClass().getName(), "compList.get(0)");
+ }
+ // update state
+ HtmlInputFile input = (HtmlInputFile) comp;
+ input.setDisabled(ii.isDisabled());
+ // set value
+ if (phaseId==PhaseId.RENDER_RESPONSE)
+ { // set value
+ setInputValue(input, ii);
+ }
+ }
+
+ public class HtmlInputFile extends HtmlInputText
+ {
+ public static final String RENDER_TYPE = "org.apache.empire.jsf2.custom.controls.FileInputRenderer";
+
+ @Override
+ public String getRendererType()
+ {
+ return RENDER_TYPE;
+ }
+ }
+
+ /*
+ public class FileInputRenderer extends TextRenderer
+ {
+
+ // Constants ----------------------------------------------------------------------------------
+
+ private static final String EMPTY_STRING = "";
+ private final Attribute[] INPUT_ATTRIBUTES = AttributeManager.getAttributes(AttributeManager.Key.INPUTTEXT);
+
+ // Actions ------------------------------------------------------------------------------------
+
+ @Override
+ protected void getEndTextToRender(FacesContext context, UIComponent component, String currentValue)
+ throws IOException
+ {
+ ResponseWriter writer = context.getResponseWriter();
+ writer.startElement("input", component);
+ writeIdAttributeIfNecessary(context, writer, component);
+ writer.writeAttribute("type", "file", null);
+ writer.writeAttribute("name", (component.getClientId(context)), "clientId");
+
+ // Render styleClass, if any.
+ String styleClass = (String) component.getAttributes().get("styleClass");
+ if (styleClass != null)
+ {
+ writer.writeAttribute("class", styleClass, "styleClass");
+ }
+
+ // Render standard HTMLattributes expect of styleClass.
+ RenderKitUtils.renderPassThruAttributes(context, writer, component, INPUT_ATTRIBUTES, getNonOnChangeBehaviors(component));
+ RenderKitUtils.renderXHTMLStyleBooleanAttributes(writer, component);
+ RenderKitUtils.renderOnchange(context, component, false);
+
+ writer.endElement("input");
+ }
+
+ @Override
+ public void decode(FacesContext context, UIComponent component)
+ {
+ rendererParamsNotNull(context, component);
+ if (!shouldDecode(component))
+ {
+ return;
+ }
+ String clientId = decodeBehaviors(context, component);
+ if (clientId == null)
+ {
+ clientId = component.getClientId(context);
+ }
+
+ // File file = ((MultipartRequest) context.getExternalContext().getRequest()).getFile(clientId);
+ // If no file is specified, set empty String to trigger validators.
+ // ((UIInput) component).setSubmittedValue((file != null) ? file : EMPTY_STRING);
+ }
+
+ @Override
+ public Object getConvertedValue(FacesContext context, UIComponent component, Object submittedValue)
+ throws ConverterException
+ {
+ return (submittedValue != EMPTY_STRING) ? submittedValue : null;
+ }
+
+ }
+ */
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleDB.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleDB.java
new file mode 100644
index 00000000..eefde568
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleDB.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.db;
+
+import java.sql.SQLException;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBSQLScript;
+import org.apache.empire.db.DBTableColumn;
+import org.apache.empire.db.validation.DBModelChecker;
+import org.apache.empire.db.validation.DBModelErrorLogger;
+import org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <PRE>
+ * This file contains the definition of the data model in Java.
+ * The SampleDB data model consists of three tables and two foreign key relations.
+ * The tables are defined as nested classes here, but you may put them in separate files if you want to.
+ *
+ * The class also provided methods to create the database (if it doesn't exist)
+ * and to check the datamodel i.e. to compare this definition to the database in the DBMS
+ *
+ * PLEASE NOTE THE NAMING CONVENTION:
+ * Since all tables, views and columns are declared as "final" constants we have all put them in upper case.
+ *
+ * You may declare other database tables or views in the same way.
+ * </PRE>
+ */
+public class SampleDB extends DBDatabase
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(SampleDB.class);
+
+ /**
+ * Gender enum
+ */
+ public enum Gender
+ {
+ M("Male"),
+ F("Female"),
+ U("Unknown");
+
+ private final String title;
+ private Gender(String title)
+ {
+ this.title = title;
+ }
+ @Override
+ public String toString()
+ {
+ return title;
+ }
+ }
+
+ /**
+ * This class represents the Departments table.
+ */
+ public static class TDepartments extends SampleTable
+ {
+ public final DBTableColumn ID;
+ public final DBTableColumn NAME;
+ public final DBTableColumn HEAD;
+ public final DBTableColumn BUSINESS_UNIT;
+ public final DBTableColumn UPDATE_TIMESTAMP;
+
+ public TDepartments(SampleDB db)
+ {
+ super("DEPARTMENTS", db);
+ // ID
+ ID = addIdentity ("ID", "DEP_ID_SEQUENCE"); // Optional Sequence for some DBMS (e.g. Oracle)
+ NAME = addColumn ("NAME", DataType.VARCHAR, 80, true);
+ HEAD = addColumn ("HEAD", DataType.VARCHAR, 80, false);
+ BUSINESS_UNIT = addColumn ("BUSINESS_UNIT", DataType.VARCHAR, 4, true, "ITTK");
+ UPDATE_TIMESTAMP= addTimestamp("UPDATE_TIMESTAMP");
+
+ // Primary Key (automatically set due to addIdentity()) otherwise use
+ // setPrimaryKey(...);
+
+ // Set other Indexes
+ addIndex("DEPARTMENT_NAME_IDX", true, new DBColumn[] { NAME });
+
+ // optional: set entity name for selectQualified() to singular since table is plural
+ setEntityName("DEPARTMENT");
+ }
+ }
+
+ /**
+ * This class represents the Employees table.
+ */
+ public static class TEmployees extends SampleTable
+ {
+ public final DBTableColumn ID;
+ public final DBTableColumn SALUTATION;
+ public final DBTableColumn FIRST_NAME;
+ public final DBTableColumn LAST_NAME;
+ public final DBTableColumn DATE_OF_BIRTH;
+ public final DBTableColumn DEPARTMENT_ID;
+ public final DBTableColumn GENDER;
+ public final DBTableColumn PHONE_NUMBER;
+ public final DBTableColumn EMAIL;
+ public final DBTableColumn SALARY;
+ public final DBTableColumn RETIRED;
+ public final DBTableColumn UPDATE_TIMESTAMP;
+
+ public TEmployees(SampleDB db)
+ {
+ super("EMPLOYEES", db);
+
+ // ID
+ ID = addIdentity ("ID", "EMPLOYEE_ID_SEQUENCE"); // Optional Sequence name for some DBMS (e.g. Oracle)
+ SALUTATION = addColumn ("SALUTATION", DataType.VARCHAR, 5, false);
+ FIRST_NAME = addColumn ("FIRST_NAME", DataType.VARCHAR, 40, true);
+ LAST_NAME = addColumn ("LAST_NAME", DataType.VARCHAR, 40, true);
+ DATE_OF_BIRTH = addColumn ("DATE_OF_BIRTH", DataType.DATE, 0, false);
+ DEPARTMENT_ID = addForeignKey("DEPARTMENT_ID", db.DEPARTMENTS, true);
+ GENDER = addColumn ("GENDER", DataType.VARCHAR, 1, true, Gender.class);
+ PHONE_NUMBER = addColumn ("PHONE_NUMBER", DataType.VARCHAR, 40, false, "0815-");
+ EMAIL = addColumn ("EMAIL", DataType.VARCHAR, 80, false);
+ SALARY = addColumn ("SALARY", DataType.DECIMAL, 10.2, false);
+ RETIRED = addColumn ("RETIRED", DataType.BOOL, 0, true, false);
+ UPDATE_TIMESTAMP= addTimestamp ("UPDATE_TIMESTAMP");
+
+ // Primary Key (automatically set due to addIdentity()) otherwise use
+ // setPrimaryKey(...);
+
+ // Set other Indexes
+ addIndex("EMPLOYEE_NAME_IDX", true, new DBColumn[] { FIRST_NAME, LAST_NAME, DATE_OF_BIRTH });
+
+ // optional: set entity name for selectQualified() to singular since table is plural
+ setEntityName("EMPLOYEE");
+ }
+ }
+
+ /**
+ * This class represents the Payments table.
+ */
+ public static class TPayments extends SampleTable
+ {
+ public final DBTableColumn EMPLOYEE_ID;
+ public final DBTableColumn YEAR;
+ public final DBTableColumn MONTH;
+ public final DBTableColumn AMOUNT;
+
+ public TPayments(SampleDB db)
+ {
+ super("PAYMENTS", db);
+
+ // ID
+ EMPLOYEE_ID = addForeignKey("EMPLOYEE_ID", db.EMPLOYEES, true);
+ YEAR = addColumn("YEAR", DataType.DECIMAL, 4.0, true);
+ MONTH = addColumn("MONTH", DataType.DECIMAL, 2.0, true);
+ AMOUNT = addColumn("AMOUNT", DataType.DECIMAL, 8.2, true);
+
+ // Primary Key
+ setPrimaryKey(EMPLOYEE_ID, YEAR, MONTH);
+
+ // optional: set entity name for selectQualified() to singular since table is plural
+ setEntityName("PAYMENT");
+ }
+ }
+
+ // Declare all Tables and Views here
+ public final TDepartments DEPARTMENTS = new TDepartments(this);
+ public final TEmployees EMPLOYEES = new TEmployees(this);
+ public final TPayments PAYMENTS = new TPayments(this);
+
+ /**
+ * Constructor of the SampleDB data model
+ *
+ * Put all foreign key relations here.
+ */
+ public SampleDB()
+ {
+ // Define additional Foreign-Key Relations here
+ // which have not already been defined by addForeignKey()
+ // addRelation( {Source Column}.referenceOn( {Target Column} ));
+ log.info("SampleDB has been created with {} Tables and {} Relations", getTables().size(), getRelations().size());
+ }
+
+ // Needed for the DBELResolver
+ @Override
+ protected void register(String id)
+ {
+ super.register("db");
+ }
+
+ @Override
+ public void open(DBContext context)
+ {
+ // Enable prepared statements
+ // setPreparedStatementsEnabled(true);
+ // Check exists
+ if (checkExists(context))
+ { // attach to driver
+ super.open(context);
+ // yes, it exists, then check the model
+ checkDataModel(context);
+ }
+ else
+ { // PostgreSQL does not support DDL in transaction
+ if(getDbms() instanceof DBMSHandlerPostgreSQL)
+ setAutoCommit(context, true);
+ // create the database
+ createDatabase(context);
+ // PostgreSQL does not support DDL in transaction
+ if(getDbms() instanceof DBMSHandlerPostgreSQL)
+ setAutoCommit(context, false);
+ // attach to driver
+ super.open(context);
+ }
+ }
+
+ private void createDatabase(DBContext context)
+ {
+ // create DDL for Database Definition
+ DBSQLScript script = new DBSQLScript(context);
+ getCreateDDLScript(script);
+ // Show DDL Statement
+ log.info(script.toString());
+ // Execute Script
+ script.executeAll(false);
+ // Commit
+ context.commit();
+ }
+
+ private void checkDataModel(DBContext context)
+ { try {
+ DBModelChecker modelChecker = context.getDbms().createModelChecker(this);
+ // Check data model
+ log.info("Checking DataModel for {} using {}", getClass().getSimpleName(), modelChecker.getClass().getSimpleName());
+ // dbo schema
+ DBModelErrorLogger logger = new DBModelErrorLogger();
+ modelChecker.checkModel(this, context.getConnection(), logger);
+ // show result
+ log.info("Data model check done. Found {} errors and {} warnings.", logger.getErrorCount(), logger.getWarnCount());
+ } catch(Exception e) {
+ log.error("FATAL error when checking data model. Probably not properly implemented by DBMSHandler!");
+ }
+ }
+
+ private void setAutoCommit(DBContext context, boolean enable)
+ { try {
+ context.getConnection().setAutoCommit(enable);
+ } catch (SQLException e) {
+ log.error("Unable to set AutoCommit on Connection", e);
+ }
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleDBwithMeta.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleDBwithMeta.java
new file mode 100644
index 00000000..2ee1d848
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleDBwithMeta.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.db;
+
+import org.apache.empire.commons.Options;
+import org.apache.empire.data.Column;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.jakarta.controls.TextInputControl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SampleDBwithMeta extends SampleDB
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(SampleDBwithMeta.class);
+
+ @Override
+ public void open(DBContext context)
+ {
+ super.open(context);
+ // Hint about metadata
+ log.info("Basic metadata such as Title has already been set in SampleTable.addColumn()");
+ // add additional metadata e.g. for JSF-controls
+ addMeta(EMPLOYEES);
+ }
+
+ private void addMeta(SampleDB.TEmployees T)
+ {
+ log.info("Adding additional Metadata for {}", T.getName());
+ // Set custom style for SALUTATION
+ T.SALUTATION.setAttribute("styleClass", "eInpShort");
+
+ // Create Options for GENDER column
+ // add the message-keys instead of the enum string value
+ Options genders = new Options();
+ genders.set(Gender.M, "!option.employee.gender.male");
+ genders.set(Gender.F, "!option.employee.gender.female");
+ T.GENDER.setOptions(genders);
+ T.GENDER.setControlType("select");
+ T.GENDER.setAttribute("styleClass", "eInpShort");
+
+ // RETIRED column
+ Options retired = new Options();
+ retired.set(false, "!option.employee.active");
+ retired.set(true, "!option.employee.retired");
+ T.RETIRED.setOptions(retired)
+ .setControlType("checkbox")
+ .setAttribute("wrapperClass", "checkboxWrapper"); /* NEW: Wrapper class for <e:input> and <e:control>! */
+
+ // Hint for DATE_OF_BIRTH
+ T.DATE_OF_BIRTH.setAttribute("format:date", "yyyy-MM-dd");
+ T.DATE_OF_BIRTH.setAttribute("hint", "[yyyy-MM-dd]");
+
+ // Salary special
+ T.SALARY.setAttribute("styleClass", "eInpDecimal")
+ .setAttribute(TextInputControl.FORMAT_UNIT_ATTRIBUTE, "USD")
+ .setAttribute(Column.COLATTR_NUMBER_TYPE, "Decimal")
+ .setAttribute(Column.COLATTR_FRACTION_DIGITS, 2)
+ .setAttribute(Column.COLATTR_NUMBER_GROUPSEP, true);
+
+ // Set special control types
+ T.DEPARTMENT_ID.setControlType("select");
+ // T.PHONE_NUMBER .setControlType("phone");
+
+ // UPDATE_TIMESTAMP
+ T.UPDATE_TIMESTAMP.setAttribute("format:date", "full");
+ // format="date-format:full" readonly="true" rendered="#{page.idParam != null}"
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleTable.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleTable.java
new file mode 100644
index 00000000..3b211fce
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/SampleTable.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.db;
+
+import java.util.Locale;
+
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBTable;
+import org.apache.empire.db.DBTableColumn;
+
+/**
+ * Base class definition for all database tables Automatically generates a message-key for the field title e.g. for the column
+ * EMPLOYEES.DATE_OF_BIRTH it generates the key "!field.title.employees.dateOfBirth";
+ */
+public class SampleTable extends DBTable
+{
+ // *Deprecated* private static final long serialVersionUID = 1L;
+
+ public final String MESSAGE_KEY_PREFIX = "!field.title.";
+
+ public SampleTable(String name, DBDatabase db)
+ {
+ super(name, db);
+ }
+
+ @Override
+ protected void addColumn(DBTableColumn column)
+ {
+ // Set Translation Title
+ String col = column.getBeanPropertyName();
+ String tbl = getName().toLowerCase();
+ String key = MESSAGE_KEY_PREFIX + tbl + "." + col;
+ column.setTitle(key);
+
+ // Set Default Control Type
+ DataType type = column.getDataType();
+ column.setControlType((type == DataType.BOOL) ? "checkbox" : "text");
+
+ // Add Column
+ super.addColumn(column);
+ }
+
+ public enum LanguageIndex {
+ DE(Locale.GERMAN),
+
+ EN(Locale.ENGLISH);
+
+ private final Locale locale;
+
+ private LanguageIndex(Locale locale)
+ {
+ this.locale = locale;
+ }
+
+ public Locale getLocale()
+ {
+ return this.locale;
+ }
+
+ public String getDBLangKey()
+ {
+ return this.name().toUpperCase();
+ }
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/DepartmentRecord.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/DepartmentRecord.java
new file mode 100644
index 00000000..dac3463f
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/DepartmentRecord.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.db.records;
+
+import org.apache.empire.jakarta.websample.db.SampleDB.TDepartments;
+import org.apache.empire.jakarta.websample.web.SampleContext;
+
+public class DepartmentRecord extends SampleRecord<TDepartments>
+{
+ private static final long serialVersionUID = 1L;
+
+ public DepartmentRecord(SampleContext context)
+ {
+ super(context, context.getDatabase().DEPARTMENTS);
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/EmployeeRecord.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/EmployeeRecord.java
new file mode 100644
index 00000000..b5318a20
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/EmployeeRecord.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.db.records;
+
+import org.apache.empire.commons.Options;
+import org.apache.empire.data.Column;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.jakarta.websample.db.SampleDB;
+import org.apache.empire.jakarta.websample.db.SampleDB.TEmployees;
+import org.apache.empire.jakarta.websample.web.SampleContext;
+
+public class EmployeeRecord extends SampleRecord<TEmployees>
+{
+ private static final long serialVersionUID = 1L;
+
+ public EmployeeRecord(SampleContext context)
+ {
+ super(context, context.getDatabase().EMPLOYEES);
+ }
+
+ /*
+ * Add some business logic:
+ * Make all fields read only if employee is retired (except the retired field itself!)
+ */
+ @Override
+ public boolean isFieldVisible(Column column)
+ {
+ if (!super.isFieldVisible(column))
+ return false;
+ // UPDATE_TIMESTAMP
+ if (column==T.UPDATE_TIMESTAMP && isNew())
+ return false; // invisible for new records
+ // default
+ return true;
+ }
+
+ @Override
+ public boolean isFieldReadOnly(Column column)
+ {
+ if (column!=T.RETIRED && getBoolean(T.RETIRED))
+ { /* Employee is retired */
+ return true;
+ }
+ return super.isFieldReadOnly(column);
+ }
+
+ @Override
+ public Options getFieldOptions(DBColumn column)
+ {
+ if (column.equals(T.DEPARTMENT_ID))
+ {
+ SampleDB db = (SampleDB) getDatabase();
+ DBCommand cmd = context.createCommand();
+ cmd.select(db.DEPARTMENTS.ID);
+ cmd.select(db.DEPARTMENTS.NAME);
+ cmd.orderBy(db.DEPARTMENTS.NAME);
+ return context.getUtils().queryOptionList(cmd);
+ }
+ // base class implementation
+ return super.getFieldOptions(column);
+ }
+
+ // Sample Implementation for Department Record
+ public DepartmentRecord getDepartmentRecord()
+ {
+ DepartmentRecord rec = new DepartmentRecord((SampleContext)getContext());
+ rec.read(this.getInt(T.DEPARTMENT_ID));
+ return rec;
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/SampleRecord.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/SampleRecord.java
new file mode 100644
index 00000000..f114e766
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/db/records/SampleRecord.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.db.records;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import jakarta.faces.context.FacesContext;
+
+import org.apache.empire.commons.ClassUtils;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.DBRecord;
+import org.apache.empire.jakarta.app.FacesUtils;
+import org.apache.empire.jakarta.websample.db.SampleTable;
+import org.apache.empire.jakarta.websample.web.pages.SamplePage;
+
+public abstract class SampleRecord<T extends SampleTable> extends DBRecord
+{
+ private static final long serialVersionUID = 1L;
+
+ /*
+ * Store the table for convenience
+ */
+ protected final transient T T; /* transient for serialization */
+
+ /**
+ * Custom deserialization for transient T.
+ */
+ private void readObject(ObjectInputStream strm)
+ throws IOException, ClassNotFoundException
+ { // Restore T
+ @SuppressWarnings("unchecked")
+ T table = (T)super.getRowSet();
+ ClassUtils.setPrivateFieldValue(SampleRecord.class, this, "T", table);
+ // read the rest
+ strm.defaultReadObject();
+ }
+
+ @Override
+ protected void writeContext(ObjectOutputStream strm) throws IOException
+ {
+ // Nothing
+ }
+
+ @Override
+ protected DBContext readContext(ObjectInputStream strm) throws IOException, ClassNotFoundException
+ {
+ SamplePage page = FacesUtils.getPage(FacesContext.getCurrentInstance());
+ return page.getSampleContext();
+ }
+
+ /**
+ * Constructor for SampleRecord
+ * @param context
+ * @param table
+ */
+ protected SampleRecord(DBContext context, T table)
+ {
+ super(context, table);
+ this.T = table;
+ }
+
+ /**
+ * Returns the table this record is based upon.
+ * @return The table this record is based upon.
+ */
+ public T getTable()
+ {
+ return T;
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AppExceptionHandler.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AppExceptionHandler.java
new file mode 100644
index 00000000..074bfbbf
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AppExceptionHandler.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import jakarta.faces.FacesException;
+import jakarta.faces.application.FacesMessage;
+import jakarta.faces.context.ExceptionHandler;
+import jakarta.faces.context.ExceptionHandlerWrapper;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.event.ExceptionQueuedEvent;
+import jakarta.faces.event.ExceptionQueuedEventContext;
+
+import org.apache.empire.exceptions.EmpireException;
+import org.apache.empire.exceptions.InternalException;
+import org.apache.empire.jakarta.app.FacesUtils;
+import org.apache.empire.jakarta.app.TextResolver;
+import org.apache.empire.jakarta.app.WebApplication;
+import org.apache.empire.jakarta.websample.web.pages.SamplePages;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/*
+ * AppExceptionHandler
+ */
+public class AppExceptionHandler extends ExceptionHandlerWrapper
+{
+ private static final Logger log = LoggerFactory.getLogger(AppExceptionHandler.class);
+
+ private final ExceptionHandler delegate;
+
+ public AppExceptionHandler(ExceptionHandler delegate)
+ {
+ // super(delegate); --- Use this in MyFaces 2.3.x
+ this.delegate = delegate;
+ }
+
+ @Override
+ public ExceptionHandler getWrapped()
+ {
+ return delegate;
+ }
+
+ @Override
+ public void handle()
+ throws FacesException
+ {
+ // boolean redirectToErrorPage = false;
+ Throwable rootCause = null;
+ Iterator<ExceptionQueuedEvent> events = getUnhandledExceptionQueuedEvents().iterator();
+
+ // log each error
+ while (events.hasNext())
+ {
+ ExceptionQueuedEvent event = events.next();
+ // handle
+ try {
+
+ ExceptionQueuedEventContext source = (ExceptionQueuedEventContext) event.getSource();
+ FacesContext context = source.getContext();
+ Throwable t = source.getException();
+
+ // check t
+ if (t==null)
+ { log.error("Cannot handle exception. Exception not supplied with context!");
+ setErrorMessage(context, null);
+ continue;
+ }
+
+ // find root
+ rootCause = t.getCause();
+ // second option: getRootCause
+ if (rootCause == null)
+ { // get cause
+ rootCause = getRootCause(t);
+ }
+ // third option: use t
+ if (rootCause == null)
+ {
+ rootCause = t;
+ }
+
+ // Walk up the tree
+ while (true)
+ { // if its an empire-exception: game over
+ if (rootCause instanceof EmpireException)
+ break;
+ // has root cause
+ t = rootCause.getCause();
+ if (t==null)
+ break;
+ // yes, continue search
+ rootCause = t;
+ }
+
+ /*
+ if (rootCause instanceof org.icefaces.application.SessionExpiredException)
+ { // expired
+ log.info("Handling SessionExpiredException. No error message is set.");
+ continue;
+ }
+ */
+
+ // set message
+ String msg = "Handling exception of type "+rootCause.getClass().getSimpleName();
+ // log
+ // msg = appendSessionInfo(context, msg);
+ log.error(msg, rootCause);
+
+ // set message
+ if (!(rootCause instanceof EmpireException))
+ { // Wrap as internal exception
+ rootCause = new InternalException(rootCause);
+ }
+ setErrorMessage(context, rootCause);
+
+ } catch(Throwable t2) {
+ log.error("Failed to handle exception: "+t2.getMessage(), t2);
+ } finally {
+ events.remove();
+ }
+ }
+
+ // if an error has been found
+ /*
+ if (redirectToErrorPage)
+ {
+ HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
+ try
+ {
+ // put error to session map (will be cleared by ErrorPage)
+ if (rootCause != null)
+ {
+ FacesUtils.getFin2Session().setError(rootCause);
+ }
+ // redirect to error page
+ String errorPage = FacesUtils.getContextPath() + "/pages/error.iface";
+ Fin2ExceptionHandler.log.debug("Redirecting to error page at '" + errorPage + "'...");
+ FacesUtils.redirectDirectly(errorPage);
+ }
+ catch (Exception e)
+ {
+ Fin2ExceptionHandler.log.error("Error during exception handling.", e);
+ throw new FacesException(e);
+ }
+ }
+ */
+ // let next handler deal
+ getWrapped().handle();
+ }
+
+ /*
+ private String appendSessionInfo(FacesContext context, String msg)
+ {
+ // Provide session info
+ Fin2Session session = FinUtils.getFin2Session(context, false);
+ Fin2User user = (session!=null ? session.getUser() : null);
+ if (user!=null)
+ { // Add user information
+ String viewId;
+ if (context.getViewRoot()!=null)
+ viewId = context.getViewRoot().getViewId();
+ else {
+ viewId = "[NO VIEW]";
+ if (context.getExternalContext()!=null)
+ { // External Context
+ Object request = context.getExternalContext().getRequest();
+ if (request instanceof HttpServletRequest)
+ { // The HttpServletRequest Servlet Path
+ String path = ((HttpServletRequest)request).getServletPath();
+ viewId += " ("+String.valueOf(path)+")";
+ }
+ else if (request!=null)
+ viewId += " {"+request.getClass().getName()+"}";
+ }
+ }
+ // Message
+ msg += MessageFormat.format(" for User {0} of DUNS {1} on View {2}.", user.getUserId(), user.getDUNS(), viewId);
+ }
+ return msg;
+ }
+ */
+
+ private void setErrorMessage(FacesContext fContext, Throwable rootCause)
+ {
+ FacesMessage message = new FacesMessage();
+ message.setSeverity(FacesMessage.SEVERITY_ERROR);
+
+ Map<String, Object> sm = fContext.getExternalContext().getSessionMap();
+ if (sm.containsKey(SampleSession.MANAGED_BEAN_NAME))
+ {
+ TextResolver resolver = WebApplication.getInstance().getTextResolver(fContext);
+ if (rootCause instanceof Exception)
+ {
+ message.setSummary(resolver.getExceptionMessage((Exception)rootCause));
+ }
+ else
+ {
+ message.setSummary(resolver.resolveKey("global_general_error"));
+ }
+ // Add message
+ fContext.addMessage(null, message);
+ }
+ else
+ {
+ log.error("Unable to handle exception, redirecting to StartPage.", rootCause);
+ FacesUtils.redirectDirectly(fContext, SamplePages.LoginPage);
+ }
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AppExceptionHandlerFactory.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AppExceptionHandlerFactory.java
new file mode 100644
index 00000000..693cd344
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AppExceptionHandlerFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import jakarta.faces.context.ExceptionHandler;
+import jakarta.faces.context.ExceptionHandlerFactory;
+
+/**
+ * AppExceptionHandlerFactory
+ */
+public class AppExceptionHandlerFactory extends ExceptionHandlerFactory
+{
+ private final ExceptionHandlerFactory delegate;
+ public AppExceptionHandlerFactory(ExceptionHandlerFactory delegate)
+ {
+ // super(delegate); --- Use this in MyFaces 2.3.x
+ this.delegate = delegate;
+ }
+
+ @Override
+ public ExceptionHandlerFactory getWrapped()
+ {
+ return delegate;
+ }
+
+ @Override
+ public ExceptionHandler getExceptionHandler()
+ {
+ return new AppExceptionHandler(getWrapped().getExceptionHandler());
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AuthenticationPhaseListener.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AuthenticationPhaseListener.java
new file mode 100644
index 00000000..ae26fefb
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/AuthenticationPhaseListener.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.event.PhaseEvent;
+import jakarta.faces.event.PhaseId;
+import jakarta.faces.event.PhaseListener;
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.jakarta.app.FacesUtils;
+import org.apache.empire.jakarta.websample.web.pages.SamplePages;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AuthenticationPhaseListener implements PhaseListener
+{
+ private static final long serialVersionUID = 1L;
+ private static final Logger log = LoggerFactory.getLogger(AuthenticationPhaseListener.class);
+
+ private static final String LOGOUT_PARAM = "logout"; // must be ?logout=true
+// private static final String LANGUAGE_PARAM = "prefLanguage"; // must be ?logout=true
+
+ public AuthenticationPhaseListener()
+ {
+ }
+
+ @Override
+ public PhaseId getPhaseId()
+ {
+ return PhaseId.RESTORE_VIEW;
+ }
+
+ @Override
+ public void beforePhase(PhaseEvent pe)
+ {
+ // Check App
+ SampleApplication app = SampleUtils.getSampleApplication();
+ if (app == null)
+ {
+ log.error("Application not initialized!");
+ return;
+ }
+ try
+ {
+ FacesContext fc = pe.getFacesContext();
+ // Path Info
+ if (log.isDebugEnabled())
+ {
+ String path = fc.getExternalContext().getRequestServletPath();
+ log.debug("Restoring view for request path {}.", path);
+ }
+ checkAuthetication(fc, app);
+ }
+ catch (Exception e)
+ {
+ log.error("Authetication exception \"{}\" redirecting to error page!", e.getMessage(), e);
+ String errorPage = getPageUri(app.getSampleConfig().getAccessDeniedPage());
+ FacesUtils.redirectDirectly(pe.getFacesContext(), errorPage);
+ }
+ }
+
+ @Override
+ public void afterPhase(PhaseEvent event)
+ {
+
+ }
+
+ /******************************************************************************************/
+
+
+ private void checkAuthetication(FacesContext fc, SampleApplication app)
+ throws Exception
+ {
+ SampleSession session = SampleUtils.getSampleSession(fc);
+
+ if (!session.isAuthorized())
+ {
+ HttpServletRequest req = FacesUtils.getHttpRequest(fc);
+ String reqURI = req.getRequestURI();
+ int iSession = reqURI.indexOf(';');
+ if (iSession>0)
+ reqURI = reqURI.substring(0,iSession);
+
+ String loginPage = getPageUri(SamplePages.LoginPage.getOutcome().toString());
+ if (reqURI.equalsIgnoreCase(loginPage))
+ {
+ log.debug("Showing login page {}.", loginPage);
+ return;
+ }
+ FacesUtils.redirectDirectly(fc, SamplePages.LoginPage);
+ }
+ else
+ { // Check Logout
+ HttpServletRequest req = FacesUtils.getHttpRequest(fc);
+ Object logout = req.getParameter(LOGOUT_PARAM);
+ if (logout!=null && ObjectUtils.getBoolean(logout))
+ { // Perform logout
+ doLogout(fc);
+ // return to login page
+ String redirectPage = req.getRequestURI();
+ log.info("User logout performed. Returning to start page {}!", redirectPage);
+ FacesUtils.redirectDirectly(fc, redirectPage);
+ }
+ }
+
+
+ }
+
+ /******************************************************************************************/
+
+ private void doLogout(FacesContext fc)
+ {
+ fc.getExternalContext().invalidateSession();
+ }
+
+
+
+ private String getPageUri(String page)
+ {
+ if (StringUtils.isEmpty(page))
+ throw new InvalidArgumentException("page", page);
+ if (page.startsWith("http"))
+ return page;
+ if (page.startsWith("/")==false)
+ page="/"+page;
+ // Detect page uri
+ String pathPrefix = SampleUtils.getContextPath();
+ String pageUri = pathPrefix + page;
+ return pageUri;
+ }
+
+
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleAppStartupListener.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleAppStartupListener.java
new file mode 100644
index 00000000..30f18596
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleAppStartupListener.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import jakarta.servlet.ServletContext;
+
+import org.apache.empire.jakarta.app.FacesConfiguration;
+import org.apache.empire.jakarta.app.WebAppStartupListener;
+import org.apache.empire.jakarta.app.WebApplication;
+
+import jakarta.faces.context.FacesContext;
+
+/**
+ * Custom StartupListener
+ * Faces Configuration is done programmatically
+ * @author rainer
+ */
+public class SampleAppStartupListener extends WebAppStartupListener
+{
+ public final SampleConfig config = new SampleConfig();
+
+ public SampleAppStartupListener()
+ {
+ super(FacesConfiguration.class);
+ }
+
+ @Override
+ protected void initFacesConfiguration(FacesContext startupContext)
+ {
+ ServletContext servletContext = (ServletContext)startupContext.getExternalContext().getContext();
+ config.init(servletContext.getRealPath("WEB-INF/config.xml"));
+ // Load Configuration
+ super.initFacesConfiguration(startupContext);
+ }
+
+ @Override
+ protected void initWebApplication(WebApplication facesApp, FacesContext startupContext)
+ {
+ // Set Configuration
+ ((SampleApplication)facesApp).setConfig(config);
+ // init now
+ super.initWebApplication(facesApp, startupContext);
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleApplication.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleApplication.java
new file mode 100644
index 00000000..de8f119d
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleApplication.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.time.LocalDate;
+import java.util.Locale;
+
+import jakarta.servlet.ServletContext;
+import javax.sql.DataSource;
+
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.db.DBContext;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBRecord;
+import org.apache.empire.db.context.DBContextStatic;
+import org.apache.empire.db.context.DBRollbackManager;
+import org.apache.empire.db.context.DBRollbackManager.ReleaseAction;
+import org.apache.empire.dbms.DBMSHandler;
+import org.apache.empire.jakarta.app.WebApplication;
+import org.apache.empire.jakarta.controls.InputControlManager;
+import org.apache.empire.jakarta.custom.controls.FileInputControl;
+import org.apache.empire.jakarta.websample.db.SampleDB;
+import org.apache.empire.jakarta.websample.db.SampleDBwithMeta;
+import org.apache.empire.jakarta.websample.web.pages.SamplePages;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SampleApplication extends WebApplication {
+ // Logger
+ private static final Logger log = LoggerFactory
+ .getLogger(SampleApplication.class);
+
+ protected static final String MANAGED_BEAN_NAME = "sampleApplication";
+ protected static final Locale DEFAULT_LOCALE = Locale.ENGLISH;
+ // Non-Static
+ private SampleConfig config = null; // SampleAppStartupListener.config;
+ private SampleDB sampleDB = null;
+
+ private Connection conn = null;
+
+ public static SampleApplication get() {
+ return (SampleApplication)WebApplication.getInstance();
+ }
+
+ public SampleApplication() {
+
+ // trace
+ SampleApplication.log.trace("SampleApplication created");
+ }
+
+ /**
+ * Sets the Configuration
+ * Called only from SampleAppStartupListener
+ */
+ void setConfig(SampleConfig config)
+ {
+ this.config = config;
+ }
+
+ @Override
+ public void init(ServletContext servletContext) {
+ // register all controls
+ // InputControlManager.registerControl("myType", new
+ // MyTypeInputControl());
+
+ // Get a JDBC Connection
+ log.info("*** getJDBCConnection() ***");
+ conn = getJDBCConnection(servletContext);
+
+ log.info("*** initDatabase() ***");
+ initDatabase();
+
+ log.info("*** initControls() ***");
+ initControls();
+
+ log.info("*** initPages() ***");
+ initPages(servletContext);
+
+ // Set Database to Servlet Context
+ servletContext.setAttribute("db", sampleDB);
+
+ // Done
+ }
+
+ /**
+ * initializes JSF control components
+ */
+ private void initControls()
+ {
+ // register custom control types
+ InputControlManager.registerControl(new FileInputControl());
+ }
+
+ /*
+ * getJDBCConnection
+ */
+ private Connection getJDBCConnection(ServletContext appContext)
+ {
+ // Establish a new database connection
+ Connection conn = null;
+ String jdbcURL = config.getJdbcURL();
+ if (jdbcURL.indexOf("file:") > 0)
+ jdbcURL = StringUtils.replace(jdbcURL, "file:", "file:" + appContext.getRealPath("/"));
+ // Connect
+ log.info("Connecting to Database'" + jdbcURL + "' / User=" + config.getJdbcUser());
+ try
+ { // Connect to the database
+ Class.forName(config.getJdbcClass()).newInstance();
+ conn = DriverManager.getConnection(jdbcURL, config.getJdbcUser(), config.getJdbcPwd());
+ log.info("Connected successfully");
+ // set the AutoCommit to false this session. You must commit
+ // explicitly now
+ conn.setAutoCommit(false);
+ log.info("AutoCommit is " + conn.getAutoCommit());
+
+ }
+ catch (Exception e)
+ {
+ log.error("Failed to connect directly to '" + config.getJdbcURL() + "' / User=" + config.getJdbcUser());
+ log.error(e.toString());
+ throw new RuntimeException(e);
+ }
+ return conn;
+ }
+
+ private void initDatabase() {
+ // Create Database with Metadata!
+ sampleDB = new SampleDBwithMeta();
+
+ // Open Database (and create if not existing)
+ DBMSHandler dbmsHandler = getDBMSHandler(config.getDatabaseProvider(), conn);
+ log.info("Opening database '{}' using '{}'", sampleDB.getClass().getSimpleName(), dbmsHandler.getClass().getSimpleName());
+ Connection conn = null;
+ DBContext context = null;
+ try {
+ conn = getConnection(sampleDB);
+ context = new DBContextStatic(dbmsHandler, conn);
+ sampleDB.open(context);
+ // check if database was just created
+ DBCommand cmd = context.createCommand();
+ cmd.select(sampleDB.EMPLOYEES.count());
+ if (context.getUtils().querySingleInt(cmd)==0)
+ { // Populate Database
+ populateDatabase(context);
+ }
+ } finally {
+ context.discard();
+ releaseConnection(conn, true, null);
+ }
+ }
+
+ /**
+ * Creates an Empire-db DatabaseDriver for the given provider and applies dbms specific configuration
+ */
+ private DBMSHandler getDBMSHandler(String provider, Connection conn)
+ {
+ try
+ { // Get Driver Class Name
+ String dbmsHandlerClass = config.getDbmsHandlerClass();
+ if (StringUtils.isEmpty(dbmsHandlerClass))
+ throw new RuntimeException("Configuration error: Element 'dbmsHandlerClass' not found in node 'properties-"+provider+"'");
+
+ // Create dbms
+ DBMSHandler dbms = (DBMSHandler) Class.forName(dbmsHandlerClass).newInstance();
+
+ // Configure dbms
+ config.readProperties(dbms, "properties-"+provider, "dbmsHandlerProperties");
+
+ // done
+ return dbms;
+
+ } catch (Exception e)
+ { // catch any checked exception and forward it
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void initPages(ServletContext sc) {
+ // register Page Beans
+ new SamplePages().registerPageBeans(this.getFacesImplementation());
+ }
+
+ public SampleDB getDatabase() {
+ return sampleDB;
+ }
+
+ /*
+ * creates a DDL Script for the entire SampleDB Database then checks if the
+ * department table exists by running "select count(*) from DEPARTMENTS" if
+ * the department tables does not exist, the entire dll-script is executed
+ * line by line
+ */
+ private void populateDatabase(DBContext context) {
+ // Insert Sample Departments
+ insertDepartmentSampleRecord(context, "Procurement", "ITTK");
+ int idDevDep = insertDepartmentSampleRecord(context, "Development", "ITTK");
+ int idSalDep = insertDepartmentSampleRecord(context, "Sales", "ITTK");
+ // Insert Sample Employees
+ insertEmployeeSampleRecord(context, "Mr.", "Eugen", "Miller", "M", 30000, idDevDep);
+ insertEmployeeSampleRecord(context, "Mr.", "Max", "Mc. Callahan", "M", 22500, idDevDep);
+ insertEmployeeSampleRecord(context, "Mrs.", "Anna", "Smith", "F", 44250, idSalDep);
+ // Commit
+ context.commit();
+ }
+
+ /*
+ * Insert a department
+ */
+ private int insertDepartmentSampleRecord(DBContext context, String department_name, String businessUnit) {
+ // Insert a Department
+ DBRecord rec = new DBRecord(context, sampleDB.DEPARTMENTS);
+ rec.create()
+ .set(sampleDB.DEPARTMENTS.NAME, department_name)
+ .set(sampleDB.DEPARTMENTS.BUSINESS_UNIT, businessUnit)
+ .update();
+ // Return Department ID
+ return rec.getInt(sampleDB.DEPARTMENTS.ID);
+ }
+
+ /*
+ * Insert a person
+ */
+ private int insertEmployeeSampleRecord(DBContext context, String salutation, String firstName, String lastName, String gender, int salary, int depID) {
+ // Insert an Employee
+ SampleDB.TEmployees EMP = sampleDB.EMPLOYEES;
+ DBRecord rec = new DBRecord(context, sampleDB.EMPLOYEES);
+ rec.create()
+ .set(EMP.SALUTATION, salutation)
+ .set(EMP.FIRST_NAME, firstName)
+ .set(EMP.LAST_NAME, lastName)
+ .set(EMP.GENDER, gender)
+ .set(EMP.DEPARTMENT_ID, depID)
+ .set(EMP.SALARY, salary)
+ .update();
+ // insert Payments
+ insertPayments(rec);
+ // Return Employee ID
+ return rec.getInt(sampleDB.EMPLOYEES.ID);
+ }
+
+ /**
+ * <PRE>
+ * Inserts an Payments for a particular Employee
+ * </PRE>
+ */
+ private void insertPayments(DBRecord employee)
+ {
+ if (employee.isNull(sampleDB.EMPLOYEES.SALARY))
+ return; // No salary
+ // monthlySalary
+ BigDecimal monthlySalary = employee.getDecimal(sampleDB.EMPLOYEES.SALARY).divide(new BigDecimal(12), 2, RoundingMode.HALF_UP);
+ // Insert an Employee
+ LocalDate date = LocalDate.now();
+ date = date.minusDays(date.getDayOfMonth()-1); // first day of this month
+ // Add Payment for each month
+ SampleDB.TPayments PAY = sampleDB.PAYMENTS;
+ DBRecord rec = new DBRecord(employee.getContext(), PAY);
+ int months = (int)(Math.random()*6)+11;
+ for (LocalDate month=date.minusMonths(months); !month.isAfter(date); month=month.plusMonths(1))
+ {
+ BigDecimal variation = new BigDecimal((Math.random()*200) - 100.0);
+ variation = variation.setScale(2, RoundingMode.HALF_UP);
+ // insert
+ rec.create(DBRecord.key(employee.getIdentity(), month.getYear(), month.getMonth()));
+ rec.set(PAY.AMOUNT, monthlySalary.add(variation));
+ rec.update();
+ }
+ }
+
+ @Override
+ /**
+ * returns null as connection pooling is not used
+ */
+ protected DataSource getAppDataSource(DBDatabase db) {
+ return null;
+ }
+
+ /**
+ * Usually returns a connection from the connection pool
+ * As the sample does not use connection pooling, a static connection is returned
+ * @return a connection for the requested db
+ */
+ @Override
+ protected Connection getConnection(DBDatabase db)
+ {
+ return conn;
+ }
+
+ /**
+ * Usually releases a connection from the connection pool
+ * As the sample does not use connection pooling, only a commit or rollback is performed
+ */
+ @Override
+ protected void releaseConnection(Connection conn, boolean commit, DBRollbackManager dbrm)
+ {
+ try
+ { // release connection
+ if (conn == null)
+ {
+ return;
+ }
+ log.trace("releasing Connection {}", conn.hashCode());
+ // Commit or rollback connection depending on the exit code
+ if (commit)
+ { // success: commit all changes
+ if (dbrm!=null)
+ dbrm.releaseConnection(conn, ReleaseAction.Discard); // before commit
+ conn.commit();
+ log.debug("REQUEST commited.");
+ }
+ else
+ { // failure: rollback all changes
+ conn.rollback();
+ if (dbrm!=null)
+ dbrm.releaseConnection(conn, ReleaseAction.Rollback); // after rollback
+ log.debug("REQUEST rolled back.");
+ }
+ // Release Connection (don't do that here!)
+ // conn.close();
+ // done
+ if (log.isDebugEnabled())
+ log.debug("Connection released but not closed.");
+ }
+ catch (SQLException e)
+ {
+ log.error("Error releasing connection", e);
+ e.printStackTrace();
+ }
+ }
+
+ protected SampleConfig getSampleConfig() {
+ if (config == null) {
+ SampleApplication.log
+ .error("Application configuration not initialized!");
+ }
+ return config;
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleConfig.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleConfig.java
new file mode 100644
index 00000000..0105c7c3
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleConfig.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import org.apache.empire.xml.XMLConfiguration;
+import org.apache.empire.xml.XMLUtil;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+
+public class SampleConfig extends XMLConfiguration
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(SampleConfig.class);
+
+ private String loggingNodeName = "log4j:configuration";
+
+ private String jndiContext = "";
+
+ private String databaseProvider = "oracle";
+
+ private String jdbcClass = "oracle.jdbc.driver.OracleDriver";
+
+ private String jdbcURL = "jdbc:oracle:thin:@192.168.0.2:1521:ora10";
+
+ private String dataSource = "java:comp/env/jdbc/sampleDataSource";
+
+ private String jdbcUser = "DBSAMPLE";
+
+ private String jdbcPwd = "DBSAMPLE";
+
+ private String dbmsHandlerClass = "";
+
+ /**
+ * Initialize the configuration
+ *
+ * @param filename the file to read
+ *
+ * @return true on success
+ */
+ public boolean init(String filename)
+ {
+ try
+ { // init
+ super.init(filename, false);
+ // Init Logging
+ if (initLogging() == false)
+ return false;
+ // Read config
+ log.info("*** init Configuration ***");
+ log.info("Config file is '{}'", filename);
+ // Read the properties
+ readProperties(this, "properties");
+ readProperties(this, "properties-" + databaseProvider);
+ } catch (Exception e)
+ {
+ log.error(e.getLocalizedMessage());
+ return false;
+ }
+ return true;
+ }
+
+ public String getDatabaseProvider()
+ {
+ return databaseProvider;
+ }
+
+ public String getJdbcClass()
+ {
+ return jdbcClass;
+ }
+
+ public void setJdbcClass(String jdbcClass)
+ {
+ this.jdbcClass = jdbcClass;
+ }
+
+ public String getJdbcPwd()
+ {
+ return jdbcPwd;
+ }
+
+ public void setJdbcPwd(String jdbcPwd)
+ {
+ this.jdbcPwd = jdbcPwd;
+ }
+
+ public String getJdbcURL()
+ {
+ return jdbcURL;
+ }
+
+ public String getDbmsHandlerClass()
+ {
+ return dbmsHandlerClass;
+ }
+
+ // ------- Setters -------
+
+ public void setDatabaseProvider(String databaseProvider)
+ {
+ this.databaseProvider = databaseProvider;
+ }
+
+ public void setJdbcURL(String jdbcURL)
+ {
+ this.jdbcURL = jdbcURL;
+ }
+
+ public String getJdbcUser()
+ {
+ return jdbcUser;
+ }
+
+ public void setJdbcUser(String jdbcUser)
+ {
+ this.jdbcUser = jdbcUser;
+ }
+
+ public void setDbmsHandlerClass(String dbmsHandlerClass)
+ {
+ this.dbmsHandlerClass = dbmsHandlerClass;
+ }
+
+ /**
+ * Init logging using Log4J's DOMConfigurator
+ *
+ * @return
+ */
+ private boolean initLogging()
+ {
+ // Get configuration root node
+ Element rootNode = getRootNode();
+ if (rootNode == null)
+ {
+ return false;
+ }
+ // Find log configuration node
+ Element loggingNode = XMLUtil.findFirstChild(rootNode, loggingNodeName);
+ if (loggingNode == null)
+ { // log configuration node not found
+ SampleConfig.log.error("Log configuration node {} has not been found. Logging has not been configured.", loggingNodeName);
+ return false;
+ }
+ // Init Log4J
+ DOMConfigurator.configure(loggingNode);
+ // done
+ SampleConfig.log.info("Logging successfully configured from node {}.", loggingNodeName);
+ return true;
+ }
+
+ public String getDataSourceName()
+ {
+ return dataSource;
+ }
+
+ public void setDataSourceName(String dataSource)
+ {
+ this.dataSource = dataSource;
+ }
+
+
+ public String getJndiContextFactoryName()
+ {
+ return jndiContext;
+ }
+
+ public void setJndiContextFactoryName(String jndiContext)
+ {
+ this.jndiContext = jndiContext;
+ }
+
+ public String getAccessDeniedPage()
+ {
+ return null;
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleContext.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleContext.java
new file mode 100644
index 00000000..95aa92d6
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleContext.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import jakarta.faces.context.FacesContext;
+
+import org.apache.empire.commons.ClassUtils;
+import org.apache.empire.jakarta.app.WebDBContext;
+import org.apache.empire.jakarta.websample.db.SampleDB;
+
+/**
+ * This is an example for a custom DBContext extension
+ * @author rainer
+ */
+public class SampleContext extends WebDBContext<SampleDB>
+{
+ private static final long serialVersionUID = 1L;
+
+ private final transient SampleSession session;
+
+ /**
+ * Custom serialization for transient fields.
+ *
+ */
+ private void writeObject(ObjectOutputStream strm) throws IOException
+ { // write the object
+ strm.defaultWriteObject();
+ }
+
+ /**
+ * Custom deserialization for transient fields.
+ */
+ private void readObject(ObjectInputStream strm)
+ throws IOException, ClassNotFoundException
+ { // Restore Session
+ FacesContext fc = FacesContext.getCurrentInstance();
+ SampleSession session = SampleUtils.getSampleSession(fc);
+ ClassUtils.setPrivateFieldValue(SampleContext.class, this, "session", session);
+ // Read the object
+ strm.defaultReadObject();
+ }
+
+ /**
+ * Constructs a SampleContext
+ */
+ public SampleContext(SampleDB db, SampleSession session)
+ {
+ super(db);
+ // the session
+ this.session = session;
+ }
+
+ public SampleUser getUser()
+ {
+ return session.getUser();
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleSession.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleSession.java
new file mode 100644
index 00000000..3725931b
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleSession.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SampleSession // *Deprecated* implements Serializable
+{
+ // *Deprecated* private static final long serialVersionUID = 1L;
+
+ private static final Logger log = LoggerFactory.getLogger(SampleSession.class);
+
+ protected static final String MANAGED_BEAN_NAME = "sampleSession";
+
+ private SampleUser user = null;
+
+ private String page = "loginPage";
+
+ private Throwable rootCause;
+
+ public SampleSession()
+ {
+ SampleSession.log.info("SESSION: created.");
+ }
+
+ public void setPage(String page)
+ {
+ this.page = page;
+ }
+
+ public String getPage()
+ {
+ return this.page;
+ }
+
+ public void setError(Throwable rootCause)
+ {
+ this.rootCause = rootCause;
+ }
+
+ public Throwable getError()
+ {
+ return this.rootCause;
+ }
+
+ public boolean isAuthorized()
+ {
+ return user != null;
+ }
+
+ public SampleUser getUser()
+ {
+ return user;
+ }
+
+ public void setUser(SampleUser user)
+ {
+ this.user = user;
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleUser.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleUser.java
new file mode 100644
index 00000000..a9ef8160
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleUser.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import java.util.Locale;
+
+public class SampleUser // *Deprecated* implements Serializable
+{
+ // *Deprecated* private static final long serialVersionUID = 1L;
+
+ protected static final String MANAGED_BEAN_NAME = "sampleUser";
+
+ private String userName = "test";
+ private String password = "";
+ private Locale language = SampleApplication.DEFAULT_LOCALE;
+
+ public SampleUser()
+ {
+ }
+
+ public String getPassword()
+ {
+ return password;
+ }
+
+ public String getUserName()
+ {
+ return userName;
+ }
+
+ public void setUserName(String name)
+ {
+ this.userName = name;
+ }
+
+ public void setPassword(String password)
+ {
+ this.password = password;
+ }
+
+ public Locale getLocale()
+ {
+ return this.language;
+ }
+
+ public void setLocale(Locale locale)
+ {
+ this.language = locale;
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleUtils.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleUtils.java
new file mode 100644
index 00000000..9991f7b1
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/SampleUtils.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web;
+
+import jakarta.faces.context.FacesContext;
+
+import org.apache.empire.jakarta.app.FacesUtils;
+import org.apache.empire.jakarta.utils.ParameterMap;
+import org.apache.empire.jakarta.websample.db.SampleDB;
+
+public class SampleUtils extends FacesUtils
+{
+ public static SampleApplication getSampleApplication()
+ {
+ return SampleApplication.get();
+ }
+
+ public static SampleDB getDatabase()
+ {
+ return getSampleApplication().getDatabase();
+ }
+
+ public static SampleUser getSampleUser()
+ {
+ FacesContext fc = getContext();
+ return (SampleUser) getManagedBean(fc, SampleUser.MANAGED_BEAN_NAME);
+ }
+
+ public static SampleSession getSampleSession(FacesContext fc)
+ {
+ return (SampleSession) getManagedBean(fc, SampleSession.MANAGED_BEAN_NAME);
+ }
+
+ public static SampleSession getSampleSession()
+ {
+ return getSampleSession(FacesContext.getCurrentInstance());
+ }
+
+ public static ParameterMap getParameterMap()
+ {
+ return getParameterMap(getContext());
+ }
+
+ public static String getContextPath()
+ {
+ return FacesContext.getCurrentInstance().getExternalContext().getRequestContextPath();
+ }
+
+ public static Object getRequestAttribute(final String key)
+ {
+ FacesContext fc = getContext();
+ return getRequestAttribute(fc, key);
+ }
+
+ public static void setRequestAttribute(final String key, Object value)
+ {
+ FacesContext fc = getContext();
+ setRequestAttribute(fc, key, value);
+ }
+
+ public static <T> T getManagedBean(final Class<T> cls)
+ {
+ FacesContext fc = getContext();
+ return getManagedBean(fc, cls);
+ }
+
+ /* file */
+
+ public static String getRealPath(String path)
+ {
+ FacesContext fc = getContext();
+ return getRealPath(fc, path);
+ }
+
+ public static String getFilePath(String path, String file)
+ {
+ FacesContext fc = getContext();
+ return getFilePath(fc, path, file);
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/objects/EmployeeSearchFilter.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/objects/EmployeeSearchFilter.java
new file mode 100644
index 00000000..0ecab457
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/objects/EmployeeSearchFilter.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.objects;
+
+
+public class EmployeeSearchFilter // *Deprecated* implements Serializable
+{
+ // *Deprecated* private static final long serialVersionUID = 1L;
+
+ private String employeeId;
+ private String firstName;
+ private String lastName;
+ private String departmentId;
+ private String gender;
+
+ public EmployeeSearchFilter()
+ {
+ resetFilter();
+ }
+
+ public void resetFilter()
+ {
+ employeeId = "";
+ firstName = "";
+ lastName = "";
+ departmentId = "";
+ gender = "";
+ }
+
+ /**
+ * Additional getter for addSearchConstraint(queryCmd, EMP.ID, filter);
+ * @return the employeeId
+ */
+ public String getId()
+ {
+ return employeeId;
+ }
+
+ public String getEmployeeId()
+ {
+ return employeeId;
+ }
+
+ public void setEmployeeId(String employeeId)
+ {
+ this.employeeId = employeeId;
+ }
+
+ public String getDepartmentId()
+ {
+ return departmentId;
+ }
+
+ public void setDepartmentId(String departmentId)
+ {
+ this.departmentId = departmentId;
+ }
+
+ public String getFirstName()
+ {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName)
+ {
+ this.firstName = firstName;
+ }
+
+ public String getLastName()
+ {
+ return lastName;
+ }
+
+ public void setLastName(String lastName)
+ {
+ this.lastName = lastName;
+ }
+
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/objects/User.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/objects/User.java
new file mode 100644
index 00000000..0c6b3437
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/objects/User.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.objects;
+
+
+public class User // *Deprecated* implements Serializable
+{
+ // *Deprecated* private static final long serialVersionUID = 1L;
+
+ private String name = "";
+
+ private String password = "";
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public String getPassword()
+ {
+ return password;
+ }
+
+ public void setPassword(String password)
+ {
+ this.password = password;
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/EmployeeDetailPage.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/EmployeeDetailPage.java
new file mode 100644
index 00000000..eb6df0a6
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/EmployeeDetailPage.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.pages;
+
+import java.util.List;
+
+import jakarta.faces.event.ActionEvent;
+
+import org.apache.empire.commons.ClassUtils;
+import org.apache.empire.data.list.DataListEntry;
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.jakarta.pageelements.RecordPageElement;
+import org.apache.empire.jakarta.pages.PageOutcome;
+import org.apache.empire.jakarta.websample.db.SampleDB;
+import org.apache.empire.jakarta.websample.db.records.EmployeeRecord;
+import org.apache.empire.jakarta.websample.web.SampleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EmployeeDetailPage extends SamplePage
+{
+
+ private static final Logger log = LoggerFactory.getLogger(EmployeeDetailPage.class);
+
+ private String idParam;
+
+ private RecordPageElement<EmployeeRecord> employee;
+
+ private int activeTab = 0;
+
+ private List<DataListEntry> payments;
+
+ public EmployeeDetailPage()
+ {
+ log.trace("EmployeeDetailPage created");
+
+ EmployeeRecord emplRec = new EmployeeRecord(getSampleContext());
+ employee = new RecordPageElement<EmployeeRecord>(this, emplRec.getTable(), emplRec);
+ }
+
+ public String getIdParam()
+ {
+ return this.idParam;
+ }
+
+ public void setIdParam(String idParam)
+ {
+ log.info("EmployeeDetailPage idParam = {}.", idParam);
+ this.idParam = idParam;
+ }
+
+ public RecordPageElement<EmployeeRecord> getEmployee()
+ {
+ return employee;
+ }
+
+ public EmployeeRecord getEmployeeRecord()
+ {
+ return employee.getRecord();
+ }
+
+ public List<DataListEntry> getPayments()
+ {
+ return payments;
+ }
+
+ public int getActiveTab()
+ {
+ return activeTab;
+ }
+
+ public void setActiveTab(int activeTab)
+ {
+ this.activeTab = activeTab;
+ }
+
+ @Override
+ public void doInit()
+ { // Notify Elements
+ if (!employee.getRecord().isValid())
+ {
+ employee.reloadRecord();
+ // Load payment data
+ loadPaymentData();
+ }
+ }
+
+ public void doLoad()
+ {
+ log.info("EmployeeDetailPage Loading entryId {}.", this.idParam);
+ // load the record
+ this.employee.loadRecord(this.idParam);
+ // Load payment data
+ loadPaymentData();
+ }
+
+ public void doCreate()
+ {
+ // use create(null) to defer primaryKey generation
+ getEmployeeRecord().create(null);
+ doRefresh();
+ }
+
+ public PageOutcome doSave()
+ {
+ getEmployeeRecord().update();
+
+ /* test transaction
+ SampleDB db = this.getDatabase();
+ if (getEmployeeRecord().isNull(db.T_EMPLOYEES.PHONE_NUMBER))
+ throw new MiscellaneousErrorException("Phone number must not be empty!");
+ */
+
+ return getParentOutcome(true);
+ }
+
+ public void doTestSerialization(ActionEvent ae)
+ {
+ /* test serialization */
+ EmployeeRecord before = getEmployeeRecord();
+ EmployeeRecord after = ClassUtils.testSerialization(EmployeeRecord.class, before);
+
+ addInfoMessage("!employeeDetail_testMessage");
+ addInfoMessage("Rowset is {0}", (after.getRowSet() ==before.getRowSet()) ? "Same" : "Different");
+ addInfoMessage("Context is {0}", (after.getContext()==before.getContext()) ? "Same" : "Different");
+ after.read(before.getKey());
+
+ addInfoMessage("!global_status", after.isValid() ? "!global_valid" : "!global_invalid");
+ }
+
+ public PageOutcome doDelete()
+ {
+ getEmployeeRecord().delete();
+ return getParentOutcome(true);
+ }
+
+ public PageOutcome doCancel()
+ {
+ return getParentOutcome(true);
+ }
+
+ public void onTabChanged(int newPage)
+ {
+ log.debug("onTabChanged " + newPage);
+ }
+
+ private void loadPaymentData()
+ {
+ SampleContext context = getSampleContext();
+ SampleDB db = this.getDatabase();
+ SampleDB.TPayments PAY = db.PAYMENTS;
+
+ DBCommand cmd = context.createCommand();
+ cmd.select(PAY.YEAR, PAY.MONTH, PAY.AMOUNT);
+ cmd.where(PAY.EMPLOYEE_ID.is(employee.getRecord().getIdentity()));
+ cmd.orderBy(PAY.YEAR.desc(), PAY.MONTH.desc());
+
+ this.payments = context.getUtils().queryDataList(cmd);
+ log.info("{} payments have been loaded", this.payments.size());
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/EmployeeListPage.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/EmployeeListPage.java
new file mode 100644
index 00000000..643c33e1
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/EmployeeListPage.java
@@ -0,0 +1,267 @@
+/*
+
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.pages;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.Date;
+
+import org.apache.commons.beanutils.PropertyUtils;
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.Options;
+import org.apache.empire.db.DBColumn;
+import org.apache.empire.db.DBColumnExpr;
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.exceptions.BeanPropertyGetException;
+import org.apache.empire.jakarta.pageelements.BeanListPageElement;
+import org.apache.empire.jakarta.pageelements.ListPageElement;
+import org.apache.empire.jakarta.pageelements.ListPageElement.ParameterizedItem;
+import org.apache.empire.jakarta.websample.db.SampleDB.Gender;
+import org.apache.empire.jakarta.websample.db.SampleDB.TDepartments;
+import org.apache.empire.jakarta.websample.db.SampleDB.TEmployees;
+import org.apache.empire.jakarta.websample.web.SampleUtils;
+import org.apache.empire.jakarta.websample.web.objects.EmployeeSearchFilter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class EmployeeListPage extends SamplePage
+{
+
+ private static final Logger log = LoggerFactory.getLogger(EmployeeListPage.class);
+
+ private BeanListPageElement<EmployeeListEntry> employees;
+
+ public static class EmployeeListEntry extends ListPageElement.SelectableItem implements ParameterizedItem
+ {
+ // *Deprecated* private static final long serialVersionUID = 1L;
+
+ private int id; // employeeId;
+ private String name;
+ private Gender gender;
+ private Date dateOfBirth;
+ private String department;
+ private boolean retired;
+ private String idParam;
+
+ /**
+ * Implements ParameterizedItem.
+ * Used to uniquely identify this entry for selection and navigation
+ */
+ @Override
+ public String getIdParam()
+ {
+ return this.idParam;
+ }
+
+ /**
+ * Implements ParameterizedItem.
+ * This will automatically set the item idParam for navigation
+ */
+ @Override
+ public void setIdParam(String idParam)
+ {
+ this.idParam = idParam;
+ }
+
+ public int getId()
+ {
+ return id;
+ }
+
+ public void setId(int id)
+ {
+ this.id = id;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+
+ public Gender getGender()
+ {
+ return gender;
+ }
+
+ public void setGender(Gender gender)
+ {
+ this.gender = gender;
+ }
+
+ public Date getDateOfBirth()
+ {
+ return dateOfBirth;
+ }
+
+ public void setDateOfBirth(Date dateOfBirth)
+ {
+ this.dateOfBirth = dateOfBirth;
+ }
+
+ public String getDepartment()
+ {
+ return department;
+ }
+
+ public void setDepartment(String department)
+ {
+ this.department = department;
+ }
+
+ public boolean isRetired() {
+ return retired;
+ }
+
+ public void setRetired(boolean retired) {
+ this.retired = retired;
+ }
+
+ }
+
+ public EmployeeListPage()
+ {
+ EmployeeListPage.log.trace("EmployeeListPage created");
+ TEmployees EMP = getDatabase().EMPLOYEES;
+
+ // create the Employees List page element
+ employees = new BeanListPageElement<EmployeeListEntry>(this, EmployeeListEntry.class, getSampleContext(), EMP.ID);
+ }
+
+
+ public EmployeeSearchFilter getSearchFilter()
+ {
+ return SampleUtils.getManagedBean(EmployeeSearchFilter.class);
+ }
+
+ public ListPageElement<EmployeeListEntry> getEmployees()
+ {
+ return employees;
+ }
+
+ /*** Action Section ***/
+
+ @Override
+ public void doInit()
+ { // Notify Elements
+ super.doInit();
+ }
+
+ public void doResetSearch()
+ {
+ getSearchFilter().resetFilter();
+ this.employees.clearItems();
+ }
+
+ public void doSearch()
+ {
+ TDepartments DEP = getDatabase().DEPARTMENTS;
+ TEmployees EMP = getDatabase().EMPLOYEES;
+
+ DBColumnExpr FULL_NAME = EMP.LAST_NAME.append(", ").append(EMP.FIRST_NAME).as("NAME");
+ DBColumnExpr DEPARTMENT = DEP.NAME.as("DEPARTMENT");
+
+ DBCommand queryCmd = createQueryCommand();
+
+ queryCmd.select(EMP.ID, FULL_NAME);
+ queryCmd.select(EMP.GENDER, EMP.DATE_OF_BIRTH, EMP.RETIRED);
+ // queryCmd.select(EMP.RETIRED.decode(true, "X", "-"));
+ queryCmd.select(DEPARTMENT);
+
+ queryCmd.join(DEP.ID, EMP.DEPARTMENT_ID);
+ queryCmd.orderBy(EMP.FIRST_NAME);
+
+ addAllConstraints(queryCmd);
+
+ employees.initItems(queryCmd);
+ }
+
+
+ public Options getDepartmentOptions()
+ {
+ TDepartments DEP = getDatabase().DEPARTMENTS;
+
+ DBCommand queryCmd = createQueryCommand();
+ queryCmd.select(DEP.ID, DEP.NAME);
+
+ return getSampleContext().getUtils().queryOptionList(queryCmd);
+ }
+
+
+ protected void addAllConstraints(DBCommand queryCmd)
+ {
+ TEmployees EMP = getDatabase().EMPLOYEES;
+ EmployeeSearchFilter filter = getSearchFilter();
+
+ addSearchConstraint(queryCmd, EMP.ID, filter);
+ addSearchConstraint(queryCmd, EMP.FIRST_NAME, filter);
+ addSearchConstraint(queryCmd, EMP.LAST_NAME, filter);
+ addSearchConstraint(queryCmd, EMP.GENDER, filter);
+ addSearchConstraint(queryCmd, EMP.DEPARTMENT_ID, filter);
+ }
+
+ private void addSearchConstraint(DBCommand cmd, DBColumn col, Object bean)
+ {
+ Object value;
+ try
+ {
+ value = PropertyUtils.getProperty(bean, col.getBeanPropertyName());
+ if (ObjectUtils.isEmpty(value))
+ return;
+ // is it an array
+ if (value instanceof Collection<?> || value.getClass().isArray())
+ {
+ cmd.where(col.in(value));
+ return;
+ }
+ // text
+ if (col.getOptions() == null && col.getDataType().isText())
+ {
+ StringBuilder b = new StringBuilder();
+ b.append("%");
+ b.append(((String) value).toUpperCase());
+ b.append("%");
+ cmd.where(col.upper().like(b.toString()));
+ return;
+ }
+ // value
+ cmd.where(col.is(value));
+ return;
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new BeanPropertyGetException(bean, col.getBeanPropertyName(), e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new BeanPropertyGetException(bean, col.getBeanPropertyName(), e);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new BeanPropertyGetException(bean, col.getBeanPropertyName(), e);
+ }
+ }
+
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/ErrorPage.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/ErrorPage.java
new file mode 100644
index 00000000..ead91891
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/ErrorPage.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.pages;
+
+
+public class ErrorPage extends SamplePage
+{
+
+ private boolean showDetails = false;
+
+ public boolean isShowDetails()
+ {
+ return this.showDetails;
+ }
+
+ public void setShowDetails(boolean showDetails)
+ {
+ this.showDetails = showDetails;
+ }
+
+ public void toggleShowDetails()
+ {
+ this.showDetails = !this.showDetails;
+ }
+
+// public String getMessage()
+// {
+// if (getSampleSession().getError() != null)
+// {
+// return getSampleSession().getError().getMessage();
+// }
+// return "NULL";
+// }
+//
+// public String getType()
+// {
+// if (getSampleSession().getError() != null)
+// {
+// return getSampleSession().getError().getClass().getName();
+// }
+// return "NULL";
+// }
+
+// public List<String> getTrace()
+// {
+// List<String> trace = new ArrayList<String>();
+// if (getSampleSession().getError() != null && getSampleSession().getError().getStackTrace() != null)
+// {
+// for (StackTraceElement element : getSampleSession().getError().getStackTrace())
+// {
+// trace.add(element.toString());
+// }
+// }
+// return trace;
+// }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/LoginPage.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/LoginPage.java
new file mode 100644
index 00000000..778ec729
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/LoginPage.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.pages;
+
+import java.util.Locale;
+
+import org.apache.empire.commons.Options;
+import org.apache.empire.jakarta.app.FacesUtils;
+import org.apache.empire.jakarta.pages.PageOutcome;
+import org.apache.empire.jakarta.websample.web.SampleSession;
+import org.apache.empire.jakarta.websample.web.SampleUtils;
+
+public class LoginPage extends SamplePage {
+
+ private static final Options languageOptions;
+
+ static {
+ languageOptions = new Options();
+ languageOptions.set(Locale.US, "English");
+ languageOptions.set(Locale.GERMAN, "German");
+ }
+
+ public Options getLanguageOptions()
+ {
+ return languageOptions;
+ }
+
+ public void doLogin()
+ {
+ SampleSession session = SampleUtils.getSampleSession();
+ session.setUser(SampleUtils.getSampleUser());
+ // goto list page
+ navigateTo(SamplePages.EmployeeListPage.getRedirect());
+ }
+
+ public void doLogout()
+ {
+ // Perform logout
+ PageOutcome logout = this.getPageDefinition().getRedirect().addParam("logout", String.valueOf(true));
+ navigateTo( logout );
+
+ // Invalidate Session
+ FacesUtils.getContext().getExternalContext().invalidateSession();
+ }
+
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/SamplePage.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/SamplePage.java
new file mode 100644
index 00000000..c2639664
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/SamplePage.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.pages;
+
+import org.apache.empire.db.DBCommand;
+import org.apache.empire.jakarta.pages.Page;
+import org.apache.empire.jakarta.websample.db.SampleDB;
+import org.apache.empire.jakarta.websample.web.SampleApplication;
+import org.apache.empire.jakarta.websample.web.SampleContext;
+import org.apache.empire.jakarta.websample.web.SampleSession;
+import org.apache.empire.jakarta.websample.web.SampleUser;
+import org.apache.empire.jakarta.websample.web.SampleUtils;
+
+public class SamplePage extends Page
+{
+
+ private SampleContext sampleContext = null;
+
+ public synchronized SampleContext getSampleContext()
+ {
+ if (this.sampleContext == null)
+ {
+ SampleApplication app = SampleUtils.getSampleApplication();
+ SampleSession session = SampleUtils.getSampleSession();
+ this.sampleContext = new SampleContext(app.getDatabase(), session);
+ }
+ return this.sampleContext;
+ }
+
+ protected SampleApplication getApplication()
+ {
+ return SampleUtils.getSampleApplication();
+ }
+
+ protected SampleDB getDatabase()
+ {
+ return SampleUtils.getDatabase();
+ }
+
+ public SampleUser getUser()
+ {
+ return SampleUtils.getSampleUser();
+ }
+
+// public int getLanguageIndex()
+// {
+// return SampleUtils.getSampleSession().getUser().getLanguageIndex();
+// }
+
+ protected DBCommand createQueryCommand()
+ {
+ return getSampleContext().createCommand();
+ }
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/SamplePages.java b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/SamplePages.java
new file mode 100644
index 00000000..cb04fb94
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/java/org/apache/empire/jakarta/websample/web/pages/SamplePages.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.websample.web.pages;
+
+import org.apache.empire.jakarta.pages.PageDefinition;
+import org.apache.empire.jakarta.pages.PageDefinitions;
+
+public final class SamplePages extends PageDefinitions
+{
+
+ public static final PageDefinition LoginPage = new PageDefinition("/pages/loginPage.xhtml", LoginPage.class);
+
+ public static final PageDefinition EmployeeListPage = new PageDefinition("/pages/employeeListPage.xhtml", EmployeeListPage.class, SamplePages.LoginPage);
+ public static final PageDefinition EmployeeDetailPage = new PageDefinition("/pages/employeeDetailPage.xhtml", EmployeeDetailPage.class, SamplePages.EmployeeListPage);
+
+}
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/resources/lang/messages_de.properties b/empire-db-examples/empire-db-example-jakarta-faces/src/main/resources/lang/messages_de.properties
new file mode 100644
index 00000000..b28e91a1
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/resources/lang/messages_de.properties
@@ -0,0 +1,151 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#fields
+field.title.employees.id=Mitarbeiter Nr.
+field.title.employees.salutation=Anrede
+field.title.employees.firstName=Vorname
+field.title.employees.lastName=Nachname
+field.title.employees.dateOfBirth=Geburtsdatum
+field.title.employees.departmentId=Abteilung
+field.title.employees.gender=Geschlecht
+field.title.employees.phoneNumber=Telefonnummer
+field.title.employees.email=E-Mail
+field.title.employees.salary=Jahresgehalt
+field.title.employees.retired=Ausgeschieden
+field.title.employees.updateTimestamp=Zuletzt ge�ndert am
+
+field.title.departments.id=Abteilungsnr
+field.title.departments.name=Abteilungsname
+field.title.departments.head=Abteilugnsleiter
+
+field.title.payments.year=Jahr
+field.title.payments.month=Monat
+field.title.payments.amount=Betrag
+
+#options
+option.employee.gender.male=M�nnlich
+option.employee.gender.female=Weiblich
+
+#options
+option.employee.active=-
+option.employee.retired=Ja
+
+#application
+application_title=Jakarta Mitarbeiterverwaltung
+application_todo=Noch zu implementieren
+application_logout=Abmelden
+
+
+#
+global_list_result_empty=Es wurden keine Elemente mit den angegebenen Filterkriterien gefunden.
+global_status=Der Status ist {0}
+global_valid=g�ltig
+global_invalid=ung�ltig
+
+#pages
+login_title=Anmeldung
+login_login=anmelden
+login_field_name=Benutzername
+login_field_name_required=Es muss ein Benutzername angegeben werden.
+login_field_password=Passwort
+login_field_password_required=Es muss ein Passwort eingegeben werden.
+login_field_language=Sprache
+login_hint=Hinweis: Geben Sie einen beliebigen Benutzernamen und Kennwort an.
+
+employeeSearch_title=Mitarbeitersuche
+employeeSearch_search=Suche
+employeeSearch_field_employeeId=Mitarbeiter ID
+employeeSearch_field_firstName=Vorname
+employeeSearch_field_lastName=Nachname
+employeeSearch_field_departmentId= Abteilung
+
+employeeList_title=Mitarbeiter�bersicht
+employeeList_create=Neuen Mitarbeiter erstellen
+employeeList_field_employeeId=ID
+employeeList_field_name=Name
+employeeList_field_gender=Geschlecht
+employeeList_field_dateOfBirth=Geburtsdatum
+employeeList_field_department=Abteilung
+employeeList_search=Suchen
+employeeList_resetSearch=Suche zur�cksetzen
+
+employeeDetail_title=Mitarbeiterdetails
+employeeDetail_tab_basic=Grunddaten
+employeeDetail_tab_extended=Weitere Daten
+employeeDetail_msg_extended=Hier k�nnen weitere Daten angezeigt werden
+employeeDetail_save=Speichern
+employeeDetail_back=Zur�ck
+employeeDetail_delete=L�schen
+employeeDetail_test=Test
+employeeDetail_testMessage=Serialization erfolgreich durchgef�hrt
+employeeDetail_payments=Zahlungen
+
+# Old stuff
+
+#labels
+application.title=Mitarbeiterverwaltung (Empire-db)
+label.employees=Mitarbeiterliste
+label.employees.beanList=Mitarbeiterliste (Bean-Liste)
+label.employees.reader=Mitarbeiterliste (DBReader)
+label.delete=L�schen
+label.edit=Bearbeiten
+label.employee.edit=Eintrag bearbeiten
+label.employee.add=Eintrag hinzuf�gen
+label.name=Name
+label.gender=Geschlecht
+label.age=Alter
+label.department=Abteilung
+label.user.Name=Name
+label.user.Pwd=Passwort
+
+page.label.login=Anmelden
+page.label.logininfo=Sie sind angemeldet als:
+page.label.search=Mitarbeiter suchen
+page.label.list=Mitarbeiter ausw�hlen
+page.label.update=Mitarbeiter bearbeiten
+page.label.add=Mitarbeiter hinzuf�gen
+
+#button labels
+button.label.login=Anmelden
+button.label.search=Suchen...
+button.label.save=Speichern
+button.label.cancel=Abbrechen
+button.label.delete=L�schen
+
+#link labels
+link.label.addemployee=Neuen Eintrag anlegen
+link.label.edit=Bearbeiten
+link.label.delete=L�schen
+link.label.logout=Logout
+link.label.search=Suchen
+
+##-- errors
+errors.prefix=<span style="color:red;font-weight:bold;">
+errors.suffix=</span>
+
+error.invalidpassword=Der eingegebene Benutzername ist ung�ltig (Es ist mindestens ein Zeichen erforderlich!)
+error.web.recordsDontMatch=The form submitted is invalid for the current context.
+error.web.columnNotFound=Die Spalte {0} wurde nicht gefunden!
+error.web.fieldError=Feld {0}: {1}
+error.web.input.integerFormat=Ung�ltiges Zahlenformat
+error.web.input.numberFormat=Ung�ltiges Zahlenformat
+error.web.input.dateFormat=Ung�ltiges Datumsformat
+error.web.input.date.notime=Es wurde keine Zeit angegeben
+error.web.input.required=Das Feld ist eingabepflichtig
+error.web.input.outOfRange=Der Wert muss zwischen {0} und {1} liegen.
+error.web.input.textTooShort=Der Wert muss mindestens {0} Zeichen enthalten.
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/resources/lang/messages_en.properties b/empire-db-examples/empire-db-example-jakarta-faces/src/main/resources/lang/messages_en.properties
new file mode 100644
index 00000000..bcf5132d
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/resources/lang/messages_en.properties
@@ -0,0 +1,151 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+#fields
+field.title.employees.id=Employee ID
+field.title.employees.salutation=Salutation
+field.title.employees.firstName=Firstname
+field.title.employees.lastName=Lastname
+field.title.employees.dateOfBirth=Date of Birth
+field.title.employees.departmentId=Department
+field.title.employees.gender=Gender
+field.title.employees.phoneNumber=Phone
+field.title.employees.email=E-Mail
+field.title.employees.salary=Salary p.a.
+field.title.employees.retired=Retired
+field.title.employees.updateTimestamp=Last change
+
+field.title.departments.id=Department ID
+field.title.departments.name=Department
+field.title.departments.head=Head of Dept.
+
+field.title.payments.year=Year
+field.title.payments.month=Month
+field.title.payments.amount=Amount
+
+#options
+option.employee.gender.male=Male
+option.employee.gender.female=Female
+
+#options
+option.employee.active=-
+option.employee.retired=Yes
+
+#application
+application_title=Jakarta Employee Management
+application_todo=To do...
+application_logout=Logout
+
+
+#
+global_list_result_empty=No employee(s) found
+global_status=The status is {0}
+global_valid=valid
+global_invalid=invalid
+
+#pages
+login_title=Login
+login_login=login
+login_field_name=User name
+login_field_name_required=User name is required.
+login_field_password=Password
+login_field_password_required=Password is required.
+login_field_language=Sprache
+login_hint=Hint: enter any non null username and password.
+
+employeeSearch_title=Search employee
+employeeSearch_search=Search
+employeeSearch_field_employeeId=Employee ID
+employeeSearch_field_firstName=First name
+employeeSearch_field_lastName=Last name
+employeeSearch_field_departmentId=Department
+
+employeeList_title=Employee List
+employeeList_create=Create employee
+employeeList_field_employeeId=ID
+employeeList_field_name=Name
+employeeList_field_gender=Gender
+employeeList_field_dateOfBirth=Date of Birth
+employeeList_field_department=Department
+employeeList_search= Search
+employeeList_resetSearch=Search reset
+
+employeeDetail_title=Employee Detail
+employeeDetail_tab_basic=Basic data
+employeeDetail_tab_extended=Other data
+employeeDetail_msg_extended=Please display additional information here
+employeeDetail_save=Save
+employeeDetail_back=Back
+employeeDetail_delete=Delete
+employeeDetail_test=Test
+employeeDetail_testMessage=Serialization successful
+employeeDetail_payments=Payments
+
+# Old stuff
+
+#labels
+application.title=Employee Management Application
+label.employees=List of employees
+label.employees.beanList=Employees (Bean-List)
+label.employees.reader=Employees (DBReader)
+label.delete=Delete
+label.edit=Edit
+label.employee.edit=Employee details
+label.employee.add=Add employee
+label.department=Department
+label.name=Name
+label.gender=Gender
+label.age=Age
+label.user.Pwd=Password
+
+page.label.login=Login
+page.label.loginHint=Hint: enter any non null username and password.
+page.label.logininfo=You are logged in as:
+page.label.search=Search employee
+page.label.list=Select employee
+page.label.update=Edit employee details
+page.label.add=Add new employee
+
+#button labels
+button.label.login=Login
+button.label.search=Search
+button.label.save=Save
+button.label.cancel=Cancel
+button.label.delete=Delete
+
+#link labels
+link.label.addemployee=Add new employee
+link.label.edit=Edit
+link.label.delete=Delete
+link.label.logout=Logout
+link.label.search=Search
+
+##-- errors
+errors.prefix=<span style="color:red;font-weight:bold;">
+errors.suffix=</span>
+
+error.invalidpassword=The username is invalid (At least one character is required!)
+error.web.recordsDontMatch=The form submitted is invalid for the current context.
+error.web.columnNotFound=The column {0} has not been found!
+error.web.fieldError=Field {0}: {1}
+error.web.input.integerFormat=Invalid Integer format
+error.web.input.numberFormat=Invalid number format
+error.web.input.dateFormat=Invalid date format
+error.web.input.date.notime=No time specified.
+error.web.input.required=This field is required
+error.web.input.outOfRange=The value must be in the range of {0} and {1}.
+error.web.input.textTooShort=The value must contain at least {0} characters.
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/.gitignore b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/.gitignore
new file mode 100644
index 00000000..8db7f2e8
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/.gitignore
@@ -0,0 +1,2 @@
+/.#webclasspath
+/hsqldb/
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/META-INF/context.xml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/META-INF/context.xml
new file mode 100644
index 00000000..1bd17625
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/META-INF/context.xml
@@ -0,0 +1,28 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<!-- This file is read by Tomcat 6 and creates the datasources required just by being in the META-INF directory -->
+<Context path="/empjsf" debug="5" reloadable="true" crossContext="true">
+
+ <!-- Connection pool definition - not used in sample
+ <Resource name="jdbc/sampleDataSource" auth="Container" type="javax.sql.DataSource"
+ maxActive="20" maxIdle="10" maxWait="-1" driverClassName="oracle.jdbc.driver.OracleDriver"
+ url="jdbc:oracle:thin:@devserver:1521:ora19" username="DBSAMPLE" password="DBSAMPLE" />
+ -->
+
+</Context>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/config.xml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/config.xml
new file mode 100644
index 00000000..b6cc3adb
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/config.xml
@@ -0,0 +1,182 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+<config>
+
+ <properties>
+ <!-- provider name must match the property-section containing the connection data -->
+ <databaseProvider>hsqldb</databaseProvider>
+ </properties>
+
+ <properties-hsqldb>
+ <!-- JDBC properties for a HSQLDB Database connection -->
+ <!-- Required jar file: hsqldb.jar -->
+ <jdbcClass>org.hsqldb.jdbc.JDBCDriver</jdbcClass>
+ <jdbcURL>jdbc:hsqldb:file:hsqldb/sample;shutdown=true</jdbcURL>
+ <jdbcUser>sa</jdbcUser>
+ <jdbcPwd></jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.hsql.DBMSHandlerHSql</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <!-- no properties -->
+ </dbmsHandlerProperties>
+ </properties-hsqldb>
+
+ <properties-mysql>
+ <!-- JDBC properties for a MySQL Database connection -->
+ <!-- Required jar file: mysql-connector-java-5.1.6-bin.jar -->
+ <jdbcClass>com.mysql.jdbc.Driver</jdbcClass>
+ <jdbcURL>jdbc:mysql://localhost</jdbcURL>
+ <jdbcUser>root</jdbcUser>
+ <jdbcPwd>esteam</jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.mysql.DBMSHandlerMySQL</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <databaseName>DBSAMPLE</databaseName>
+ </dbmsHandlerProperties>
+ </properties-mysql>
+
+ <properties-postgresql>
+ <!-- JDBC properties for a PostreSQL Database connection -->
+ <!-- Required jar file: postgresql-8.3-603.jdbc4.jar -->
+ <jdbcClass>org.postgresql.Driver</jdbcClass>
+ <jdbcURL>jdbc:postgresql://localhost/dbsample</jdbcURL>
+ <jdbcUser>dbsample</jdbcUser>
+ <jdbcPwd>DBSAMPLE</jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.postgresql.DBMSHandlerPostgreSQL</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <!-- no properties -->
+ </dbmsHandlerProperties>
+ </properties-postgresql>
+
+ <properties-h2>
+ <!-- JDBC properties for a H2 Database connection -->
+ <!-- Required jar file: h2-1.1.107.jar -->
+ <jdbcClass>org.h2.Driver</jdbcClass>
+ <jdbcURL>jdbc:h2:h2/DBSAMPLE</jdbcURL>
+ <jdbcUser>sa</jdbcUser>
+ <jdbcPwd></jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.h2.DBMSHandlerH2</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <databaseName>DBSAMPLE</databaseName>
+ </dbmsHandlerProperties>
+ </properties-h2>
+
+ <properties-derby>
+ <!-- JDBC properties for a Derby Database connection -->
+ <!-- Required jar file: derby-10.4.2.0.jar -->
+ <jdbcClass>org.apache.derby.jdbc.EmbeddedDriver</jdbcClass>
+ <jdbcURL>jdbc:derby:derby/DBSAMPLE;create=true</jdbcURL>
+ <jdbcUser></jdbcUser>
+ <jdbcPwd></jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.derby.DBMSHandlerDerby</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <databaseName>DBSAMPLE</databaseName>
+ </dbmsHandlerProperties>
+ </properties-derby>
+
+ <properties-oracle>
+ <!-- JDBC properties for an Oracle Database connection -->
+ <!-- Required jar file: ojdbc14.jar -->
+ <jdbcClass>oracle.jdbc.driver.OracleDriver</jdbcClass>
+ <jdbcURL>jdbc:oracle:thin:@devserver:1521:ora19</jdbcURL>
+ <jdbcUser>DBSAMPLE</jdbcUser>
+ <jdbcPwd>DBSAMPLE</jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.oracle.DBMSHandlerOracle</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <!-- no properties -->
+ </dbmsHandlerProperties>
+ </properties-oracle>
+
+ <properties-sqlserver>
+ <!-- JDBC properties for a Microsoft SQL-Server Database connection -->
+ <jdbcClass>com.microsoft.sqlserver.jdbc.SQLServerDriver</jdbcClass>
+ <jdbcURL>jdbc:sqlserver://devserver</jdbcURL>
+ <jdbcUser>jdbcUser</jdbcUser>
+ <jdbcPwd>jdbcPwd</jdbcPwd>
+ <!-- Empire-db driver configuration -->
+ <dbmsHandlerClass>org.apache.empire.dbms.sqlserver.DBMSHandlerMSSQL</dbmsHandlerClass>
+ <dbmsHandlerProperties property="false">
+ <databaseName>DBSAMPLE</databaseName>
+ <useSequenceTable>false</useSequenceTable>
+ <!-- useDateTime2: set to false for databases using DATETIME for timestamps instead of DATETIME2 -->
+ <useDateTime2>true</useDateTime2>
+ </dbmsHandlerProperties>
+ </properties-sqlserver>
+
+ <!-- log4j configuration node -->
+ <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <!-- Default Appender -->
+ <appender name="default" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p : %m at %l %n"/>
+ </layout>
+ </appender>
+
+ <!-- Appender for DBUtils (SQL-Statements) -->
+ <appender name="utils" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="--------------------------------%n%-5p : %m%n"/>
+ </layout>
+ </appender>
+
+ <!-- Appender for SampleApp -->
+ <appender name="sample" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p : *** %m *** %n"/>
+ </layout>
+ </appender>
+
+ <!-- log detail configuration -->
+ <logger name="org.apache.empire.db" additivity="false">
+ <level value="warn"/>
+ <appender-ref ref="default"/>
+ </logger>
+
+ <!-- Show Config-file settings -->
+ <logger name="org.apache.empire.xml" additivity="false">
+ <level value="info"/>
+ <appender-ref ref="default"/>
+ </logger>
+
+ <!-- Set this level to "debug" to log all SQL-Statements or to "info" to log DML-Statements only -->
+ <logger name="org.apache.empire.db.DBUtils" additivity="false">
+ <level value="debug"/>
+ <appender-ref ref="utils"/>
+ </logger>
+
+ <!-- This is the log output of the SampleApp -->
+ <logger name="org.apache.empire.jsf2.websample" additivity="false">
+ <level value="info"/>
+ <appender-ref ref="sample"/>
+ </logger>
+
+ <root>
+ <priority value="info"/>
+ <appender-ref ref="default"/>
+ </root>
+
+ </log4j:configuration>
+
+</config>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/facelets/templates/header.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/facelets/templates/header.xhtml
new file mode 100644
index 00000000..eb7f3f12
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/facelets/templates/header.xhtml
@@ -0,0 +1,31 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<h:panelGroup id="header" xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:f="http://java.sun.com/jsf/core">
+
+ <!-- application title -->
+ <h:panelGroup layout="block" styleClass="titleDiv">
+ <h:outputText value="#{msg.application_title}" />
+ </h:panelGroup>
+
+ <!-- page box -->
+ <h1><h:outputText value="#{pageTitle}" /></h1>
+
+</h:panelGroup>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/facelets/templates/layout.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/facelets/templates/layout.xhtml
new file mode 100644
index 00000000..6daae079
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/facelets/templates/layout.xhtml
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:ui="http://java.sun.com/jsf/facelets"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:f="http://java.sun.com/jsf/core">
+
+<!-- internationalization -->
+<f:view locale="#{sampleUser.locale}" />
+<ui:param name="webRoot" value=".." />
+
+<f:metadata>
+ <ui:insert name="metadata" />
+</f:metadata>
+
+<h:head>
+
+ <!-- dynamic page title -->
+ <title>#{msg.application_title} - #{pageTitle}</title>
+
+ <!-- meta information -->
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+ <meta http-equiv="Content-Language" content="en" />
+ <meta http-equiv="imagetoolbar" content="no" />
+ <meta name="keywords" content="" />
+ <meta name="description" content="" />
+
+ <!-- global stylesheets -->
+ <link href="#{webRoot}/css/layout.css" rel="stylesheet" type="text/css" />
+
+ <!-- custom page stylesheets -->
+ <ui:insert name="pageCss" />
+
+ <!-- javascript -->
+
+ <!-- custom page script -->
+ <ui:insert name="pageScript" />
+</h:head>
+<h:body>
+ <f:view>
+
+ <!-- header -->
+ <ui:include src="header.xhtml" />
+
+ <!-- (error) messages -->
+ <h:panelGroup id="messages" layout="block">
+ <h:messages />
+ </h:panelGroup>
+
+ <!-- content -->
+ <h:panelGroup id="content" layout="block">
+ <ui:insert name="content" />
+ </h:panelGroup>
+
+ </f:view>
+</h:body>
+</html>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/faces-config.xml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/faces-config.xml
new file mode 100644
index 00000000..5ebfe1ac
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/faces-config.xml
@@ -0,0 +1,143 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ you under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<faces-config xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd"
+ version="2.0">
+
+ <factory>
+ <exception-handler-factory>org.apache.empire.jakarta.websample.web.AppExceptionHandlerFactory</exception-handler-factory>
+ </factory>
+
+ <application>
+ <!-- System event listener -->
+ <system-event-listener>
+ <!-- (StartupListener for manual configuration)
+ <system-event-listener-class>org.apache.empire.jakarta.app.WebAppStartupListener</system-event-listener-class>
+ -->
+ <!-- (StartupListener for programmatic configuration) -->
+ <system-event-listener-class>org.apache.empire.jakarta.websample.web.SampleAppStartupListener</system-event-listener-class>
+ <!-- Faces Event -->
+ <system-event-class>jakarta.faces.event.PostConstructApplicationEvent</system-event-class>
+ </system-event-listener>
+ <!-- locales -->
+ <locale-config>
+ <default-locale>en</default-locale>
+ <supported-locale>en</supported-locale>
+ <supported-locale>de</supported-locale>
+ </locale-config>
+ <message-bundle>lang.messages</message-bundle>
+ <!-- messages -->
+ <message-bundle>lang.messages</message-bundle>
+ <resource-bundle>
+ <base-name>lang.messages</base-name>
+ <var>msg</var>
+ </resource-bundle>
+ <!-- EL-Resolvers (Cannot be added programmatically in Mojarra!) -->
+ <el-resolver>org.apache.empire.jakarta.app.DBELResolver</el-resolver>
+ <el-resolver>org.apache.empire.jakarta.pages.PagesELResolver</el-resolver>
+ <!-- Navigation handler (added programmatically)
+ <navigation-handler>org.apache.empire.jakarta.pages.PageNavigationHandler</navigation-handler>
+ -->
+ </application>
+
+ <!-- Lifecycle -->
+ <lifecycle>
+ <!-- (added programmatically)
+ <phase-listener>org.apache.empire.jakarta.app.FacesRequestPhaseListener</phase-listener>
+ <phase-listener>org.apache.empire.jakarta.pages.PagePhaseListener</phase-listener>
+ -->
+ <phase-listener>org.apache.empire.jakarta.websample.web.AuthenticationPhaseListener</phase-listener>
+ </lifecycle>
+
+ <!-- Application bean (MUST be named 'webApplication' for WebAppStartupListener!) -->
+ <managed-bean>
+ <managed-bean-name>webApplication</managed-bean-name>
+ <managed-bean-class>org.apache.empire.jakarta.websample.web.SampleApplication</managed-bean-class>
+ <managed-bean-scope>application</managed-bean-scope>
+ </managed-bean>
+
+ <!-- Session beans -->
+ <managed-bean>
+ <managed-bean-name>sampleSession</managed-bean-name>
+ <managed-bean-class>org.apache.empire.jakarta.websample.web.SampleSession</managed-bean-class>
+ <managed-bean-scope>session</managed-bean-scope>
+ </managed-bean>
+
+ <managed-bean>
+ <managed-bean-name>employeeSearchFilter</managed-bean-name>
+ <managed-bean-class>org.apache.empire.jakarta.websample.web.objects.EmployeeSearchFilter</managed-bean-class>
+ <managed-bean-scope>session</managed-bean-scope>
+ </managed-bean>
+
+ <managed-bean>
+ <managed-bean-name>sampleUser</managed-bean-name>
+ <managed-bean-class>org.apache.empire.jakarta.websample.web.SampleUser</managed-bean-class>
+ <managed-bean-scope>session</managed-bean-scope>
+ </managed-bean>
+
+ <!-- Empire-JSF components (added programmatically)
+ <component>
+ <component-type>org.apache.empire.component.RecordTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.RecordTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.ControlTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.ControlTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.InputTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.InputTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.FormGridTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.FormGridTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.LabelTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.LabelTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.LinkTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.LinkTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.SelectTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.SelectTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.TitleTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.TitleTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.ValueTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.ValueTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.MenuListTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.MenuListTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.MenuItemTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.MenuItemTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.TabViewTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.TabViewTag</component-class>
+ </component>
+ <component>
+ <component-type>org.apache.empire.component.TabPageTag</component-type>
+ <component-class>org.apache.empire.jakarta.components.TabPageTag</component-class>
+ </component>
+ -->
+
+</faces-config>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/web.xml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 00000000..5152f879
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+ license agreements. See the NOTICE file distributed with this work for additional
+ information regarding copyright ownership. The ASF licenses this file to
+ you under the Apache License, Version 2.0 (the "License"); you may not use
+ this file except in compliance with the License. You may obtain a copy of
+ the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+ by applicable law or agreed to in writing, software distributed under the
+ License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+ OF ANY KIND, either express or implied. See the License for the specific
+ language governing permissions and limitations under the License. -->
+<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ id="JSF2Sample" version="3.0">
+ <display-name>Empire DBWebSample</display-name>
+
+ <welcome-file-list>
+ <welcome-file>index.html</welcome-file>
+ </welcome-file-list>
+
+ <servlet>
+ <servlet-name>Faces Servlet</servlet-name>
+ <servlet-class>jakarta.faces.webapp.FacesServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>Faces Servlet</servlet-name>
+ <url-pattern>/pages/</url-pattern>
+ <url-pattern>*.xhtml</url-pattern>
+ <url-pattern>*.jsf</url-pattern>
+ </servlet-mapping>
+
+
+ <!-- Taglib
+ <context-param>
+ <param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
+ <param-value>/WEB-INF/facelets/taglib.xml</param-value>
+ </context-param>
+ -->
+
+ <context-param>
+ <description>State saving method: 'client' or 'server' (=default). See JSF Specification 2.5.2</description>
+ <param-name>jakarta.faces.STATE_SAVING_METHOD</param-name>
+ <param-value>server</param-value>
+ </context-param>
+ <context-param>
+ <param-name>jakarta.faces.PROJECT_STAGE</param-name>
+ <param-value>Development</param-value>
+ </context-param>
+ <context-param>
+ <param-name>jakarta.faces.FACELETS_SKIP_COMMENTS</param-name>
+ <param-value>true</param-value>
+ </context-param>
+ <context-param>
+ <param-name>jakarta.faces.PARTIAL_STATE_SAVING</param-name>
+ <param-value>false</param-value>
+ </context-param>
+
+</web-app>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/common.css b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/common.css
new file mode 100644
index 00000000..1c65dc7c
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/common.css
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* common */
+* {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+*:before,
+*:after {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+html, body {
+ min-width: 400px;
+ max-width: 1200px;
+ margin-left: 10px;
+ margin-right: auto;
+ margin-bottom: 5px;
+ padding-right: 20px;
+ color: black;
+ background-color: white;
+ font-family: Verdana, Arial, sans-serif;
+ font-size:12px;
+}
+.titleDiv {
+ background-color: #EFFBEF;
+ font-weight:bold;
+ font-size:18px;
+ text-align:left;
+ padding-left:10px;
+ padding-top:10px;
+ padding-bottom:10px;
+ border:2px solid #8F99EF;
+}
+h1 { font-weight:bold; color: brown; font-size:15px; text-align:left;}
+
+td { font-size:12px; padding-right:10px; }
+th { text-align:left; font-weight:bold; font-size:13px; padding-right:10px; }
+
+A:link { text-decoration:none;}
+A:visited { text-decoration:none;}
+A:hover { text-decoration:underline;}
+
+.even { background-color: #EFEFFB; text-align:left;}
+.odd { background-color: #FFFFFF; text-align:left; }
+
+.nowrap { white-space:nowrap; }
+
+
+#messages ul {
+ background-color: #FBF0F0;
+ border:2px solid #8F99EF;
+ list-style-type: square;
+ padding: 8px;
+ padding-left: 24px;
+}
+#messages ul > li {
+ padding: 4px 0;
+/*
+ padding-left:18px;
+ padding-top:2px;
+ padding-bottom:10px;
+ padding-right:5px;
+*/
+ color: #922727;
+ font-weight: bold;
+}
+
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/content.css b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/content.css
new file mode 100644
index 00000000..ef0806ba
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/content.css
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+.margin-above {
+ margin-top: 10px;
+}
+
+/* form panel */
+.formPanel {
+ border: 1px #8F99EF solid;
+ background-color: var(--form-background-color);
+ padding: 8px 4px;
+}
+.formPanel > .buttonBar {
+ padding: var(--control-v-pad) 0;
+ padding-left: var(--label-width);
+ margin-top: 10px;
+}
+
+.buttonBar a,
+.buttonBar a:visited {
+ display: inline-block;
+ border: var(--button-border-width) var(--button-border-color) solid;
+ padding: var(--input-v-pad) 12px;
+ color: black;
+ text-decoration: none;
+ margin-right: 16px;
+ background-color: white;
+ font-weight: bold;
+}
+.buttonBar a:hover {
+ background-color: #EFFBEF;
+}
+
+/* result table */
+div.searchResult,
+span.searchResult {
+ display: block;
+ margin: 12px 0;
+ padding: 4px 0;
+}
+.searchResult table {
+ width: 100%;
+ border: 1px #8F99EF solid;
+ border-collapse: collapse;
+}
+.searchResult table tr th,
+.searchResult table tr td {
+ padding: var(--table-cell-v-pad) var(--table-cell-h-pad);
+}
+.searchResult table tr th {
+ background-color: #EFFBEF;
+ border-bottom: 1px #8F99EF solid;
+}
+.searchResult table a.eLink,
+.searchResult table a.eLink:visited {
+ color: brown;
+ font-weight: bold;
+}
+
+/* tab view */
+div.eTabView {
+}
+div.eTabBar {
+ display: flex;
+}
+table.eTabBar {
+ width: 100%;
+ border-spacing: 0;
+ border-collapse: collapse;
+ table-layout: auto;
+ padding: 0;
+}
+div.eTabBar .eTabLabel,
+table.eTabBar tr td.eTabLabel {
+ border: 1px #8F99EF solid;
+ padding: 8px;
+ white-space: nowrap;
+}
+div.eTabBar .eTabLabel.eTabActive,
+table.eTabBar tr td.eTabLabel.eTabActive {
+ border-bottom: 1px #F8F8F8 solid;
+ background-color: #F8F8F8;
+}
+table.eTabBar tr td.eTabLabel.eTabDisabled {
+ color: gray;
+ font-style: italic;
+}
+.eTabBar .eTabBarEmpty {
+ width: 99%;
+ border-bottom: 1px #8F99EF solid;
+}
+table.eTabPanel {
+ width: 100%;
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+div.eTabPanel {
+ display: grid;
+}
+.eTabPanel {
+ border: 1px #8F99EF solid;
+ border-top: 0px;
+}
+.eTabPanel .eTabPage {
+ padding: 8px;
+ background-color: #F8F8F8;
+ vertical-align: top;
+}
+.eTabPanel .eTabPage .formPanel {
+ border: 0;
+}
+div.info-msg {
+ padding: 12px;
+ background-color: #FFFFCA;
+}
+.paymentData {
+ border: 2px gray solid;
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/form.css b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/form.css
new file mode 100644
index 00000000..eb4f8f78
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/form.css
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+label {
+ font-weight: bold;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+label.eLabel.eInpReq:after {
+ content: "*";
+ color: #D00000;
+}
+select,
+input[type=text],
+input[type=password] {
+ padding: var(--input-v-pad) var(--input-h-pad);
+ border: var(--input-border-width) var(--input-border-color) solid;
+}
+select.eInpReq,
+input[type=text].eInpReq {
+ background-color: #FFFFE0;
+}
+select.eInpDis,
+textarea.eInpDis,
+input[type=text].eInpDis {
+ border: 1px #C0C0C0 solid;
+ color: #808080;
+}
+select.eInput.eInpShort,
+input.eInput.eInpShort,
+input.eInput.eTypeDate {
+ width: 100px;
+}
+input.eInput.eInpDecimal {
+ width: 100px;
+ text-align: right;
+}
+
+/* general */
+.eCtlInput > .eInput {
+ width: 100%;
+}
+.eCtlInput > .eInput.eTypeBool {
+ width: auto;
+}
+.eCtlInput > span {
+ display: inline-block;
+ padding: var(--input-v-pad) 0;
+ border: var(--input-border-width) transparent solid;
+}
+.eCtlInput .eUnit {
+ padding-left: var(--input-h-pad);
+ font-weight: bold;
+}
+.eInputHint {
+ padding-left: 4px;
+ font-style: italic;
+}
+.eTypeBoolFalse {
+ width: 12px;
+ height: 12px;
+ background-image:url('../img/boxes.gif');
+ background-repeat: no-repeat;
+ background-position: 0 0;
+}
+.eTypeBoolTrue {
+ width: 12px;
+ height: 12px;
+ background-image:url('../img/boxes.gif');
+ background-repeat: no-repeat;
+ background-position: -12px 0;
+}
+.eControl > .eCtlLabel {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* wrapper class styles */
+.eWrapInp {
+ /* general wrapper style */
+ display: flex;
+ padding: var(--input-v-pad) var(--input-h-pad);
+}
+.eWrapVal {
+ /* general wrapper style */
+ display: inline-block;
+}
+.checkboxWrapper {
+ /* special checkbox wrapper style */
+ /* background-color: #E8E8E8; */
+}
+
+/* formGrid in Grid mode */
+div.eFormGrid {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-gap: var(--grid-v-space) var(--grid-h-space);
+/* background-color: white; */
+}
+
+/* grid-top-align */
+div.eFormGrid > .eControl {
+ display: flex;
+/* background-color: #F8F8F8; */
+ min-height: var(--control-min-height);
+}
+div.eFormGrid > .eControl > .eCtlLabel {
+ width: var(--label-width);
+ padding: calc(var(--control-v-pad) + var(--input-v-pad) + 1px) var(--control-h-pad) var(--control-v-pad) var(--control-h-pad);
+}
+div.eFormGrid > .eControl > .eCtlInput {
+ width: calc(100% - var(--label-width));
+ padding: var(--control-v-pad) var(--control-h-pad);
+ padding-left: 0;
+}
+
+/* grid-v-align */
+div.eFormGrid.grid-v-align {
+}
+div.eFormGrid.grid-v-align > .eControl {
+ display: table;
+}
+div.eFormGrid.grid-v-align > .eControl > .eCtlLabel {
+ display: table-cell;
+ vertical-align: middle;
+ padding: 0 var(--control-h-pad);
+ max-width: var(--label-width);
+}
+div.eFormGrid.grid-v-align > .eControl > .eCtlInput {
+ display: table-cell;
+ vertical-align: middle;
+}
+
+/* extentions */
+@media (min-width: 1024px) {
+ div.eFormGrid.grid-columns-2 {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ div.eFormGrid > .eControl.grid-full-row {
+ grid-column: 1 / -1;
+ }
+ div.eFormGrid > .eControl.grid-new-row {
+ grid-column: 1 / 2;
+ }
+}
+
+/* formGrid in table mode */
+table.eFormGrid {
+ width: 100%;
+/* max-width: 800px; */
+ table-layout: fixed;
+ border-spacing: 0 var(--grid-v-space);
+ border-collapse: separate;
+}
+table.eFormGrid td.eCtlLabel {
+ vertical-align: middle;
+ padding: 0 var(--control-h-pad);
+ width: var(--label-width);
+}
+table.eFormGrid td.eCtlInput {
+ vertical-align: middle;
+ padding: var(--control-v-pad) var(--control-h-pad);
+ padding-left: 0;
+}
+
+/* grid-white-gap */
+.grid-white-gap {
+ background-color: white;
+}
+.grid-white-gap .eCtlLabel {
+ /* optional */
+ background-color: var(--form-background-color);
+}
+.grid-white-gap .eCtlInput {
+ /* optional */
+ background-color: var(--form-background-color);
+}
+
+/* misc */
+.formSection {
+ grid-column: 1 / -1;
+ background-color: lightgray;
+ padding: var(--control-v-pad) 0;
+ text-align: center;
+ font-weight: bold;
+}
+.xSalaryWrapper {
+ background-color: yellow;
+}
+
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/layout.css b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/layout.css
new file mode 100644
index 00000000..839f9ce7
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/layout.css
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+@charset "UTF-8";
+
+@import url("root.css");
+@import url("common.css");
+@import url("content.css");
+@import url("form.css");
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/root.css b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/root.css
new file mode 100644
index 00000000..8a6a5f35
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/css/root.css
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+:root {
+ --label-width: 150px;
+ --button-border-width: 2px;
+ --button-border-color: #8F99EF;
+ --input-border-width: 1px;
+ --input-border-color: #8F99EF;
+ --input-v-pad: 7px;
+ --input-h-pad: 7px;
+ --control-v-pad: 5px;
+ --control-h-pad: 5px;
+ --control-min-height: 20px;
+ --grid-v-space: 2px;
+ --grid-h-space: 20px;
+ --table-cell-v-pad: 10px;
+ --table-cell-h-pad: 5px;
+ --form-background-color: #F8F8F8;
+}
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/img/boxes.gif b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/img/boxes.gif
new file mode 100644
index 00000000..57bf32d6
Binary files /dev/null and b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/img/boxes.gif differ
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/index.html b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/index.html
new file mode 100644
index 00000000..2e100884
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html>
+<head>
+ <title>Redirecting</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <META HTTP-EQUIV="Refresh" CONTENT="0; URL=pages/loginPage.xhtml">
+</head>
+<body>
+ Redirecting to <a href="pages/loginPage.xhtml">Start...</a>
+</body>
+
+
+</html>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/employeeDetailPage.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/employeeDetailPage.xhtml
new file mode 100644
index 00000000..f811e116
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/employeeDetailPage.xhtml
@@ -0,0 +1,108 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<ui:composition template="/WEB-INF/facelets/templates/layout.xhtml"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:ui="http://java.sun.com/jsf/facelets"
+ xmlns:f="http://java.sun.com/jsf/core"
+ xmlns:e="http://java.sun.com/jsf/composite/empire"
+ xmlns:sample="http://java.sun.com/jsf/composite/components"
+ xmlns:h="http://java.sun.com/jsf/html">
+ <f:metadata>
+ <f:viewParam name="action" value="#{page.action}" />
+ <f:viewParam name="id" value="#{page.idParam}" />
+ </f:metadata>
+
+ <!-- parameters used in layout.xhtml -->
+ <ui:param name="pageTitle" value="#{msg.employeeDetail_title}" />
+ <ui:param name="contentStyle" value="unstyled" />
+
+ <!-- content -->
+ <ui:define name="content">
+
+ <h:form id="employeeDetail">
+
+ <e:tabView minHeight="400px" activeIndex="#{page.activeTab}" tabChangedListener="#{page.onTabChanged}">
+ <!-- first page -->
+ <e:tabPage id="tabBasic" title="#{msg.employeeDetail_tab_basic}">
+ <e:record value="#{page.employeeRecord}">
+ <!-- formPanel -->
+ <div class="formPanel">
+ <e:formGrid mode="grid" styleClass="grid-columns-2"><!-- grid-v-align grid-white-gap -->
+ <e:control column="#{db.EMPLOYEES.SALUTATION}"/>
+ <e:control column="#{db.EMPLOYEES.FIRST_NAME}" controlClass="grid-new-row"/>
+ <e:control column="#{db.EMPLOYEES.LAST_NAME}"/>
+ <e:control column="#{db.EMPLOYEES.GENDER}"/>
+ <e:control column="#{db.EMPLOYEES.DATE_OF_BIRTH}"/>
+ <e:control column="#{db.EMPLOYEES.DEPARTMENT_ID}"/>
+ <e:control column="#{db.EMPLOYEES.PHONE_NUMBER}" controlClass="grid-new-row"/>
+ <e:control column="#{db.EMPLOYEES.EMAIL}" controlClass="grid-full-row"/>
+ <e:control column="#{db.EMPLOYEES.SALARY}"/>
+ <e:control column="#{db.EMPLOYEES.RETIRED}"/>
+ <e:control column="#{db.EMPLOYEES.UPDATE_TIMESTAMP}"/>
+ </e:formGrid>
+ </div>
+ <!-- end formPanel -->
+ </e:record>
+ </e:tabPage>
+
+ <!-- second page -->
+ <e:tabPage id="tabExtended" title="#{msg.employeeDetail_tab_extended}" disabled="#{not page.employeeRecord.exists}">
+ <h3><h:outputText value="#{msg.employeeDetail_payments}"/></h3>
+ <!-- Payment data -->
+ <h:dataTable id="paymentData" value="#{page.payments}" var="item" rowClasses="odd,even">
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.PAYMENTS.YEAR}" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.PAYMENTS.YEAR}"/>
+ </h:column>
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.PAYMENTS.MONTH}" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.PAYMENTS.MONTH}"/>
+ </h:column>
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.PAYMENTS.AMOUNT}" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.PAYMENTS.AMOUNT}"/>
+ </h:column>
+ </h:dataTable>
+ <!-- info message -->
+ <h:panelGroup layout="block" styleClass="info-msg">
+ <h:outputText value="#{msg.employeeDetail_msg_extended}"/>
+ </h:panelGroup>
+ </e:tabPage>
+ </e:tabView>
+
+ <!-- button bar -->
+ <sample:pageButtons>
+ <h:commandLink value="#{msg.employeeDetail_back}" action="#{page.doCancel}" immediate="true" />
+ <h:commandLink value="#{msg.employeeDetail_delete}" action="#{page.doDelete}" immediate="true" rendered="#{page.employeeRecord.exists}" />
+ <h:commandLink value="#{msg.employeeDetail_save}" action="#{page.doSave}" />
+ <!--
+ <h:commandLink value="#{msg.employeeDetail_test}" actionListener="#{page.doTestSerialization}" />
+ -->
+ </sample:pageButtons>
+
+ </h:form>
+
+ </ui:define>
+</ui:composition>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/employeeListPage.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/employeeListPage.xhtml
new file mode 100644
index 00000000..3cef89fc
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/employeeListPage.xhtml
@@ -0,0 +1,111 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<ui:composition template="/WEB-INF/facelets/templates/layout.xhtml"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:ui="http://java.sun.com/jsf/facelets"
+ xmlns:f="http://java.sun.com/jsf/core"
+ xmlns:e="http://java.sun.com/jsf/composite/empire"
+ xmlns:sample="http://java.sun.com/jsf/composite/components"
+ xmlns:h="http://java.sun.com/jsf/html">
+ <f:metadata>
+ <f:viewParam name="action" value="#{page.action}" />
+ </f:metadata>
+
+ <!-- parameters used in layout.xhtml -->
+ <ui:param name="pageTitle" value="#{msg.employeeList_title}" />
+ <ui:param name="contentStyle" value="unstyled" />
+
+ <!-- content -->
+ <ui:define name="content">
+ <h:form id="employeeListFilter">
+
+ <!-- search settings -->
+ <div class="formPanel">
+ <!-- search criteria -->
+ <e:formGrid mode="grid" styleClass="grid-columns-2">
+ <e:control column="#{db.EMPLOYEES.FIRST_NAME}" value="#{page.searchFilter.firstName}" />
+ <e:control column="#{db.EMPLOYEES.LAST_NAME}" value="#{page.searchFilter.lastName}" />
+ <e:control column="#{db.EMPLOYEES.DEPARTMENT_ID}" value="#{page.searchFilter.departmentId}"
+ options="#{page.departmentOptions}" />
+ <e:control column="#{db.EMPLOYEES.GENDER}" value="#{page.searchFilter.gender}" />
+ </e:formGrid>
+ <!-- button bar -->
+ <div class="buttonBar">
+ <h:commandLink action="#{page.doResetSearch}" value="#{msg.employeeList_resetSearch}" immediate="true" />
+ <h:commandLink action="#{page.doSearch}" value="#{msg.employeeList_search}" />
+ </div>
+ </div>
+ </h:form>
+
+ <h:form id="employeeList">
+ <h:panelGroup rendered="#{page.employees.resultEmpty}" styleClass="searchResult" layout="block">
+ <h:outputText value="#{msg.global_list_result_empty}" />
+ </h:panelGroup>
+ <!-- list -->
+ <h:panelGroup styleClass="searchResult" layout="block" rendered="#{page.employees.valid}">
+ <h:dataTable id="employeeTable" value="#{page.employees.items}" var="item" rowClasses="odd,even">
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.EMPLOYEES.ID}" value="ID" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.EMPLOYEES.ID}"/>
+ </h:column>
+ <h:column styleClass="item">
+ <f:facet name="header">
+ <h:outputText value="#{msg.employeeList_field_name}" />
+ </f:facet>
+ <e:link page="#{pages.EmployeeDetailPage.doLoad}" idparam="#{item.idParam}" value="#{item.name}" />
+ </h:column>
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.EMPLOYEES.DEPARTMENT_ID}" />
+ </f:facet>
+ <h:outputText value="#{item.department}" />
+ </h:column>
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.EMPLOYEES.GENDER}" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.EMPLOYEES.GENDER}" />
+ </h:column>
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.EMPLOYEES.DATE_OF_BIRTH}" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.EMPLOYEES.DATE_OF_BIRTH}" />
+ </h:column>
+ <h:column>
+ <f:facet name="header">
+ <e:title column="#{db.EMPLOYEES.RETIRED}" />
+ </f:facet>
+ <e:value record="#{item}" column="#{db.EMPLOYEES.RETIRED}" />
+ </h:column>
+ </h:dataTable>
+ </h:panelGroup>
+ </h:form>
+
+ <!-- button bar -->
+ <sample:pageButtons>
+ <e:link page="#{pages.LoginPage.doLogout}" value="#{msg.application_logout}"/>
+ <e:link page="#{pages.EmployeeDetailPage.doCreate}" value="#{msg.employeeList_create}" />
+ </sample:pageButtons>
+
+ </ui:define>
+
+</ui:composition>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/loginPage.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/loginPage.xhtml
new file mode 100644
index 00000000..a34248a3
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/pages/loginPage.xhtml
@@ -0,0 +1,62 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<ui:composition
+ template="/WEB-INF/facelets/templates/layout.xhtml"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:ui="http://java.sun.com/jsf/facelets"
+ xmlns:f="http://java.sun.com/jsf/core"
+ xmlns:e="http://java.sun.com/jsf/composite/empire"
+ xmlns:sample="http://java.sun.com/jsf/composite/components"
+ xmlns:h="http://java.sun.com/jsf/html">
+ <f:metadata>
+ <f:viewParam name="action" value="#{page.action}" />
+ </f:metadata>
+
+ <!-- parameters used in layout.xhtml -->
+ <ui:param name="pageTitle" value="#{msg.login_title}" />
+ <ui:param name="contentFooter" value="true" />
+ <ui:param name="contentStyle" value="unstyled" />
+
+ <!-- content -->
+ <ui:define name="content">
+ <h:form id="loginForm" method="post">
+ <p><h:outputText value="#{msg.login_hint}"/></p>
+ <!-- input form -->
+ <div class="formPanel">
+ <!-- search criteria -->
+ <e:formGrid mode="grid">
+ <sample:formControl for="name" label="#{msg.login_field_name}">
+ <h:inputText id="name" value="#{sampleUser.userName}" required="true" requiredMessage="#{msg.login_field_name_required}" />
+ </sample:formControl>
+ <sample:formControl for="password" label="#{msg.login_field_password}">
+ <h:inputSecret id="password" value="#{sampleUser.password}" required="true" requiredMessage="#{msg.login_field_password_required}" />
+ </sample:formControl>
+ <sample:formControl for="password" label="#{msg.login_field_language}">
+ <e:select id="language" value="#{sampleUser.locale}" options="#{page.languageOptions}" />
+ </sample:formControl>
+ </e:formGrid>
+ <!-- button bar -->
+ <div class="buttonBar">
+ <h:commandLink action="#{page.doLogin}" value="#{msg.login_title}" />
+ </div>
+ </div>
+ </h:form>
+ </ui:define>
+
+</ui:composition>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/components/formControl.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/components/formControl.xhtml
new file mode 100644
index 00000000..9f40011e
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/components/formControl.xhtml
@@ -0,0 +1,41 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:cc="http://java.sun.com/jsf/composite"
+ xmlns:h="http://java.sun.com/jsf/html">
+
+ <!-- Interface -->
+ <cc:interface>
+ <cc:attribute name="label" />
+ <cc:attribute name="for" targets="outputLabel"/>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <div class="eControl">
+ <div class="eCtlLabel">
+ <h:outputLabel id="outputLabel" value="#{cc.attrs.label}" />:
+ </div>
+ <div class="eCtlInput">
+ <cc:insertChildren/>
+ </div>
+ </div>
+ </cc:implementation>
+
+</html>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/components/pageButtons.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/components/pageButtons.xhtml
new file mode 100644
index 00000000..d22870bf
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/components/pageButtons.xhtml
@@ -0,0 +1,36 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:ice="http://www.icesoft.com/icefaces/component"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <h:panelGroup styleClass="buttonBar margin-above" layout="block">
+ <cc:insertChildren />
+ </h:panelGroup>
+ </cc:implementation>
+
+</html>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/control.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/control.xhtml
new file mode 100644
index 00000000..cd3a917c
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/control.xhtml
@@ -0,0 +1,53 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.ControlTag">
+ <cc:attribute name="column" required="true" />
+ <cc:attribute name="record" />
+ <cc:attribute name="custom" />
+ <cc:attribute name="value" />
+ <cc:attribute name="options" />
+ <cc:attribute name="mandatory"/>
+ <cc:attribute name="readonly" />
+ <cc:attribute name="disabled"/>
+ <cc:attribute name="tabindex"/>
+ <cc:attribute name="format"/>
+ <cc:attribute name="minValue"/>
+ <cc:attribute name="maxValue"/>
+ <cc:attribute name="styleClass" />
+ <cc:attribute name="labelClass" />
+ <cc:attribute name="inputClass" />
+ <cc:attribute name="label" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <c:choose>
+ <c:when test="#{cc.attrs.custom == true}">
+ <cc:insertChildren/>
+ </c:when>
+ </c:choose>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/formGrid.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/formGrid.xhtml
new file mode 100644
index 00000000..eb07beb4
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/formGrid.xhtml
@@ -0,0 +1,35 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.FormGridTag">
+ <cc:attribute name="mode" />
+ <cc:attribute name="renderAutoId" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/input.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/input.xhtml
new file mode 100644
index 00000000..eb7d35b1
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/input.xhtml
@@ -0,0 +1,43 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.InputTag">
+ <cc:attribute name="column" required="true" />
+ <cc:attribute name="record" />
+ <cc:attribute name="value" />
+ <cc:attribute name="options" />
+ <cc:attribute name="mandatory"/>
+ <cc:attribute name="readonly" />
+ <cc:attribute name="disabled"/>
+ <cc:attribute name="tabindex"/>
+ <cc:attribute name="format"/>
+ <cc:attribute name="minValue"/>
+ <cc:attribute name="maxValue"/>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/label.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/label.xhtml
new file mode 100644
index 00000000..54c4fe9c
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/label.xhtml
@@ -0,0 +1,38 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.LabelTag">
+ <cc:attribute name="column" required="true" />
+ <cc:attribute name="format" />
+ <cc:attribute name="styleClass" />
+ <cc:attribute name="style" />
+ <cc:attribute name="colon" type="java.lang.Boolean"/>
+ <cc:attribute name="required" type="java.lang.Boolean"/>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ </cc:implementation>
+
+</html>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/link.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/link.xhtml
new file mode 100644
index 00000000..3e65ba3d
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/link.xhtml
@@ -0,0 +1,40 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.LinkTag">
+ <cc:attribute name="styleClass" />
+ <cc:attribute name="page" />
+ <cc:attribute name="idparam" />
+ <cc:attribute name="title" />
+ <cc:attribute name="action" method-signature="void method()" />
+ <cc:attribute name="actionListener" method-signature="void method(jakarta.faces.event.ActionEvent)" />
+ <cc:attribute name="disabled" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/mitem.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/mitem.xhtml
new file mode 100644
index 00000000..4752e852
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/mitem.xhtml
@@ -0,0 +1,37 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.MenuItemTag">
+ <cc:attribute name="menuId" required="true"/>
+ <cc:attribute name="page" required="true" />
+ <cc:attribute name="idparam"/>
+ <cc:attribute name="currentOnly" type="java.lang.Boolean" default="false" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/mlist.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/mlist.xhtml
new file mode 100644
index 00000000..60d5930d
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/mlist.xhtml
@@ -0,0 +1,37 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.MenuListTag">
+ <cc:attribute name="currentId" />
+ <cc:attribute name="currentClass"/>
+ <cc:attribute name="expandedClass"/>
+ <cc:attribute name="disabledClass"/>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/record.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/record.xhtml
new file mode 100644
index 00000000..d3222683
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/record.xhtml
@@ -0,0 +1,35 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.RecordTag">
+ <cc:attribute name="value" />
+ <cc:attribute name="readonly" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/select.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/select.xhtml
new file mode 100644
index 00000000..d644d35c
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/select.xhtml
@@ -0,0 +1,39 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.SelectTag">
+ <cc:attribute name="options" required="true" />
+ <cc:attribute name="allowNull"/>
+ <cc:attribute name="nullText"/>
+ <cc:attribute name="disabled" />
+ <cc:attribute name="update"/>
+ <cc:attribute name="listener" type="jakarta.el.MethodExpression"/>
+ <cc:clientBehavior name="change" event="change" targets="#{cc.clientId}:select" default="true"/>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/tabPage.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/tabPage.xhtml
new file mode 100644
index 00000000..f47fd512
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/tabPage.xhtml
@@ -0,0 +1,33 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.TabPageTag">
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/tabView.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/tabView.xhtml
new file mode 100644
index 00000000..060f7d79
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/tabView.xhtml
@@ -0,0 +1,37 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.TabViewTag">
+ <cc:attribute name="mode" />
+ <cc:attribute name="minHeight" />
+ <cc:attribute name="activeIndex" type="java.lang.Integer" />
+ <cc:attribute name="tabChangedListener" method-signature="void method(java.lang.Integer)"/>
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/title.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/title.xhtml
new file mode 100644
index 00000000..99532b2e
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/title.xhtml
@@ -0,0 +1,37 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.TitleTag">
+ <cc:attribute name="column" required="true" />
+ <cc:attribute name="format" />
+ <cc:attribute name="tag" />
+ <cc:attribute name="styleClass" />
+ <cc:attribute name="style" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ </cc:implementation>
+
+</html>
diff --git a/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/value.xhtml b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/value.xhtml
new file mode 100644
index 00000000..332dd020
--- /dev/null
+++ b/empire-db-examples/empire-db-example-jakarta-faces/src/main/webapp/resources/empire/value.xhtml
@@ -0,0 +1,41 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:h="http://java.sun.com/jsf/html"
+ xmlns:c="http://java.sun.com/jsp/jstl/core"
+ xmlns:cc="http://java.sun.com/jsf/composite">
+
+ <!-- Interface -->
+ <cc:interface componentType="org.apache.empire.component.ValueTag">
+ <cc:attribute name="column" required="true" />
+ <cc:attribute name="record" />
+ <cc:attribute name="format" />
+ <cc:attribute name="title" />
+ <cc:attribute name="tag" />
+ <cc:attribute name="styleClass" />
+ <cc:attribute name="style" />
+ <cc:attribute name="options" />
+ </cc:interface>
+
+ <!-- Implementation -->
+ <cc:implementation>
+ <cc:insertChildren/>
+ </cc:implementation>
+
+</html>
\ No newline at end of file
diff --git a/empire-db-examples/empire-db-example-jsf2/pom.xml b/empire-db-examples/empire-db-example-jsf2/pom.xml
index 91f9c91c..f413f5da 100644
--- a/empire-db-examples/empire-db-example-jsf2/pom.xml
+++ b/empire-db-examples/empire-db-example-jsf2/pom.xml
@@ -52,10 +52,12 @@
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
+ <version>2.2.20</version>
</dependency>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
+ <version>2.2.20</version>
</dependency>
-->
@@ -63,17 +65,19 @@
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-api</artifactId>
+ <version>2.3.10</version>
</dependency>
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
+ <version>2.3.10</version>
</dependency>
<!-- EL -->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
- <type>jar</type>
+ <version>3.0.0</version>
<scope>provided</scope>
</dependency>
diff --git a/empire-db-examples/pom.xml b/empire-db-examples/pom.xml
index 3dbc8e2f..0a55a437 100644
--- a/empire-db-examples/pom.xml
+++ b/empire-db-examples/pom.xml
@@ -36,6 +36,7 @@
<module>empire-db-example-advanced</module>
<module>empire-db-example-codegen</module>
<module>empire-db-example-jsf2</module>
+ <module>empire-db-example-jakarta-faces</module>
<module>empire-db-example-vue</module>
<module>empire-db-example-spring-boot</module>
</modules>
diff --git a/empire-db-jakarta-faces/.gitignore b/empire-db-jakarta-faces/.gitignore
new file mode 100644
index 00000000..c49244bd
--- /dev/null
+++ b/empire-db-jakarta-faces/.gitignore
@@ -0,0 +1,7 @@
+/target/
+/.settings/
+/.classpath
+/.project
+/.apt_generated/
+/.factorypath
+/.apt_generated_tests/
diff --git a/empire-db-jsf2/pom.xml b/empire-db-jakarta-faces/pom.xml
similarity index 72%
copy from empire-db-jsf2/pom.xml
copy to empire-db-jakarta-faces/pom.xml
index 78e1e234..c03644bc 100644
--- a/empire-db-jsf2/pom.xml
+++ b/empire-db-jakarta-faces/pom.xml
@@ -22,9 +22,9 @@
<groupId>org.apache.empire-db</groupId>
<version>3.1.1-SNAPSHOT</version>
</parent>
- <artifactId>empire-db-jsf2</artifactId>
+ <artifactId>empire-db-jakarta-faces</artifactId>
<packaging>jar</packaging>
- <name>Apache Empire-db Java Server Faces 2.x extensions</name>
+ <name>Apache Empire-db Jakarta Faces extensions</name>
<properties>
</properties>
@@ -36,31 +36,42 @@
</dependency>
<!-- Servlet API -->
<dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
+ <groupId>jakarta.servlet</groupId>
+ <artifactId>jakarta.servlet-api</artifactId>
+ <version>5.0.0</version>
+ <scope>provided</scope>
</dependency>
+ <!-- jakarta.faces -->
<dependency>
- <groupId>com.sun.faces</groupId>
- <artifactId>jsf-api</artifactId>
+ <groupId>jakarta.faces</groupId>
+ <artifactId>jakarta.faces-api</artifactId>
+ <version>3.0.0</version>
<scope>provided</scope>
</dependency>
- <!-- Mojarra -->
<dependency>
- <groupId>com.sun.faces</groupId>
- <artifactId>jsf-impl</artifactId>
+ <groupId>org.glassfish</groupId>
+ <artifactId>jakarta.faces</artifactId>
+ <version>3.0.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <!-- MyFaces -->
+ <dependency>
+ <groupId>org.apache.myfaces.core</groupId>
+ <artifactId>myfaces-api</artifactId>
+ <version>3.0.2</version>
<scope>provided</scope>
</dependency>
- <!-- MyFaces -->
<dependency>
<groupId>org.apache.myfaces.core</groupId>
<artifactId>myfaces-impl</artifactId>
+ <version>3.0.2</version>
<scope>provided</scope>
</dependency>
<!-- EL -->
<dependency>
- <groupId>javax.el</groupId>
- <artifactId>javax.el-api</artifactId>
- <type>jar</type>
+ <groupId>jakarta.el</groupId>
+ <artifactId>jakarta.el-api</artifactId>
+ <version>4.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/DBELResolver.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/DBELResolver.java
new file mode 100644
index 00000000..148283c9
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/DBELResolver.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import java.beans.FeatureDescriptor;
+import java.lang.reflect.Field;
+import java.util.Iterator;
+
+import jakarta.el.ELContext;
+import jakarta.el.ELResolver;
+import jakarta.faces.context.FacesContext;
+
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.data.RecordData;
+import org.apache.empire.db.DBColumnExpr;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.DBRowSet;
+import org.apache.empire.exceptions.NotSupportedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DBELResolver extends ELResolver
+{
+ private static final Logger log = LoggerFactory.getLogger(DBELResolver.class);
+
+ @Override
+ public Class<?> getCommonPropertyType(ELContext context, Object base)
+ {
+ if (base==null)
+ log.warn("DBELResolver:getCommonPropertyType is not implemented!");
+ return null;
+ }
+
+ @Override
+ public Class<?> getType(ELContext context, Object base, Object property)
+ {
+ if ((base instanceof DBRowSet) ||
+ (base instanceof DBDatabase) ||
+ (base==null && property.equals("db")))
+ log.warn("DBELResolver:getType is not implemented!");
+ return null;
+ }
+
+ @Override
+ public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext arg0, Object arg1)
+ {
+ log.warn("DBELResolver:getFeatureDescriptors is not implemented!");
+ return null;
+ }
+
+ @Override
+ public Object getValue(ELContext context, Object base, Object property)
+ {
+ // Resolve database, table/view or column
+ if (base instanceof DBRowSet)
+ { // Find matching column
+ String name = StringUtils.toString(property);
+ DBColumnExpr column = ((DBRowSet)base).getColumn(name);
+ if (column==null)
+ column = findExpressionField(base, name);
+ if (column!=null)
+ context.setPropertyResolved(true);
+ else
+ log.error("ELResolver error: Column '{}' cannot be resolved for table/view '{}'.", name.toUpperCase(), ((DBRowSet)base).getName());
+ // done
+ return column;
+ }
+ else if (base instanceof DBDatabase)
+ { // Lookup RowSet
+ String name = StringUtils.toString(property);
+ DBRowSet rset = ((DBDatabase)base).getRowSet(name);
+ if (rset!=null)
+ context.setPropertyResolved(true);
+ else
+ log.error("ELResolver error: Table/View '{}' cannot be resolved.", name.toUpperCase());
+ // done
+ return rset;
+ }
+ else if (base instanceof RecordData)
+ { // Lookup RowSet
+ String field= StringUtils.toString(property);
+ // field must start with a capital Letter
+ if (field==null || field.length()==0 || field.charAt(0)<'A' || field.charAt(0)>'Z')
+ return null;
+ // try to find field
+ int index = ((RecordData)base).getFieldIndex(field);
+ if (index<0)
+ { // not a field, it may be a property
+ log.warn("ELResolver warning: field '{}' not found in record .", field);
+ // not resolved, continue search
+ return null;
+ }
+ // Found! Return field value.
+ context.setPropertyResolved(true);
+ return ((RecordData)base).getValue(index);
+ }
+ else if (base==null)
+ { // LookupDatabase
+ String name = StringUtils.toString(property);
+ DBDatabase db = DBDatabase.findByIdentifier(name);
+ if (db!=null)
+ context.setPropertyResolved(true);
+ // done
+ return db;
+ }
+ return null;
+ }
+
+ public static DBColumnExpr findExpressionField(Object rowset, String property)
+ {
+ Class<?> c = rowset.getClass();
+ try
+ { // Find a matching field name
+ Field f = c.getField(property);
+ if (f==null)
+ return null;
+ Object v = f.get(rowset);
+ if (v==null)
+ { // invalid data type
+ log.error("ELResolver error: Field '{}.{}' is null.", c.getSimpleName(), property);
+ return null;
+ }
+ if (!(v instanceof DBColumnExpr))
+ { // invalid data type
+ log.error("ELResolver error: Field '{}.{}' is not a DBColumnExpr.", c.getSimpleName(), property);
+ return null;
+ }
+ return ((DBColumnExpr)v);
+ }
+ catch (SecurityException e)
+ {
+ log.error("ELResolver error: Unable to access field "+c.getSimpleName()+"."+property, e);
+ return null;
+ }
+ catch (NoSuchFieldException e)
+ {
+ return null;
+ }
+ catch (IllegalArgumentException e)
+ {
+ log.error("ELResolver error: Unable to access field "+c.getSimpleName()+"."+property, e);
+ return null;
+ }
+ catch (IllegalAccessException e)
+ {
+ log.error("ELResolver error: Unable to access field "+c.getSimpleName()+"."+property, e);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isReadOnly(ELContext context, Object base, Object property)
+ {
+ // is it our's?
+ if ((base instanceof DBRowSet) ||
+ (base instanceof DBDatabase) ||
+ (base==null && property.equals("db")))
+ { // read only
+ log.info("ELResolver: property {} is read only.", property);
+ return true;
+ }
+ // not our business
+ return false;
+ }
+
+ @Override
+ public void setValue(ELContext context, Object base, Object property, Object value)
+ {
+ if (isReadOnly(context, base, property))
+ {
+ RuntimeException e = new NotSupportedException(this, "setValue");
+ FacesContext.getCurrentInstance().getExternalContext().log(e.getMessage(), e);
+ throw e;
+ }
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesConfiguration.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesConfiguration.java
new file mode 100644
index 00000000..69dc62c5
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesConfiguration.java
@@ -0,0 +1,615 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import jakarta.el.ELResolver;
+import jakarta.faces.FactoryFinder;
+import jakarta.faces.application.Application;
+import jakarta.faces.application.NavigationHandler;
+import jakarta.faces.application.ProjectStage;
+import jakarta.faces.application.ViewHandler;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.context.ExternalContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.convert.Converter;
+import jakarta.faces.event.PhaseListener;
+import jakarta.faces.lifecycle.Lifecycle;
+import jakarta.faces.lifecycle.LifecycleFactory;
+import jakarta.faces.render.RenderKit;
+import jakarta.faces.render.RenderKitFactory;
+import jakarta.faces.render.Renderer;
+
+import org.apache.empire.commons.ClassUtils;
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.exceptions.InvalidOperationException;
+import org.apache.empire.exceptions.ItemExistsException;
+import org.apache.empire.exceptions.ItemNotFoundException;
+import org.apache.empire.exceptions.ObjectNotValidException;
+import org.apache.empire.jakarta.impl.FacesImplementation;
+import org.apache.empire.jakarta.impl.FacesImplementation.BeanStorageProvider;
+import org.apache.empire.jakarta.pages.PageNavigationHandler;
+import org.apache.empire.jakarta.pages.PagePhaseListener;
+import org.apache.empire.jakarta.pages.PagesELResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * FacesConfiguration
+ * Allows programmatic Faces configuration as an alternative to the faces-config.xml
+ * In order to provide custom configuration you must override the class WebAppStartupListener
+ * and put it in the faces-config.xml
+ * <pre>
+ * <system-event-listener-class>de.volkswagen.jsf.FacesStartupListener</system-event-listener-class>
+ * </pre>
+ * Further you must call the super constructor with a FacesConfiguration class like this:
+ * <pre>
+ * public FacesStartupListener()
+ * {
+ * super(FacesExtensions.class);
+ * }
+ * </pre>
+ */
+public class FacesConfiguration
+{
+ protected static final Logger log = LoggerFactory.getLogger(FacesConfiguration.class);
+
+ public final static String EMPIRE_COMPONENT_FAMILY = "org.apache.empire.component";
+
+ /*
+ * Initialized
+ */
+ private static boolean initialized = false;
+ public static boolean isInitialized()
+ {
+ return initialized;
+ }
+
+ /*
+ * Project Stage
+ * see org.apache.myfaces.application.ApplicationImpl
+ * How to set:
+ * set JVM-Parameter: "org.apache.myfaces.PROJECT_STAGE"
+ * set JNDI-Parameter: "java:comp/env/jsf/ProjectStage"
+ * set in web.inf:
+ * <context-param>
+ * <param-name>jakarta.faces.PROJECT_STAGE</param-name>
+ * <param-value>Development</param-value>
+ * </context-param>
+ */
+ private static ProjectStage projectStage;
+ public static ProjectStage getProjectStage()
+ {
+ if (projectStage==null)
+ throw new ObjectNotValidException(FacesConfiguration.class, "Not Initialized");
+ return projectStage;
+ }
+
+ /**
+ * Static Initializer
+ * @param clazz the configuration class
+ * @param startupContext the startupContext
+ */
+ public static <T extends FacesConfiguration> void initialize(Class<T> configClass, FacesContext startupContext, FacesImplementation facesImpl)
+ {
+ // crate and initialize
+ FacesConfiguration fConfig = ClassUtils.newInstance(configClass, FacesImplementation.class, facesImpl);
+ fConfig.initialize(startupContext);
+ }
+
+ /*
+ * Inject
+ */
+ protected final FacesImplementation facesImpl;
+ /*
+ * Temp Variables
+ */
+ protected ExternalContext externalContext;
+ protected Application application;
+ /*
+ * Lazy
+ */
+ private BeanStorageProvider beanStorage; // call getBeanStorageProvider()
+
+ public FacesConfiguration(FacesImplementation facesImpl)
+ {
+ this.facesImpl = facesImpl;
+ }
+
+ public final void initialize(FacesContext startupContext)
+ {
+ if (initialized)
+ throw new InvalidOperationException("FacesConfiguration already initialized!");
+ try
+ { // Set temporary variables
+ this.externalContext = startupContext.getExternalContext();
+ this.application = startupContext.getApplication();
+ this.beanStorage = null;
+
+ projectStage = application.getProjectStage();
+ log.info("Initializing Faces Configuration for ProjectStage {}", projectStage.name());
+
+ // Init everything
+ initAll(startupContext);
+
+ // done
+ log.info("Faces Configuration complete.");
+ }
+ finally
+ { // cleanup
+ this.beanStorage = null;
+ this.application = null;
+ this.externalContext = null;
+ this.facesImpl.configComplete();
+ // set initialized (even if Exception has been thrown)
+ initialized = true;
+ }
+ }
+
+ /*
+ * Overrideable methods
+ */
+
+ protected void initAll(FacesContext context)
+ {
+ log.debug("Init FacesParams...");
+ initFacesParams();
+
+ log.debug("Init Factories...");
+ initFactories();
+
+ log.debug("Init NavigationHandler...");
+ initNavigationHandler();
+
+ log.debug("Init ResourceHandler...");
+ initResourceHandler();
+
+ log.debug("Registrating Converters...");
+ initConverters();
+
+ log.debug("Registrating EL-Resolvers...");
+ initElResolvers();
+
+ log.debug("Registrating Lifecycle...");
+ initLifecycle(new LifecycleUpdater());
+
+ log.debug("Registrating Search Expression Resolvers...");
+ initSearchExpressionResolvers();
+
+ log.debug("Registrating Components...");
+ initComponents();
+
+ log.debug("Registrating Renderers...");
+ initRenderers(new RenderKitUpdater(getApplicationRenderKit(context)));
+
+ log.debug("Registrating Managed Beans...");
+ initManagedBeans();
+
+ log.debug("Registrating Controls...");
+ initControls();
+ }
+
+ protected void initFacesParams()
+ { // set params
+ setFacesInitParam(ViewHandler.FACELETS_SKIP_COMMENTS_PARAM_NAME, true);
+ }
+
+ protected void initFactories()
+ {
+ // Noting yet
+ /* example
+ * log.info("Setting Factory {}", FactoryFinder.TAG_HANDLER_DELEGATE_FACTORY);
+ FactoryFinder.setFactory(FactoryFinder.TAG_HANDLER_DELEGATE_FACTORY, CustomTagHandlerDelegateFactory.class.getName());
+ // (check)
+ // TagHandlerDelegateFactory tagHandlerFactory = (TagHandlerDelegateFactory)FactoryFinder.getFactory(FactoryFinder.TAG_HANDLER_DELEGATE_FACTORY);
+ // log.info(tagHandlerFactory.getClass().getName());
+ */
+ }
+
+ protected void initNavigationHandler()
+ {
+ NavigationHandler wrapped = application.getNavigationHandler();
+ if (wrapped instanceof PageNavigationHandler)
+ return; // Already set
+ // replace
+ log.info("Setting NavigationHandler to {}", PageNavigationHandler.class.getName());
+ application.setNavigationHandler(new PageNavigationHandler(wrapped));
+ }
+
+ protected void initResourceHandler()
+ {
+ // Not implemented
+ // application.setResourceHandler(new MyResourceHandler(wrapped));
+ }
+
+ protected void initConverters()
+ {
+ // Noting
+ }
+
+ protected void initElResolvers()
+ {
+ // add
+ addELResolver(DBELResolver.class);
+ addELResolver(PagesELResolver.class);
+ }
+
+ protected void initLifecycle(LifecycleUpdater lcu)
+ {
+ lcu.addPhaseListener(FacesRequestPhaseListener.class);
+ lcu.addPhaseListener(PagePhaseListener.class);
+ }
+
+ protected void initSearchExpressionResolvers()
+ {
+ // Nothing
+ // SearchExpressionResolverFactory.registerResolver("@fragment", new FragmentExpressionResolver());
+ }
+
+ protected void initComponents()
+ {
+ // Empire Components
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.ControlTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.InputTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.FormGridTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.LabelTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.LinkTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.MenuItemTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.MenuListTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.RecordTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.SelectTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.TabPageTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.TabViewTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.TitleTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.UnitTag.class);
+ addComponent(EMPIRE_COMPONENT_FAMILY, org.apache.empire.jakarta.components.ValueTag.class);
+ }
+
+ protected void initRenderers(RenderKitUpdater rku)
+ {
+ // Noting
+ // rku.replace("jakarta.faces.Input", "jakarta.faces.Text", FacesTextInputRenderer.class);
+ }
+
+ protected void initManagedBeans()
+ {
+ // Nothing
+ // addManagedBean(ConfirmPopup.MANAGED_BEAN_NAME, ConfirmPopup.class, ConfirmPopup.MANAGED_BEAN_SCOPE);
+ }
+
+ protected void initControls()
+ {
+ // Not implemented
+ // InputControlManager.registerControl(new CustomCheckboxInputControl());
+ }
+
+ /*
+ * Helpers
+ */
+
+ protected BeanStorageProvider getBeanStorageProvider()
+ {
+ if (this.beanStorage==null)
+ this.beanStorage = facesImpl.getBeanStorageProvider(externalContext);
+ return this.beanStorage;
+ }
+
+ protected RenderKit getApplicationRenderKit(FacesContext context)
+ {
+ String renderKitId = StringUtils.coalesce(application.getDefaultRenderKitId(), RenderKitFactory.HTML_BASIC_RENDER_KIT);
+ return ((RenderKitFactory)FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY)).getRenderKit(context, renderKitId);
+ }
+
+ protected void setFacesInitParam(String paramName, Object paramValue, boolean overwriteExisting)
+ {
+ // special case
+ if (ProjectStage.PROJECT_STAGE_PARAM_NAME.equals(paramName))
+ throw new InvalidOperationException(ProjectStage.PROJECT_STAGE_PARAM_NAME+" cannot be changed!");
+ // get map
+ String paramVal = StringUtils.toString(paramValue);
+ String orgValue = this.externalContext.getInitParameter(paramName);
+ if (ObjectUtils.compareEqual(paramVal, orgValue))
+ return; // No change
+ if (ObjectUtils.isNotEmpty(orgValue) && !overwriteExisting)
+ { // Ingnore
+ log.info("Ignoring FacesParam \"{}\" (\"{}\"). Keeping current value of \"{}\"", paramName, paramVal, orgValue);
+ return;
+ }
+ if (orgValue!=null)
+ log.info("Setting FacesParam \"{}\" to \"{}\". Original value was \"{}\"", paramName, paramVal, orgValue);
+ else
+ log.info("Setting FacesParam \"{}\" to \"{}\".", paramName, paramVal);
+ // add to map
+ @SuppressWarnings("unchecked")
+ Map<String,String> paramMap = this.externalContext.getInitParameterMap();
+ paramMap.put(paramName, paramVal);
+ }
+
+ protected void setFacesInitParam(String paramName, Object paramValue)
+ {
+ setFacesInitParam(paramName, paramValue, false);
+ }
+
+ protected void setFacesInitParam(Enum<?> paramName, Object paramValue)
+ {
+ setFacesInitParam(paramName.toString(), paramValue, false);
+ }
+
+ protected void addConverter(Class<?> targetClass, Class<? extends Converter> converterClass)
+ {
+ log.info("Adding Type-Converter for type \"{}\" using {}", targetClass.getName(), converterClass.getName());
+ application.addConverter(targetClass, converterClass.getName());
+ }
+
+ protected void addComponent(String componentFamily, Class<? extends UIComponent> clazz)
+ {
+ String type = StringUtils.concat(componentFamily, ".", clazz.getSimpleName());
+ log.info("Adding component type \"{}\" using {}", type, clazz.getName());
+ application.addComponent(type, clazz.getName());
+ }
+
+ protected void addManagedBean(String beanName, Class<?> beanClass, String scope)
+ {
+ facesImpl.registerManagedBean(beanName, beanClass.getName(), scope);
+ }
+
+ protected void replaceComponent(String componentType, Class<? extends UIComponent> overrideComponent)
+ {
+ if (StringUtils.isEmpty(componentType))
+ throw new InvalidArgumentException("componentType", componentType);
+ // check
+ checkComponentTypeExists(componentType);
+ log.info("Replacing component type \"{}\" with {}", componentType, overrideComponent.getName());
+ application.addComponent(componentType, overrideComponent.getName());
+ }
+
+ protected void replaceComponent(Class<? extends UIComponent> componentClassWithType, Class<? extends UIComponent> overrideComponent)
+ {
+ String componentType = (String) ClassUtils.getFieldValue(componentClassWithType, null, "COMPONENT_TYPE", true);
+ replaceComponent(componentType, overrideComponent);
+ }
+
+ protected void replaceComponent(String componentFamily, Class<? extends UIComponent> componentClassToReplace, Class<? extends UIComponent> overrideComponent)
+ {
+ String componentType = StringUtils.concat(componentFamily, ".", componentClassToReplace.getSimpleName());
+ replaceComponent(componentType, overrideComponent);
+ }
+
+ protected void checkComponentTypeExists(String componentType)
+ {
+ Iterator<String> types = application.getComponentTypes();
+ while (types.hasNext())
+ {
+ String type = types.next();
+ if (componentType.equals(type))
+ return; // found;
+ }
+ throw new ItemNotFoundException("Component-Type: "+componentType);
+ }
+
+ protected void addELResolver(Class<? extends ELResolver> resolverClass)
+ {
+ boolean added = facesImpl.registerElResolver(resolverClass);
+ if (added)
+ log.info("Adding FacesConfigElResolver {}", resolverClass.getName());
+ }
+
+ /*
+ * list
+ */
+ protected void listCompoennts()
+ {
+ ConfigTypeList list = new ConfigTypeList("Component-Types");
+ Iterator<String> types = application.getComponentTypes();
+ while (types.hasNext())
+ {
+ String componentType = types.next();
+ // log.info("Renderer-Family: {} Type {}", componentFamily, rendererType);
+ list.addItem(componentType);
+ }
+ log.info(list.toString());
+ }
+
+ /**
+ * RenderKitReplacer
+ * @author doebele
+ */
+ protected class RenderKitUpdater
+ {
+ private final RenderKit renderKit;
+ public RenderKitUpdater(RenderKit renderKit)
+ {
+ this.renderKit = renderKit;
+ }
+
+ public RenderKit getRenderKit()
+ {
+ return renderKit;
+ }
+
+ public void listAll()
+ { // list all
+ ConfigTypeList list = new ConfigTypeList("Renderer-Types");
+ Iterator<String> families = renderKit.getComponentFamilies();
+ while (families.hasNext())
+ {
+ String componentFamily = families.next();
+ Iterator<String> types = renderKit.getRendererTypes(componentFamily);
+ while (types.hasNext())
+ {
+ String rendererType = types.next();
+ // log.info("Renderer-Family: {} Type {}", componentFamily, rendererType);
+ list.addItem(componentFamily, rendererType);
+ }
+ }
+ log.info(list.toString());
+ }
+
+ public void add(String componentFamily, String rendererType, Class<? extends Renderer> rendererClass)
+ {
+ Renderer check = findRenderer(componentFamily, rendererType);
+ if (check!=null)
+ { if (check.getClass().equals(rendererClass))
+ return; // already there
+ // Another renderer exists
+ throw new ItemExistsException(StringUtils.concat(componentFamily, " / ", rendererType));
+ }
+ // add
+ log.info("Adding Renderer type \"{}\" using {}", rendererType, rendererClass.getName());
+ renderKit.addRenderer(componentFamily, rendererType, ClassUtils.newInstance(rendererClass));
+ }
+
+ public void replace(String componentFamily, String rendererType, Class<? extends Renderer> replaceClass)
+ {
+ // checkRenderTypeExists(componentFamily, rendererType);
+ Renderer check = findRenderer(componentFamily, rendererType);
+ if (check==null)
+ throw new ItemNotFoundException(StringUtils.concat(componentFamily, " / ", rendererType));
+ if (check.getClass().equals(replaceClass))
+ return; // Already replaced
+ // replace
+ log.info("Replacing Renderer type \"{}\" with class {}", rendererType, replaceClass.getName());
+ renderKit.addRenderer(componentFamily, rendererType, ClassUtils.newInstance(replaceClass));
+ }
+
+ /*
+ public void replace(Class<? extends CoreRenderer> orgClass, Class<? extends CoreRenderer> replaceClass)
+ {
+ String name = orgClass.getName();
+ int sep = name.lastIndexOf('.');
+ replace(name.substring(0, sep), name.substring(sep+1), replaceClass);
+ }
+ */
+
+ private Renderer findRenderer(String componentFamily, String rendererType)
+ {
+ // list all
+ Iterator<String> families = renderKit.getComponentFamilies();
+ while (families.hasNext())
+ {
+ String family = families.next();
+ if (componentFamily.equals(family)) {
+ Iterator<String> types = renderKit.getRendererTypes(family);
+ while (types.hasNext())
+ {
+ String type = types.next();
+ if (rendererType.equals(type))
+ return renderKit.getRenderer(componentFamily, rendererType); // found
+ }
+ }
+ }
+ return null;
+ }
+
+ }
+
+ /**
+ * LifecycleUpdater
+ * @author doebele
+ */
+ protected class LifecycleUpdater
+ {
+ private final Lifecycle lifecycle;
+ private PhaseListener[] phaseListeners;
+
+ public LifecycleUpdater()
+ {
+ // The DEFAULT Lifecycle
+ LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
+ this.lifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
+ this.phaseListeners = lifecycle.getPhaseListeners();
+ }
+
+ public void listAll()
+ {
+ ConfigTypeList list = new ConfigTypeList("Phase-Listeners");
+ this.phaseListeners = lifecycle.getPhaseListeners();
+ for (PhaseListener pl : phaseListeners)
+ {
+ list.addItem(pl.getClass(), pl.getPhaseId());
+ }
+ log.info(list.toString());
+ }
+
+ public void addPhaseListener(Class<? extends PhaseListener> phaseListenerClass)
+ {
+ for (PhaseListener pl : phaseListeners)
+ {
+ if (pl.getClass().equals(phaseListenerClass))
+ return; // already there
+ }
+ // Not found: Create and Append
+ log.info("Adding Lifecycle PhaseListener {}", phaseListenerClass.getName());
+ PhaseListener listener = ClassUtils.newInstance(phaseListenerClass);
+ // Add to bean storage
+ getBeanStorageProvider().injectBean(listener);
+ // Add to lifecycle
+ lifecycle.addPhaseListener(listener);
+ // refresh
+ // this.phaseListeners = lifecycle.getPhaseListeners();
+ }
+ }
+
+ /**
+ * ConfigTypeList
+ * @author doebele
+ */
+ protected static class ConfigTypeList
+ {
+ private static final String CRLF = "\r\n";
+ private static final String TAB = "\t";
+ private final StringBuilder b;
+ public ConfigTypeList(String listName)
+ {
+ this.b = new StringBuilder(200);
+ b.append(listName);
+ b.append(":");
+ b.append(CRLF);
+ }
+ public void addItem(Object item, Object... more)
+ {
+ b.append(TAB);
+ b.append(toString(item));
+ for (int i=0; i<more.length; i++)
+ {
+ b.append(TAB);
+ b.append(toString(more[i]));
+ }
+ b.append(CRLF);
+ }
+ protected String toString(Object o)
+ {
+ if (o instanceof Class<?>)
+ return ((Class<?>)o).getName();
+ if (o instanceof Enum<?>)
+ return ((Enum<?>)o).name();
+ return String.valueOf(o);
+ }
+ @Override
+ public String toString()
+ {
+ return this.b.toString();
+ }
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesRequestPhaseListener.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesRequestPhaseListener.java
new file mode 100644
index 00000000..27a48a87
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesRequestPhaseListener.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.event.PhaseEvent;
+import jakarta.faces.event.PhaseId;
+import jakarta.faces.event.PhaseListener;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FacesRequestPhaseListener implements PhaseListener
+{
+ private static final long serialVersionUID = 1L;
+ final Logger log = LoggerFactory.getLogger(FacesRequestPhaseListener.class);
+
+ public FacesRequestPhaseListener()
+ {
+ // foo
+ }
+
+ @Override
+ public PhaseId getPhaseId()
+ {
+ return PhaseId.ANY_PHASE;
+ }
+
+ @Override
+ public void beforePhase(PhaseEvent pe)
+ {
+ // Only when rendering the response
+ if (pe.getPhaseId() != PhaseId.RENDER_RESPONSE)
+ return;
+ // Add header information
+ FacesContext facesContext = pe.getFacesContext();
+ HttpServletResponse response = (HttpServletResponse) facesContext.getExternalContext().getResponse();
+ response.addHeader("Pragma", "no-cache");
+ response.addHeader("Cache-Control", "no-cache");
+ response.addHeader("Cache-Control", "no-store");
+ response.addHeader("Cache-Control", "must-revalidate");
+ }
+
+ /**
+ * VERY VERY IMPORTANT FUNCTION: Release Connection on End of request!
+ **/
+ @Override
+ public void afterPhase(PhaseEvent pe)
+ {
+ // Check Response complete
+ FacesContext ctx = pe.getFacesContext();
+ if (pe.getPhaseId() == PhaseId.RENDER_RESPONSE || ctx.getResponseComplete())
+ { // Cleanup when response is complete
+ WebApplication app = WebApplication.getInstance();
+ if (app!=null)
+ app.onRequestComplete(ctx);
+ else
+ log.warn("No WebApplication available to complete and cleanup request. Please create a managed bean of name "+WebApplication.APPLICATION_BEAN_NAME);
+ }
+
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesUtils.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesUtils.java
new file mode 100644
index 00000000..edbd5601
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/FacesUtils.java
@@ -0,0 +1,544 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import jakarta.el.ValueExpression;
+import jakarta.faces.application.FacesMessage;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIInput;
+import jakarta.faces.component.UIViewRoot;
+import jakarta.faces.context.ExternalContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.event.ActionEvent;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.data.Column;
+import org.apache.empire.exceptions.EmpireException;
+import org.apache.empire.exceptions.InternalException;
+import org.apache.empire.exceptions.ItemNotFoundException;
+import org.apache.empire.jakarta.impl.FacesImplementation;
+import org.apache.empire.jakarta.pages.Page;
+import org.apache.empire.jakarta.pages.PageDefinition;
+import org.apache.empire.jakarta.pages.PageOutcome;
+import org.apache.empire.jakarta.utils.ParameterMap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FacesUtils
+{
+
+ private static final Logger log = LoggerFactory.getLogger(FacesUtils.class);
+
+ public static final String SKIP_INPUT_VALIDATION_PARAM = "empire.jsf.input.skipValidation";
+
+ /* App */
+
+ public static WebApplication getWebApplication()
+ {
+ return WebApplication.getInstance();
+ }
+
+ public static FacesImplementation getFacesImplementation()
+ {
+ return getWebApplication().getFacesImplementation();
+ }
+
+ public static FacesContext getContext()
+ {
+ return FacesContext.getCurrentInstance();
+ }
+
+ /* Session */
+
+ public static Map<String, Object> getSessionMap(final FacesContext fc)
+ {
+ return fc.getExternalContext().getSessionMap();
+ }
+
+ /* Request */
+
+ public static HttpServletRequest getHttpRequest(final FacesContext fc)
+ {
+ return (HttpServletRequest) fc.getExternalContext().getRequest();
+ }
+
+ public static String getRequestUri(FacesContext context)
+ {
+ ExternalContext ec = context.getExternalContext();
+ Object req = (ec!=null ? ec.getRequest() : null);
+ if (req instanceof HttpServletRequest) {
+ HttpServletRequest hr = (HttpServletRequest)req;
+ return hr.getRequestURI();
+ }
+ return "{No HttpServletRequest}";
+ }
+
+ public static String getRequestUserAgent(FacesContext context)
+ {
+ ExternalContext ec = context.getExternalContext();
+ Object req = (ec!=null ? ec.getRequest() : null);
+ if (req instanceof HttpServletRequest)
+ { // get User-Agent
+ String agent = ((HttpServletRequest)req).getHeader("user-agent");
+ return agent;
+ }
+ return "{No HttpServletRequest}";
+ }
+
+ public static String getRequestContextPath()
+ {
+ ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
+ return (ec!=null ? ec.getRequestContextPath() : null);
+ }
+
+ public static Object getRequestAttribute(final String key)
+ {
+ FacesContext fc = getContext();
+ return getRequestAttribute(fc, key);
+ }
+
+ public static Object getRequestAttribute(final FacesContext fc, final String key)
+ {
+ return getHttpRequest(fc).getAttribute(key);
+ }
+
+ public static <T> T getRequestAttribute(final FacesContext fc, final String key, Class<T> clazz)
+ {
+ Object requestValue = getRequestAttribute(fc, key);
+ return (requestValue != null ? ObjectUtils.convert(clazz, requestValue) : null);
+ }
+
+ public static void setRequestAttribute(final String key, Object value)
+ {
+ FacesContext fc = getContext();
+ setRequestAttribute(fc, key, value);
+ }
+
+ public static void setRequestAttribute(final FacesContext fc, final String key, Object value)
+ {
+ if (value!=null)
+ getHttpRequest(fc).setAttribute(key, value);
+ else
+ getHttpRequest(fc).removeAttribute(key);
+ }
+
+ public static String getRequestParam(final FacesContext context, final String param)
+ {
+ return context.getExternalContext().getRequestParameterMap().get(param);
+ }
+
+ public static String getRequestParam(final String key)
+ {
+ FacesContext fc = getContext();
+ return getRequestParam(fc, key);
+ }
+
+ public static boolean isSkipInputValidation(final FacesContext fc)
+ {
+ // Skip validate
+ String val = FacesUtils.getRequestParam(fc, SKIP_INPUT_VALIDATION_PARAM);
+ return (val!=null && ObjectUtils.getBoolean(val));
+ }
+
+ public static boolean isSkipInputValidation()
+ {
+ FacesContext fc = getContext();
+ return isSkipInputValidation(fc);
+ }
+
+ public static <T> T getActionEventAttribute(ActionEvent ae, String name, Class<T> clazz)
+ {
+ Object item = ae.getComponent().getAttributes().get(name);
+ return ObjectUtils.convert(clazz, item);
+ }
+
+ /* Navigation */
+
+ public static void redirectDirectly(final FacesContext fc, final String url)
+ {
+ try
+ { // log
+ if (log.isDebugEnabled())
+ log.debug("Redirecting directly to {}.", url);
+ if (fc.getResponseComplete())
+ log.warn("Redirecting although response is already complete!");
+ // redirectDirectly
+ fc.getExternalContext().redirect(url);
+ fc.responseComplete();
+ }
+ catch (IOException e)
+ {
+ log.error("Unable to redirect to " + url);
+ }
+ }
+
+ public static void redirectDirectly(final FacesContext fc, final PageOutcome outcome)
+ {
+ String ctxPath = fc.getExternalContext().getRequestContextPath();
+ String pageURI = ctxPath + outcome.toString();
+ FacesUtils.redirectDirectly(fc, pageURI);
+ }
+
+ public static void redirectDirectly(final FacesContext fc, final PageDefinition page)
+ {
+ FacesUtils.redirectDirectly(fc, page.getOutcome());
+ }
+
+ /* Connection
+
+ public DBContext getDBContext(final FacesContext fc, DBDatabase db)
+ {
+ return getWebApplication().getDBContextForRequest(fc, db);
+ }
+
+ public void releaseDBContext(final FacesContext fc, DBDatabase db, boolean commit)
+ {
+ getWebApplication().releaseDBContext(fc, db, commit);
+ }
+
+ public void releaseAllDBContexts(final FacesContext fc, boolean commit)
+ {
+ getWebApplication().releaseAllDBContexts(fc, commit);
+ }
+ */
+
+ /* Pages */
+
+ @SuppressWarnings("unchecked")
+ public static <T extends Page> T getPage(final FacesContext fc)
+ {
+ UIViewRoot vr = fc.getViewRoot();
+ if (vr==null)
+ throw new ItemNotFoundException("ViewRoot");
+ // find page
+ Page page = (Page)vr.getViewMap().get("page");
+ if (page==null)
+ throw new ItemNotFoundException("page");
+ // ok
+ return (T)page;
+ }
+
+ /* Parameter-map */
+ public static final String PARAMETER_MAP_ATTRIBUTE = "PARAMETER_MAP";
+
+ public static final String PARAMETER_MAP_CLASS_ATTRIBUTE = "PARAMETER_MAP_CLASS";
+
+ public static void setParameterMapClass(final FacesContext fc, Class<? extends ParameterMap>clazz)
+ {
+ Map<String, Object> am = fc.getExternalContext().getApplicationMap();
+ am.put(PARAMETER_MAP_CLASS_ATTRIBUTE, clazz);
+ }
+
+ public static ParameterMap getParameterMap(final FacesContext fc)
+ {
+ Map<String, Object> sm = fc.getExternalContext().getSessionMap();
+ ParameterMap pm = (ParameterMap)sm.get(PARAMETER_MAP_ATTRIBUTE);
+ if (pm==null)
+ { try
+ { // Create Paramter Map
+ Map<String, Object> am = fc.getExternalContext().getApplicationMap();
+ Object pmClass = am.get(PARAMETER_MAP_CLASS_ATTRIBUTE);
+ if (pmClass instanceof Class<?>) {
+ // ParamterMapClass provided as Object
+ pm = (ParameterMap)((Class<?>)pmClass).newInstance();
+ }
+ else if (pmClass instanceof String) {
+ // ParamterMapClass provided as String
+ pm = (ParameterMap)Class.forName((String)pmClass).newInstance();
+ }
+ else {
+ // not provided, use default
+ pm = new ParameterMap();
+ }
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new InternalException(e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new InternalException(e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new InternalException(e);
+ }
+ // put on session
+ sm.put(PARAMETER_MAP_ATTRIBUTE, pm);
+ }
+ return pm;
+ }
+
+ public static ParameterMap getParameterMap()
+ {
+ return getParameterMap(getContext());
+ }
+
+ /* PageResource-map */
+ public static final String PAGE_RESOURCE_MAP_ATTRIBUTE = "pageResources"; /** use el-expression: #{pageResources.xxx} **/
+
+ /**
+ * returns the page resource map
+ * @param fc the faces context
+ * @return the ressource map
+ */
+ @SuppressWarnings("unchecked")
+ public static Map<String, Object> getPageResourceMap(final FacesContext fc)
+ {
+ Map<String, Object> sm = fc.getExternalContext().getSessionMap();
+ Map<String, Object> rm = (Map<String, Object>)sm.get(PAGE_RESOURCE_MAP_ATTRIBUTE);
+ if (rm==null)
+ { rm = new HashMap<String, Object>();
+ sm.put(PAGE_RESOURCE_MAP_ATTRIBUTE, rm);
+ }
+ return rm;
+ }
+
+ public static Map<String, Object> getPageResourceMap()
+ {
+ return getPageResourceMap(getContext());
+ }
+
+ /* Managed Beans */
+
+ public static Object getManagedBean(final FacesContext fc, final String name)
+ {
+ if (fc == null)
+ {
+ throw new NullPointerException("context must not be null");
+ }
+ if (name == null)
+ {
+ throw new NullPointerException("name must not be null");
+ }
+ /*
+ final ELContext elcontext = fc.getELContext();
+ final Application application = fc.getApplication();
+ return application.getELResolver().getValue(elcontext, null, name);
+ */
+ return getWebApplication().getFacesImplementation().getManagedBean(name, fc);
+ }
+
+ public static <T> T getManagedBean(final FacesContext fc, final Class<T> cls, final String name)
+ {
+ @SuppressWarnings("unchecked")
+ T bean = (T)getManagedBean(fc, name);
+ if (bean==null)
+ { log.warn("Managed Bean {} ist not available.", name);
+ throw new ItemNotFoundException(name);
+ }
+ return bean;
+ }
+
+ public static <T> T getManagedBean(final FacesContext fc, final Class<T> cls)
+ {
+ String name = cls.getName();
+ int i = name.lastIndexOf('.')+1;
+ name = name.substring(i, i+1).toLowerCase()+name.substring(i+1);
+ return getManagedBean(fc, cls, name);
+ }
+
+ public static <T> T getManagedBean(final Class<T> cls)
+ {
+ FacesContext fc = getContext();
+ return getManagedBean(fc, cls);
+ }
+
+ /*
+ @SuppressWarnings("unchecked")
+ public static <T> T findBean(String beanName) {
+ FacesContext context = FacesContext.getCurrentInstance();
+ return (T) context.getApplication().evaluateExpressionGet(context, "#{" + beanName + "}", Object.class);
+ }
+ */
+
+ /* file */
+
+ public static String getRealPath(final FacesContext fc, String path)
+ {
+ return (((ServletContext)fc.getExternalContext().getContext())).getRealPath(path);
+ }
+
+ public static String getFilePath(final FacesContext fc, String path, String file)
+ {
+ String realPath = getRealPath(fc, path);
+ return realPath.endsWith(File.separator) ? realPath+file : realPath + File.separator + file ;
+ }
+
+ /* Messages */
+
+ public static TextResolver getTextResolver(final FacesContext fc)
+ {
+ return getWebApplication().getTextResolver(fc);
+ }
+
+ public static String getMessage(final FacesContext fc, String key)
+ {
+ return getTextResolver(fc).resolveKey(key);
+ }
+
+ public static String getMessage(String messageKey)
+ {
+ return getMessage(getContext(), messageKey);
+ }
+
+ public static String formatMessage(String msgKey, Object... params)
+ {
+ TextResolver tr = getTextResolver(FacesContext.getCurrentInstance());
+ String pattern = tr.resolveKey(msgKey);
+ return MessageFormat.format(pattern, params);
+ }
+
+ /*
+ public static void addInfoMessage(FacesContext fc, String clientId, String msg)
+ {
+ fc.addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_INFO, msg, msg));
+ }
+
+ public static void addInfoMessage(FacesContext fc, String msg)
+ {
+ addInfoMessage(fc, null, msg);
+ }
+
+ public static void addWarnMessage(FacesContext fc, String clientId, String msg)
+ {
+ fc.addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_WARN, msg, msg));
+ }
+
+ public static void addWarnMessage(FacesContext fc, String msg)
+ {
+ addWarnMessage(fc, null, msg);
+ }
+
+ public static void addErrorMessage(FacesContext fc, String clientId, String msg)
+ {
+ fc.addMessage(clientId, new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg));
+ }
+
+ public static void addErrorMessage(FacesContext fc, String msg)
+ {
+ addErrorMessage(fc, null, msg);
+ }
+ */
+
+ /*
+ * indicates whether submitted values in InputControl should be cleared or preserved.
+ * Default is true.
+ * @param fc the faces context
+ * @return true if the submitted values should be cleared or false if they shold be preserved
+ public static boolean isClearSubmittedValues(FacesContext fc)
+ {
+ Object validate = fc.getExternalContext().getRequestMap().get("CLEAR_SUBMITTED_VALUES");
+ return (validate!=null ? ObjectUtils.getBoolean(validate) : false);
+ }
+
+ public static void setClearSubmittedValues(FacesContext fc, boolean validate)
+ {
+ fc.getExternalContext().getRequestMap().put("CLEAR_SUBMITTED_VALUES", validate);
+ }
+ */
+
+ public static FacesMessage getFacesErrorMessage(FacesContext fc, Throwable t)
+ {
+ if (!(t instanceof EmpireException))
+ t = new InternalException(t);
+ // Get Message
+ TextResolver tr = getWebApplication().getTextResolver(fc);
+ String msg = tr.getExceptionMessage((EmpireException)t);
+ // create Faces Message
+ return new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, null);
+ }
+
+ public static void redirectFromError(Page page, Throwable t)
+ {
+ PageOutcome pageTarget = page.getPageDefinition().getOutcome();
+ // throw new InternalException(e);
+ FacesContext fc = FacesUtils.getContext();
+ boolean committed = fc.getExternalContext().isResponseCommitted();
+ if (committed)
+ { log.warn("Cannot redirect to {} from an already committed response! Error is {}.", pageTarget, t.getMessage());
+ return;
+ }
+ // redirect to target page
+ FacesMessage facesMsg = getFacesErrorMessage(fc, t);
+ ExternalContext ec = fc.getExternalContext();
+ ec.getSessionMap().put(Page.SESSION_MESSAGE, facesMsg);
+ // redirect
+ FacesUtils.redirectDirectly(fc, pageTarget);
+ }
+
+
+ /* Component search */
+ public static UIInput findInputComponent(UIComponent parent, Column column)
+ {
+ // Get Children
+ Iterator<UIComponent> children = parent.getFacetsAndChildren();
+ while (children.hasNext())
+ { // Iterate through all children
+ UIComponent nextChild = children.next();
+ log.info("Checking child {}", nextChild.getClass().getName());
+ if (nextChild instanceof UIInput)
+ { // Check Column Attribute
+ Object col = nextChild.getAttributes().get("column");
+ if (col==null)
+ { // Check ID
+ ValueExpression ve = nextChild.getValueExpression("column");
+ if (ve!=null)
+ log.warn("TODO: evaluate Value Expression!");
+ }
+ else if (column.equals(col))
+ { // check form
+ return (UIInput)nextChild;
+ }
+ }
+ else
+ { // recurse children and facets
+ UIInput input = findInputComponent(nextChild, column);
+ if (input!=null)
+ return input;
+ }
+ }
+ // Not found
+ return null;
+ }
+
+ public static UIInput findInputComponent(FacesContext fc, String formId, Column column)
+ {
+ UIComponent parent = fc.getViewRoot();
+ if (StringUtils.isNotEmpty(formId))
+ {
+ parent = WebApplication.findChildComponent(parent, formId);
+ if (parent==null)
+ return null; // not found
+ }
+ // find
+ return findInputComponent(parent, column);
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/TextResolver.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/TextResolver.java
new file mode 100644
index 00000000..3b3a60ac
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/TextResolver.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import java.util.Locale;
+
+public interface TextResolver
+{
+ public static String MSG_KEY_INDICATOR = "!";
+
+ Locale getLocale();
+
+ String resolveKey(String key);
+
+ String resolveText(String text);
+
+ String getExceptionMessage(Exception e);
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebAppStartupListener.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebAppStartupListener.java
new file mode 100644
index 00000000..fccaa425
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebAppStartupListener.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import jakarta.faces.application.Application;
+import jakarta.faces.context.ExternalContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.event.AbortProcessingException;
+import jakarta.faces.event.PostConstructApplicationEvent;
+import jakarta.faces.event.PreDestroyApplicationEvent;
+import jakarta.faces.event.SystemEvent;
+import jakarta.faces.event.SystemEventListener;
+import jakarta.servlet.ServletContext;
+
+import org.apache.empire.jakarta.impl.FacesImplementation;
+import org.apache.empire.jakarta.impl.MojarraImplementation;
+import org.apache.empire.jakarta.impl.MyFacesImplementation;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class WebAppStartupListener implements SystemEventListener
+{
+ private static final Logger log = LoggerFactory.getLogger(WebAppStartupListener.class);
+
+ private final Class<? extends FacesConfiguration> facesConfigClass;
+
+ private FacesImplementation facesImplementation;
+
+ /**
+ * Default Constructor with no initialization
+ */
+ public WebAppStartupListener()
+ {
+ this.facesConfigClass = null;
+ }
+
+ /**
+ * Default Constructor with additional configuration
+ */
+ public WebAppStartupListener(Class<? extends FacesConfiguration> facesConfigClass)
+ {
+ this.facesConfigClass = facesConfigClass;
+ }
+
+ public FacesImplementation getFacesImplementation()
+ {
+ return facesImplementation;
+ }
+
+ protected ServletContext getServletContext(FacesContext startupContext)
+ {
+ return (ServletContext)startupContext.getExternalContext().getContext();
+ }
+
+ @Override
+ public boolean isListenerForSource(Object source)
+ {
+ return (source instanceof Application);
+ }
+
+ @Override
+ public void processEvent(SystemEvent event)
+ throws AbortProcessingException
+ {
+ log.info("ApplicationStartupListener:processEvent");
+ if (event instanceof PostConstructApplicationEvent)
+ {
+ FacesContext startupContext = FacesContext.getCurrentInstance();
+ ExternalContext externalContext = startupContext.getExternalContext();
+ // Detect implementation
+ this.facesImplementation = detectFacesImplementation(externalContext);
+ // Init Configuration
+ initFacesConfiguration(startupContext);
+ // Create and init application
+ WebApplication facesApp = createWebApplication(startupContext);
+ initWebApplication(facesApp, startupContext);
+ // Set Servlet Attribute
+ ServletContext servletContext = (ServletContext) externalContext.getContext();
+ if (servletContext.getAttribute(WebApplication.APPLICATION_BEAN_NAME)!=facesApp)
+ {
+ log.warn("WARNING: Ambiguous application definition. An object of name '{}' already exists on application scope!", WebApplication.APPLICATION_BEAN_NAME);
+ }
+ }
+ else if (event instanceof PreDestroyApplicationEvent)
+ {
+ log.info("Processing PreDestroyApplicationEvent");
+ }
+
+ }
+
+ /**
+ * Detects the JSF Implementation and returns an implmentation handler class.
+ * Supported Implementations are Sun Mojarra (2.2.x) and Apache MyFaces (2.2.x)
+ * @return the faces implementation
+ */
+ protected FacesImplementation detectFacesImplementation(ExternalContext externalContext)
+ {
+ log.debug("Detecting JSF-Implementation...");
+ // Test for Apache MyFaces
+ try {
+ Class.forName("org.apache.myfaces.application.ApplicationFactoryImpl");
+ return new MyFacesImplementation(externalContext);
+ } catch (ClassNotFoundException e) {
+ // It's not MyFaces
+ }
+ // Test for Sun Mojarra
+ try {
+ Class.forName("com.sun.faces.application.ApplicationFactoryImpl");
+ return new MojarraImplementation(externalContext);
+ } catch (ClassNotFoundException e) {
+ // It's not Mojarra
+ }
+ // Not found
+ log.error("JSF-Implementation missing or unknown. Please make sure either Apache MyFaces or Sun Mojarra implementation is provided");
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Allows to programmatically extend the faces configuration
+ * @param facesImplementation the Faces Implementation
+ * @param startupContext the Startup Context
+ */
+ protected void initFacesConfiguration(FacesContext startupContext)
+ {
+ // Init FacesExtentions
+ if (facesConfigClass!=null) {
+ log.info("Initializing FacesExtentions");
+ FacesConfiguration.initialize(facesConfigClass, startupContext, getFacesImplementation());
+ }
+ }
+
+ /**
+ * Creates a WebApplication instance
+ * @param facesImplementation the Faces Implementation
+ * @param startupContext the Startup Context
+ * @return the WebApplication instance
+ */
+ protected WebApplication createWebApplication(FacesContext startupContext)
+ {
+ // Create Application
+ Object app = getFacesImplementation().getManagedBean(WebApplication.APPLICATION_BEAN_NAME, startupContext);
+ if (!(app instanceof WebApplication))
+ throw new AbortProcessingException("Error: Application is not a "+WebApplication.class.getName()+" instance. Please create an ApplicationFactory!");
+ // done
+ return (WebApplication)app;
+ }
+
+ /**
+ * Initializes a WebApplication instance
+ * @param facesApp the WebApplication instance
+ * @param facesImplementation the Faces Implementation
+ * @param startupContext the Startup Context
+ */
+ protected void initWebApplication(WebApplication facesApp, FacesContext startupContext)
+ {
+ facesApp.init(getFacesImplementation(), startupContext);
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebApplication.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebApplication.java
new file mode 100644
index 00000000..c1065eb4
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebApplication.java
@@ -0,0 +1,640 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import jakarta.faces.application.Application;
+import jakarta.faces.application.FacesMessage;
+import jakarta.faces.application.FacesMessage.Severity;
+import jakarta.faces.application.ViewExpiredException;
+import jakarta.faces.component.NamingContainer;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIViewRoot;
+import jakarta.faces.context.ExternalContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.servlet.ServletContext;
+import jakarta.servlet.http.HttpServletRequest;
+import javax.sql.DataSource;
+
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.data.DataType;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.context.DBRollbackManager;
+import org.apache.empire.db.context.DBRollbackManager.ReleaseAction;
+import org.apache.empire.db.exceptions.UserLevelException;
+import org.apache.empire.exceptions.InternalException;
+import org.apache.empire.exceptions.InvalidArgumentException;
+import org.apache.empire.exceptions.NotSupportedException;
+import org.apache.empire.exceptions.UnexpectedReturnValueException;
+import org.apache.empire.jakarta.controls.TextAreaInputControl;
+import org.apache.empire.jakarta.controls.TextInputControl;
+import org.apache.empire.jakarta.impl.FacesImplementation;
+import org.apache.empire.jakarta.impl.ResourceTextResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class WebApplication
+{
+ private static final Logger log = LoggerFactory.getLogger(WebApplication.class);
+
+ private static final String REQUEST_CONNECTION_MAP = "requestConnectionMap";
+
+ private static final String CONN_ROLLBACK_MANAGER = "connRollbackManager";
+
+ public static String APPLICATION_BEAN_NAME = "webApplication";
+
+ protected TextResolver[] textResolvers = null;
+
+ private String webRoot = null;
+
+ private FacesImplementation facesImpl = null;
+
+ private static WebApplication appInstance = null;
+
+ /**
+ * Returns the one and only instance of the WebApplication (Singleton)
+ * @param <T> the application type
+ * @return the WebApplication instance
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends WebApplication> T getInstance()
+ {
+ if (appInstance==null)
+ log.warn("No WebApplication instance available. Please add a PostConstructApplicationEvent using WebAppStartupListener in your faces-config.xml to create the WebApplication object.");
+ // return instance
+ return (T)appInstance;
+ }
+
+ protected abstract void init(ServletContext servletContext);
+
+ protected abstract DataSource getAppDataSource(DBDatabase db);
+
+ protected WebApplication()
+ { // subscribe
+ log.info("WebApplication {} created", getClass().getName());
+ // Must be a singleton
+ if (appInstance!=null) {
+ throw new RuntimeException("An attempt was made to create second instance of WebApplication. WebApplication must be a Singleton!");
+ }
+ // set Instance
+ appInstance = this;
+ }
+
+ /**
+ * Init the Application
+ * @param facesImpl the faces implementation
+ * @param startupContext the startup context
+ */
+ public final void init(FacesImplementation facesImpl, FacesContext startupContext)
+ {
+ // Only call once!
+ if (this.facesImpl!=null || this.webRoot!=null)
+ { // already initialized
+ log.warn("WARNING: WebApplication has already been initialized! Continuing without init...");
+ return;
+ }
+ // set imppl
+ this.facesImpl = facesImpl;
+ // webRoot
+ ServletContext servletContext = (ServletContext) startupContext.getExternalContext().getContext();
+ webRoot = servletContext.getContextPath();
+ servletContext.setAttribute("webRoot", webRoot);
+ servletContext.setAttribute("app", this);
+ // Init
+ init(servletContext);
+ // post init
+ if (this.textResolvers==null)
+ { // text resolvers
+ log.info("*** initTextResolvers() ***");
+ initTextResolvers(startupContext.getApplication());
+ }
+ // Log info
+ log.info("*** WebApplication initialization complete ***");
+ log.info("JSF-Implementation is '{}'", facesImpl.getClass().getName());
+ log.info("WebRoot is '{}'", webRoot);
+ }
+
+ public void destroy()
+ {
+ // Override if needed
+ }
+
+ /* Context handling */
+
+ /**
+ * handle request cleanup
+ * @param ctx the faces context
+ */
+ public void onRequestComplete(final FacesContext ctx)
+ {
+ releaseAllConnections(ctx);
+ }
+
+ /**
+ * handle view not found
+ * @param fc the faces context
+ * @param req the request
+ */
+ public void onViewNotFound(final FacesContext fc, final HttpServletRequest req)
+ { // View not Found Error
+ log.warn("No view found for request to '{}'. Use FacesUtils.redirectDirectly() to redirect to valid view.", req.getRequestURI());
+ }
+
+ /**
+ * handle view change
+ * @param fc the faces context
+ * @param viewId the view id
+ */
+ public void onChangeView(final FacesContext fc, String viewId)
+ { // allow custom view change logic
+
+ // clear page resources
+ Map<String, Object> sm = FacesUtils.getSessionMap(fc);
+ if (sm!=null)
+ sm.remove(FacesUtils.PAGE_RESOURCE_MAP_ATTRIBUTE);
+ }
+
+ /**
+ * adds a Javascript call to the request
+ * @param fc the faces context
+ * @param function the javascript command
+ */
+ public void addJavascriptCall(final FacesContext fc, String function)
+ {
+ throw new NotSupportedException(this, "addJavascriptCall");
+ }
+
+ /**
+ * return the interface for Implementation specific features
+ * that are specific for Mojarra or MyFaces
+ * @return the faces implementation
+ */
+ public FacesImplementation getFacesImplementation()
+ {
+ return facesImpl;
+ }
+
+ /**
+ * returns the web context path as returned from ServletContext.getContextPath()
+ * @return the web root
+ */
+ public String getWebRoot()
+ {
+ return webRoot;
+ }
+
+ /**
+ * returns the active locale for a given FacesContext
+ * @param ctx the faces context
+ * @return the context locale
+ */
+ public Locale getContextLocale(final FacesContext ctx)
+ {
+ UIViewRoot root;
+ // Start out with the default locale
+ Locale locale;
+ Locale defaultLocale = Locale.getDefault();
+ locale = defaultLocale;
+ // See if this FacesContext has a ViewRoot
+ if (null != (root = ctx.getViewRoot()))
+ {
+ // If so, ask it for its Locale
+ if (null == (locale = root.getLocale()))
+ {
+ // If the ViewRoot has no Locale, fall back to the default.
+ locale = defaultLocale;
+ }
+ }
+ return locale;
+ }
+
+ public TextResolver getTextResolver(Locale locale)
+ {
+ // No text Resolvers provided
+ if (textResolvers == null || textResolvers.length == 0)
+ {
+ throw new NotSupportedException(this, "getTextResolver");
+ }
+ // Lookup resolver for locale
+ for (int i = 0; i < textResolvers.length; i++)
+ if (locale.equals(textResolvers[i].getLocale()))
+ return textResolvers[i];
+ // locale not found: return default
+ return textResolvers[0];
+ }
+
+ public TextResolver getTextResolver(FacesContext ctx)
+ {
+ return getTextResolver(getContextLocale(ctx));
+ }
+
+ /**
+ * checks if the current context contains an error
+ * @param fc the FacesContext
+ * @return true if the context has an error set or false otherwise
+ */
+ public boolean hasError(final FacesContext fc)
+ {
+ Iterator<FacesMessage> msgIterator = fc.getMessages();
+ if (msgIterator != null)
+ { // Check Messages
+ while (msgIterator.hasNext())
+ { // Check Severity
+ Severity fms = msgIterator.next().getSeverity();
+ if (fms == FacesMessage.SEVERITY_ERROR || fms == FacesMessage.SEVERITY_FATAL)
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Logs an exception as Error with additional information
+ * @param message the message to log
+ * @param e the underlying exception
+ */
+ public void logException(Logger logger, String message, Throwable e)
+ {
+ if (logger==null)
+ logger=log;
+ // View Exceptions
+ if ((e instanceof ViewExpiredException)) // || (e instanceof ViewNotFoundException)) -- MyFaces only
+ { // Session expired
+ logger.warn(message+": "+e.getMessage());
+ return;
+ }
+ // Get Session Info
+ String addlInfo;
+ try {
+ FacesContext fc = FacesContext.getCurrentInstance();
+ ExternalContext ec = (fc!=null ? fc.getExternalContext() : null);
+ String sessionInfo = (ec!=null ? getExceptionSessionInfo(ec.getSessionMap()) : "[NoSession]");
+ addlInfo = StringUtils.coalesce(sessionInfo, "[NoSessionInfo]");
+ } catch(Exception ex) {
+ addlInfo = "[Error:"+ex.getMessage()+"] ";
+ }
+ // Log now
+ if (e instanceof UserLevelException)
+ logger.info(StringUtils.concat(addlInfo, e.getClass().getSimpleName(), ": ", e.getMessage()));
+ else
+ logger.error(StringUtils.concat(addlInfo, message), e);
+ }
+
+ /**
+ * Override this to provide additonal session info for an exception
+ * @param sessionMap
+ * @return the additional session info
+ */
+ protected String getExceptionSessionInfo(Map<String, Object> sessionMap)
+ {
+ return null;
+ }
+
+ /**
+ * returns true if a form input element has been partially submitted
+ * @param fc the Faces Context
+ * @return the componentId or null if no partial submit was been performed
+ */
+ public boolean isPartialSubmit(final FacesContext fc)
+ {
+ // Detect Partial Submit
+ Map<String,String> parameterMap = fc.getExternalContext().getRequestParameterMap();
+ return ObjectUtils.getBoolean(parameterMap.get("jakarta.faces.partial.ajax"));
+ }
+
+ /**
+ * returns the componentId for which a partial submit has been performed.
+ * @param fc the Faces Context
+ * @return the componentId or null if no partial submit was been performed
+ */
+ public String getPartialSubmitComponentId(final FacesContext fc)
+ {
+ Map<String,String> parameterMap = fc.getExternalContext().getRequestParameterMap();
+ return parameterMap.get("jakarta.faces.source"); // or jakarta.faces.partial.execute ?
+ }
+
+ /**
+ * finds the component with the given id that is located in the same NamingContainer as a given component
+ * @param fc the FacesContext
+ * @param componentId the component id
+ * @param nearComponent a component within the same naming container from which to start the search (optional)
+ * @return the component or null if no component was found
+ */
+ public UIComponent findComponent(FacesContext fc, String componentId, UIComponent nearComponent)
+ {
+ if (StringUtils.isEmpty(componentId))
+ throw new InvalidArgumentException("componentId", componentId);
+ // Begin search near given component (if any)
+ UIComponent component = null;
+ if (nearComponent != null)
+ { // Search below the nearest naming container
+ component = nearComponent.findComponent(componentId);
+ if (component == null)
+ { // Recurse upwards
+ UIComponent nextParent = nearComponent;
+ while (true)
+ {
+ nextParent = nextParent.getParent();
+ // search NamingContainers only
+ while (nextParent != null && !(nextParent instanceof NamingContainer))
+ {
+ nextParent = nextParent.getParent();
+ }
+ if (nextParent == null)
+ {
+ break;
+ }
+ else
+ {
+ component = nextParent.findComponent(componentId);
+ }
+ if (component != null)
+ {
+ break;
+ }
+ }
+ }
+ }
+ // Not found. Search the entire tree
+ if (component == null)
+ component = findChildComponent(fc.getViewRoot(), componentId);
+ // done
+ return component;
+ }
+
+ /**
+ * finds a child component with the given id that is located below the given parent component
+ * @param parent the parent
+ * @param componentId the component id
+ * @return the component or null if no component was found
+ */
+ public static UIComponent findChildComponent(UIComponent parent, String componentId)
+ {
+ UIComponent component = null;
+ if (parent.getChildCount() == 0)
+ return null;
+ Iterator<UIComponent> children = parent.getChildren().iterator();
+ while (children.hasNext())
+ {
+ UIComponent nextChild = children.next();
+ if (nextChild instanceof NamingContainer)
+ {
+ component = nextChild.findComponent(componentId);
+ }
+ if (component == null)
+ {
+ component = findChildComponent(nextChild, componentId);
+ }
+ if (component != null)
+ {
+ break;
+ }
+ }
+ return component;
+ }
+
+ /**
+ * returns the default input control type for a given data Type
+ * @see org.apache.empire.jakarta.controls.InputControlManager
+ * @param dataType
+ * @return an Input Cnotrol type
+ */
+ public String getDefaultControlType(DataType dataType)
+ {
+ switch (dataType)
+ {
+ case CLOB:
+ return TextAreaInputControl.NAME;
+ default:
+ return TextInputControl.NAME;
+ }
+ }
+
+ /* Message handling */
+
+ protected void initTextResolvers(Application app)
+ {
+ int count = 0;
+ Iterator<Locale> locales = app.getSupportedLocales();
+ for (count = 0; locales.hasNext(); count++)
+ {
+ locales.next();
+ }
+
+ // get message bundles
+ String messageBundle = app.getMessageBundle();
+ textResolvers = new TextResolver[count];
+ locales = app.getSupportedLocales();
+ for (int i = 0; locales.hasNext(); i++)
+ {
+ Locale locale = locales.next();
+ textResolvers[i] = new ResourceTextResolver(ResourceBundle.getBundle(messageBundle, locale));
+ log.info("added TextResolver for {} bundle='{}'", locale.getLanguage(), messageBundle);
+ }
+ }
+
+ /**
+ * Obtains a JDBC-Connection from the connection pool
+ * @param db the database for which to obtain a connection
+ * @return the connection
+ */
+ protected Connection getConnection(DBDatabase db)
+ {
+ // Get From Pool
+ try
+ { // Obtain a connection
+ Connection conn = getAppDataSource(db).getConnection();
+ conn.setAutoCommit(false);
+ log.trace("Connection {} obtained from pool", conn.hashCode());
+ return conn;
+ }
+ catch (SQLException e)
+ {
+ log.error("Failed to get connection from pool.", e);
+ throw new InternalException(e);
+ }
+ }
+
+ /**
+ * Releases a JDBC-Connection from the connection pool
+ * @param conn the connection to release
+ * @param commit flag whether to commit changes
+ * @param dbrm the rollback manager
+ */
+ protected void releaseConnection(Connection conn, boolean commit, DBRollbackManager dbrm)
+ {
+ try
+ { // check
+ if (conn == null)
+ return;
+ // release connection
+ log.trace("releasing Connection {}", conn.hashCode());
+ // Commit or rollback connection depending on the exit code
+ if (commit)
+ { // success: commit all changes
+ if (dbrm!=null)
+ dbrm.releaseConnection(conn, ReleaseAction.Discard); // before commit
+ conn.commit();
+ log.debug("REQUEST commited.");
+ }
+ else
+ { // failure: rollback all changes
+ conn.rollback();
+ if (dbrm!=null)
+ dbrm.releaseConnection(conn, ReleaseAction.Rollback); // after rollback
+ log.debug("REQUEST rolled back.");
+ }
+ // Release Connection
+ conn.close();
+ // done
+ if (log.isDebugEnabled())
+ log.debug("REQUEST returned connection to pool.");
+ }
+ catch (SQLException e)
+ {
+ log.error("Error releasing connection", e);
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Obtains a DBRollbackManager for the current request
+ * @param fc the FacesContext
+ * @param create if true a DBRollbackManager will be created if not already present
+ * @return the rollback manager
+ */
+ public DBRollbackManager getRollbackManagerForRequest(FacesContext fc, boolean create)
+ {
+ DBRollbackManager dbrm = (DBRollbackManager)FacesUtils.getRequestAttribute(fc, CONN_ROLLBACK_MANAGER);
+ if (dbrm==null && create)
+ { dbrm = new DBRollbackManager(1, 8);
+ FacesUtils.setRequestAttribute(fc, CONN_ROLLBACK_MANAGER, dbrm);
+ }
+ return dbrm;
+ }
+
+ /**
+ * Obtains a JDBC-Connection for the current request
+ * @param fc the FacesContext
+ * @param db the DBDatabase for which to obtain a connection
+ * @param create if true a Connection will be created if not already present
+ * @return the connection
+ */
+ public Connection getConnectionForRequest(FacesContext fc, DBDatabase db, boolean create)
+ {
+ if (fc == null)
+ throw new InvalidArgumentException("FacesContext", fc);
+ if (db == null)
+ throw new InvalidArgumentException("DBDatabase", db);
+ // Get the ConnectionContextInfo map
+ @SuppressWarnings("unchecked")
+ Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, REQUEST_CONNECTION_MAP);
+ if (connMap== null && create)
+ { connMap = new HashMap<DBDatabase, Connection>(1);
+ FacesUtils.setRequestAttribute(fc, REQUEST_CONNECTION_MAP, connMap);
+ }
+ else if (connMap==null)
+ { // Nothing to do
+ return null;
+ }
+ Connection conn = connMap.get(db);
+ if (conn==null && create)
+ { // Get Pooled Connection
+ conn = getConnection(db);
+ if (conn== null)
+ throw new UnexpectedReturnValueException(this, "getConnection");
+ // Add to map
+ connMap.put(db, conn);
+ }
+ // done
+ return conn;
+ }
+
+ /**
+ * Releases all connections attached to the current request
+ * @param fc the FacesContext
+ * @param commit when true changes are committed otherwise they are rolled back
+ */
+ public void releaseAllConnections(final FacesContext fc, boolean commit)
+ {
+ @SuppressWarnings("unchecked")
+ Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, REQUEST_CONNECTION_MAP);
+ if (connMap == null)
+ return; // Nothing to do
+ // Walk the connection map
+ DBRollbackManager dbrm = getRollbackManagerForRequest(fc, false);
+ for (Connection conn : connMap.values())
+ {
+ releaseConnection(conn, commit, dbrm);
+ }
+ // remove from request map
+ FacesUtils.setRequestAttribute(fc, REQUEST_CONNECTION_MAP, null);
+ }
+
+ /**
+ * Releases all connections attached to the current request
+ * If an error is detected in the faces message list, a rollback will automatically be performed insteamd of a commmit
+ * @param fc the FacesContext
+ */
+ public void releaseAllConnections(final FacesContext fc)
+ {
+ releaseAllConnections(fc, !hasError(fc));
+ }
+
+ /**
+ * Releases the connection associated with a database from the request
+ * @param fc the FacesContext
+ * @param db the DBDatabase for which to release the connection
+ * @param commit when true changes are committed otherwise they are rolled back
+ */
+ public void releaseConnection(final FacesContext fc, DBDatabase db, boolean commit)
+ {
+ @SuppressWarnings("unchecked")
+ Map<DBDatabase, Connection> connMap = (Map<DBDatabase, Connection>) FacesUtils.getRequestAttribute(fc, REQUEST_CONNECTION_MAP);
+ if (connMap == null || !connMap.containsKey(db))
+ return; // Nothing to do;
+ // Get RollbackManager
+ DBRollbackManager dbrm = getRollbackManagerForRequest(fc, false);
+ // Release Connection
+ Connection conn = connMap.get(db);
+ releaseConnection(conn, commit, dbrm);
+ // Remove from map
+ connMap.remove(db);
+ }
+
+ /**
+ * Releases the connection associated with a database from the request
+ * If an error is detected in the faces message list, a rollback will automatically be performed insteamd of a commmit
+ * @param fc the FacesContext
+ * @param db the DBDatabase for which to release the connection
+ */
+ public void releaseConnection(final FacesContext fc, DBDatabase db)
+ {
+ releaseConnection(fc, db, !hasError(fc));
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebDBContext.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebDBContext.java
new file mode 100644
index 00000000..6a3b2bdf
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/app/WebDBContext.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.app;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.sql.Connection;
+
+import jakarta.faces.context.FacesContext;
+
+import org.apache.empire.commons.ClassUtils;
+import org.apache.empire.db.DBDatabase;
+import org.apache.empire.db.context.DBContextBase;
+import org.apache.empire.db.context.DBRollbackManager;
+import org.apache.empire.dbms.DBMSHandler;
+import org.apache.empire.exceptions.ItemNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the basic implementation of a DBContext for a web application
+ * Important: The connection is obtained on HttpRequest scope and hot held by the context
+ * The connection is automatically released via the FacesRequestPhaseListener
+ * @author rainer
+ */
+public class WebDBContext<DB extends DBDatabase> extends DBContextBase implements Serializable
+{
+ private static final long serialVersionUID = 1L;
+
+ private static final Logger log = LoggerFactory.getLogger(WebDBContext.class);
+
+ protected final transient WebApplication app;
+ protected final transient DB database;
+ protected final transient DBMSHandler dbms;
+
+ private final boolean rollbackHandlingEnabled;
+
+ /**
+ * Custom serialization for transient fields.
+ * @param strm the output stream
+ * @throws IOException from strm.defaultWriteObject
+ */
+ private void writeObject(ObjectOutputStream strm) throws IOException
+ { // Database
+ strm.writeObject((database!=null ? database.getIdentifier() : ""));
+ // write the rest
+ strm.defaultWriteObject();
+ }
+
+ /**
+ * Custom deserialization for transient fields.
+ * @param strm the input stream
+ * @throws IOException from strm.defaultReadObject
+ * @throws ClassNotFoundException from strm.defaultReadObject
+ */
+ private void readObject(ObjectInputStream strm)
+ throws IOException, ClassNotFoundException
+ { // WebApplication
+ WebApplication app = WebApplication.getInstance();
+ ClassUtils.setPrivateFieldValue(WebDBContext.class, this, "app", app);
+ // Database
+ String dbid = String.valueOf(strm.readObject());
+ DBDatabase database = DBDatabase.findByIdentifier(dbid);
+ if (database==null)
+ throw new ItemNotFoundException(dbid);
+ ClassUtils.setPrivateFieldValue(WebDBContext.class, this, "database", database);
+ ClassUtils.setPrivateFieldValue(WebDBContext.class, this, "dbms", database.getDbms());
+ // read the rest
+ strm.defaultReadObject();
+ }
+
+ /**
+ * Constructor
+ * @param db the database
+ * @param rollbackHandlingEnabled flag whether to enable rollback handling
+ */
+ public WebDBContext(DB db, boolean rollbackHandlingEnabled)
+ {
+ this.app = WebApplication.getInstance();
+ this.dbms = db.getDbms();
+ this.database = db;
+ // more
+ this.rollbackHandlingEnabled = rollbackHandlingEnabled;
+ // check dbms
+ if (db.getDbms() == null)
+ log.warn("Database {} has no dbms attached.", db.getClass().getSimpleName());
+ }
+
+ /**
+ * Constructor
+ * @param db the database
+ */
+ public WebDBContext(DB db)
+ {
+ this(db, true);
+ }
+
+ public DB getDatabase()
+ {
+ return database;
+ }
+
+ @Override
+ public DBMSHandler getDbms()
+ {
+ return dbms;
+ }
+
+ @Override
+ public boolean isPreparedStatementsEnabled()
+ {
+ return database.isPreparedStatementsEnabled();
+ }
+
+ @Override
+ public boolean isRollbackHandlingEnabled()
+ {
+ return rollbackHandlingEnabled;
+ }
+
+ /**
+ * Unclear weather this is any useful here
+ */
+ @Override
+ public void discard()
+ {
+ /*
+ FacesContext fc = FacesContext.getCurrentInstance();
+ this.app.releaseConnection(fc, database);
+ // discard
+ super.discard();
+ */
+ }
+
+ @Override
+ protected Connection getConnection(boolean create)
+ {
+ return app.getConnectionForRequest(getFacesContext(), database, create);
+ }
+
+ @Override
+ protected DBRollbackManager getRollbackManager(boolean create)
+ {
+ return app.getRollbackManagerForRequest(getFacesContext(), create);
+ }
+
+ public FacesContext getFacesContext()
+ {
+ return FacesContext.getCurrentInstance();
+ }
+
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/ControlTag.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/ControlTag.java
new file mode 100644
index 00000000..db4cbcf1
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/ControlTag.java
@@ -0,0 +1,820 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.components;
+
+import java.io.IOException;
+import java.util.List;
+
+import jakarta.faces.component.NamingContainer;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIComponentBase;
+import jakarta.faces.component.UIInput;
+import jakarta.faces.component.UINamingContainer;
+import jakarta.faces.component.html.HtmlOutputLabel;
+import jakarta.faces.component.visit.VisitCallback;
+import jakarta.faces.component.visit.VisitContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.context.ResponseWriter;
+import jakarta.faces.convert.ConverterException;
+
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.data.Column;
+import org.apache.empire.db.exceptions.FieldIllegalValueException;
+import org.apache.empire.exceptions.EmpireException;
+import org.apache.empire.jakarta.controls.InputControl;
+import org.apache.empire.jakarta.utils.ControlRenderInfo;
+import org.apache.empire.jakarta.utils.TagEncodingHelper;
+import org.apache.empire.jakarta.utils.TagEncodingHelperFactory;
+import org.apache.empire.jakarta.utils.TagStyleClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ControlTag extends UIInput implements NamingContainer
+{
+ /**
+ * ControlSeparatorComponent
+ */
+ public static abstract class ControlSeparatorComponent extends jakarta.faces.component.UIComponentBase
+ {
+ private ControlTag control = null;
+
+ protected String tagName = null;
+
+ protected abstract String getTagName(ControlRenderInfo renderInfo);
+
+ protected abstract void writeAttributes(ResponseWriter writer, TagEncodingHelper helper)
+ throws IOException;
+
+ @Override
+ public String getFamily()
+ {
+ return UINamingContainer.COMPONENT_FAMILY;
+ }
+
+ protected final ControlTag getControl()
+ {
+ return control;
+ }
+
+ @Override
+ public void encodeBegin(FacesContext context)
+ throws IOException
+ {
+ super.encodeBegin(context);
+
+ UIComponent parent = getParent();
+ if (!(parent instanceof ControlTag))
+ parent = parent.getParent();
+ if (!(parent instanceof ControlTag))
+ { log.error("Invalid parent component for " + getClass().getName());
+ return;
+ }
+
+ // Get Control and TagName
+ this.control = (ControlTag) parent;
+ this.tagName = (this.control.controlVisible ? getTagName(control.renderInfo) : null);
+
+ // Start
+ if (tagName!=null && tagName.length()>0)
+ { // render tag
+ ResponseWriter writer = context.getResponseWriter();
+ writer.startElement(tagName, this);
+ writeAttributes(writer, this.control.helper);
+ }
+ }
+
+ @Override
+ public void encodeEnd(FacesContext context)
+ throws IOException
+ {
+ if (tagName!=null && tagName.length()>0)
+ { // render tag
+ ResponseWriter writer = context.getResponseWriter();
+ writer.endElement(tagName);
+ }
+ // call default
+ super.encodeEnd(context);
+ }
+
+ @Override
+ public boolean getRendersChildren()
+ {
+ // return super.getRendersChildren();
+ return control.helper.isVisible();
+ }
+ }
+
+ public static class LabelSeparatorComponent extends ControlSeparatorComponent
+ {
+ @Override
+ protected String getTagName(ControlRenderInfo renderInfo)
+ {
+ return renderInfo.LABEL_WRAPPER_TAG;
+ }
+
+ @Override
+ protected void writeAttributes(ResponseWriter writer, TagEncodingHelper helper)
+ throws IOException
+ {
+ // style Class
+ String labelClass = helper.getTagAttributeStringEx("labelClass");
+ helper.writeStyleClass(writer, TagStyleClass.CONTROL_LABEL.get(), labelClass);
+ }
+
+ @Override
+ public void encodeChildren(FacesContext context)
+ throws IOException
+ {
+ // encode label component
+ getControl().encodeLabel(context, this);
+ // now render
+ super.encodeChildren(context);
+ }
+ }
+
+ public static class InputSeparatorComponent extends ControlSeparatorComponent
+ {
+ @Override
+ protected String getTagName(ControlRenderInfo renderInfo)
+ {
+ return renderInfo.INPUT_WRAPPER_TAG;
+ }
+
+ @Override
+ protected void writeAttributes(ResponseWriter writer, TagEncodingHelper helper)
+ throws IOException
+ {
+ // style Class
+ String inputClass = helper.getTagAttributeStringEx("inputClass");
+ // String inputState =(helper.isRenderValueComponent() ? TagStyleClass.INPUT_DIS.get() : null);
+ helper.writeStyleClass(writer, TagStyleClass.CONTROL_INPUT.get(), inputClass); // , inputState
+ // colspan
+ String colSpan = tagName.equalsIgnoreCase(InputControl.HTML_TAG_TD) ? helper.getTagAttributeStringEx("colspan") : null;
+ if (colSpan!=null)
+ writer.writeAttribute("colspan", colSpan, null);
+ }
+
+ @Override
+ public void encodeChildren(FacesContext context)
+ throws IOException
+ {
+ // encode input components
+ getControl().encodeInput(context, this);
+ // don't call super.encodeChildren()!
+ }
+ }
+
+ public static class ValueOutputComponent extends jakarta.faces.component.UIComponentBase
+ {
+ private final String tagName = "span";
+
+ /*
+ public ValueOutputComponent()
+ {
+ if (log.isTraceEnabled())
+ log.trace("ValueOutputComponent created.");
+ }
+ */
+
+ @Override
+ public String getFamily()
+ {
+ return UINamingContainer.COMPONENT_FAMILY;
+ }
+
+ @Override
+ public void encodeBegin(FacesContext context)
+ throws IOException
+ {
+ super.encodeBegin(context);
+
+ UIComponent parent = getParent();
+ if (!(parent instanceof ControlTag))
+ parent = parent.getParent();
+ if (!(parent instanceof ControlTag))
+ parent = parent.getParent();
+ if (!(parent instanceof ControlTag))
+ { log.error("Invalid parent component for " + getClass().getName());
+ return;
+ }
+
+ ControlTag controlTag = (ControlTag) parent;
+ if (!controlTag.controlVisible)
+ return; // Not visible!
+
+ InputControl control = controlTag.control;
+ InputControl.ValueInfo valInfo = controlTag.inpInfo;
+
+ TagEncodingHelper helper = controlTag.helper;
+ if (control == null)
+ control = helper.getInputControl(); // Oops, should not come here
+ if (valInfo == null)
+ valInfo = helper.getValueInfo(context); // Oops, should not come here
+
+ String styleClass = helper.getTagStyleClass(TagStyleClass.INPUT_DIS.get());
+ String tooltip = helper.getValueTooltip(helper.getTagAttributeValue("title"));
+
+ // render components
+ control.renderValue(this, this.tagName, styleClass, tooltip, valInfo, context);
+ }
+ }
+
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(ControlTag.class);
+
+ protected static final String readOnlyState = "readOnlyState";
+
+ protected final TagEncodingHelper helper = TagEncodingHelperFactory.create(this, TagStyleClass.INPUT.get()); // Must be "INPUT" not "CONTROL"!
+
+ protected InputControl control = null;
+ protected InputControl.InputInfo inpInfo = null;
+ protected ControlRenderInfo renderInfo = null;
+ protected boolean encodeLabel = true;
+ private boolean creatingComponents = false;
+ protected boolean valueValidated = false;
+ protected boolean controlVisible = true;
+
+ public ControlTag()
+ {
+ super();
+ }
+
+ @Override
+ public String getFamily()
+ {
+ return "jakarta.faces.NamingContainer";
+ }
+
+ protected void saveState()
+ {
+ // getStateHelper().put(inpControlPropName, control);
+ // getStateHelper().put(inputInfoPropName, inpInfo);
+ getStateHelper().put(ControlTag.readOnlyState, (this.inpInfo == null));
+ }
+
+ protected boolean initState(FacesContext context)
+ {
+ // Check read-Only
+ Boolean ros = (Boolean) getStateHelper().get(ControlTag.readOnlyState);
+ if (ros != null && ros.booleanValue())
+ return false;
+ // Must have children
+ if (getChildCount() == 0)
+ { log.warn("ControlTag '{}' has no children. encodeBegin may not have been called yet.", getClientId());
+ return false;
+ }
+ // control = ;
+ helper.prepareData();
+ this.control = helper.getInputControl();
+ this.inpInfo = helper.getInputInfo(context);
+ return (this.control != null && this.inpInfo != null);
+ }
+
+ @Override
+ public String getId()
+ {
+ String compId = super.getId();
+ // Mojarra-Patch since Id might have been set to "null"
+ if ("null".equals(compId))
+ compId = helper.completeInputTagId(null);
+ // done
+ return compId;
+ }
+
+ @Override
+ public void setId(String id)
+ { // complete
+ id = helper.completeInputTagId(id);
+ // set
+ super.setId(id);
+ }
+
+ @Override
+ public void setParent(UIComponent parent)
+ {
+ super.setParent(parent);
+ // check
+ if (helper.hasComponentId())
+ return;
+ if (this.renderInfo==null) {
+ /*
+ * Attention: Only works if FormGrid is a direct parent of the Control.
+ * Does not work, if other components are between the Control and the FormGrid.
+ */
+ this.renderInfo=helper.getControlRenderInfo();
+ if (this.renderInfo!=null && this.renderInfo.AUTO_CONTROL_ID!=null) {
+ // Auto set component Id
+ setId(this.renderInfo.AUTO_CONTROL_ID.toString());
+ log.warn("Auto-Setting compontent id for control to {}", this.getId());
+ }
+ }
+ }
+
+ /**
+ * remember original clientId
+ * necessary only inside UIData
+ */
+ protected String treeClientId = null;
+
+ @Override
+ public boolean visitTree(VisitContext visitContext, VisitCallback callback)
+ {
+ FacesContext context = visitContext.getFacesContext();
+ treeClientId = this.getClientId(context);
+ return super.visitTree(visitContext, callback);
+ }
+
+ @Override
+ public String getClientId(FacesContext context)
+ {
+ // Check if dynamic components are being created
+ if (this.treeClientId!=null && (this.creatingComponents || this.control!=null && this.control.isCreatingComponents()))
+ { // return the original tree client id
+ return treeClientId;
+ }
+ // default behavior
+ return super.getClientId(context);
+ }
+
+ @Override
+ public boolean getRendersChildren()
+ {
+ // render children ourselves using
+ // encodeChildren(FacesContext context)
+ return true;
+ }
+
+ /*
+ * Add label and input components when the view is loaded for the first time
+ */
+ @Override
+ public void encodeBegin(FacesContext context)
+ throws IOException
+ {
+ super.encodeBegin(context);
+
+ // init
+ helper.encodeBegin();
+
+ // set InputControl
+ if (this.control==null)
+ this.control = helper.getInputControl();
+
+ // renderInfo
+ if (this.renderInfo==null) {
+ this.renderInfo=helper.getControlRenderInfo();
+ if (this.renderInfo==null)
+ this.renderInfo=ControlRenderInfo.DEFAULT_CONTROL_RENDER_INFO; // No FormGrid found. Use Default!
+ }
+
+ // Check visiblity
+ this.controlVisible = helper.isVisible();
+
+ // Render
+ if (this.controlVisible && renderInfo.CONTROL_TAG!=null)
+ { // control wrapper tag
+ ResponseWriter writer = context.getResponseWriter();
+ writer.startElement(renderInfo.CONTROL_TAG, this);
+ // render id
+ helper.writeComponentId(writer, false);
+ // style class
+ String controlClass = helper.getTagAttributeStringEx("controlClass");
+ String styleClass = helper.getTagAttributeString(InputControl.CSS_STYLE_CLASS);
+ String contextClass = helper.getContextStyleClass();
+ helper.writeStyleClass(writer, TagStyleClass.CONTROL.get(), controlClass, styleClass, contextClass);
+ }
+
+ // Encode LabelSeparatorComponent
+ if (this.encodeLabel)
+ { // Create Label Separator Tag
+ ControlSeparatorComponent labelSepTag = null;
+ if (getChildCount() > 0)
+ labelSepTag = (ControlSeparatorComponent) getChildren().get(0);
+ if (labelSepTag == null)
+ { try {
+ creatingComponents = true;
+ labelSepTag = new LabelSeparatorComponent();
+ getChildren().add(labelSepTag);
+ helper.resetComponentId(labelSepTag);
+ } finally {
+ creatingComponents = false;
+ }
+ }
+ // encode
+ labelSepTag.setRendered(this.controlVisible);
+ if (this.controlVisible)
+ labelSepTag.encodeAll(context);
+ }
+
+ // Encode InputSeparatorComponent
+ boolean isCustomInput = isCustomInput();
+ if (!isCustomInput)
+ { // Create Input Separator Tag
+ ControlSeparatorComponent inputSepTag = null;
+ if (getChildCount() > 1)
+ inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
+ if (inputSepTag == null)
+ { try {
+ creatingComponents = true;
+ inputSepTag = new InputSeparatorComponent();
+ getChildren().add(inputSepTag);
+ helper.resetComponentId(inputSepTag);
+ } finally {
+ creatingComponents = false;
+ }
+ }
+ // encode
+ inputSepTag.setRendered(this.controlVisible);
+ if (this.controlVisible)
+ inputSepTag.encodeAll(context);
+ }
+ // done
+ saveState();
+ }
+
+ @Override
+ public void encodeChildren(FacesContext context)
+ throws IOException
+ {
+ /*
+ * Composite component renderer must render children for custom components
+ * <cc:implementation>
+ * <c:choose>
+ * <c:when test="#{cc.attrs.custom == true}">
+ * <cc:insertChildren/>
+ * </c:when>
+ * </c:choose>
+ * </cc:implementation>
+ */
+ if (this.controlVisible && isCustomInput())
+ { // Custom input
+ ResponseWriter writer = null;
+ String tagName = renderInfo.INPUT_WRAPPER_TAG;
+ if (tagName!=null && tagName.length()>0)
+ { // render tag
+ writer = context.getResponseWriter();
+ writer.startElement(tagName, this);
+ // style Class
+ String inpClass = helper.getTagAttributeStringEx("inputClass");
+ helper.writeStyleClass(writer, TagStyleClass.CONTROL_INPUT.get(), inpClass);
+ // write more
+ String colSpan = tagName.equalsIgnoreCase(InputControl.HTML_TAG_TD) ? helper.getTagAttributeStringEx("colspan") : null;
+ if (colSpan!=null)
+ writer.writeAttribute("colspan", colSpan, null);
+ }
+ // encode children
+ super.encodeChildren(context);
+ // end of element
+ if (writer!=null)
+ { // render tag
+ writer.endElement(tagName);
+ }
+ }
+ }
+
+ @Override
+ public void encodeEnd(FacesContext context)
+ throws IOException
+ {
+ // encodeEnd
+ if (this.controlVisible && renderInfo.CONTROL_TAG!=null)
+ { // control wrapper tag
+ ResponseWriter writer = context.getResponseWriter();
+ writer.endElement(renderInfo.CONTROL_TAG);
+ }
+ // call base
+ super.encodeEnd(context);
+ }
+
+ public boolean isCustomInput()
+ {
+ Object custom = getAttributes().get("custom");
+ if (custom != null)
+ return ObjectUtils.getBoolean(custom);
+ return false;
+ }
+
+ /**
+ * called from LabelSeparatorComponent
+ * @param context the faces context
+ * @param parent the LabelSeparatorComponent
+ * @throws IOException from ResponseWriter
+ */
+ protected void encodeLabel(FacesContext context, UIComponentBase parent)
+ throws IOException
+ {
+ UIComponent labelFacet = getFacet("label");
+ if (labelFacet!=null)
+ { // label facet
+ labelFacet.encodeAll(context);
+ }
+ else
+ { // render components
+ try {
+ creatingComponents = true;
+ HtmlOutputLabel labelComponent = null;
+ if (parent.getChildCount() > 0)
+ {
+ labelComponent = (HtmlOutputLabel) parent.getChildren().get(0);
+ // update
+ helper.updateLabelComponent(context, labelComponent, null);
+ }
+ if (labelComponent == null)
+ {
+ String forInput = isCustomInput() ? helper.getTagAttributeString("for") : "*";
+ // createLabelComponent
+ labelComponent = helper.createLabelComponent(context, forInput, "eLabel", null, getColon());
+ parent.getChildren().add(0, labelComponent);
+ helper.resetComponentId(labelComponent);
+ }
+ } finally {
+ creatingComponents = false;
+ }
+ }
+ }
+
+ /**
+ * called from InputSeparatorComponent
+ * @param context the faces context
+ * @param parent the InputSeparatorComponent
+ * @throws IOException from ResponseWriter
+ */
+ protected void encodeInput(FacesContext context, UIComponent parent)
+ throws IOException
+ {
+ // render components
+ try {
+ creatingComponents = true;
+ // check children
+ int count = parent.getChildCount();
+ UIComponent valueComp = (count>0 ? parent.getChildren().get(count-1) : null);
+ boolean resetChildId = (count==0);
+ // continue
+ this.inpInfo = helper.getInputInfo(context);
+ // set required
+ super.setRequired(helper.isValueRequired());
+ // create Input Controls
+ // boolean recordReadOnly = helper.isRecordReadOnly();
+ if (count==0)
+ { // Create components
+ control.createInput(parent, inpInfo, context);
+ // create Value Component
+ if (valueComp == null)
+ { // create ValueOutputComponent
+ valueComp = new ValueOutputComponent();
+ parent.getChildren().add(valueComp);
+ }
+ }
+ else
+ { // Update
+ control.updateInputState(parent, inpInfo, context, context.getCurrentPhaseId());
+ }
+ // set rendered
+ boolean renderValue = helper.isRenderValueComponent();
+ List<UIComponent> children = parent.getChildren();
+ for (UIComponent child : children)
+ { // reset child id
+ if (resetChildId && child.getId()!=null)
+ child.setId(child.getId());
+ // set rendered
+ boolean valueOutput = (child instanceof ValueOutputComponent);
+ child.setRendered((valueOutput ? renderValue : !renderValue));
+ }
+ // wrapperTag
+ String wrapperTag = helper.writeWrapperTag(context, false, renderValue);
+ // render
+ control.renderInput(parent, inpInfo, context);
+ // wrapperTagEnd
+ if (wrapperTag!=null)
+ { // control wrapper tag
+ ResponseWriter writer = context.getResponseWriter();
+ writer.endElement(wrapperTag);
+ }
+ } finally {
+ creatingComponents = false;
+ }
+ }
+
+ @Override
+ public Object getValue()
+ {
+ // check for record
+ if (helper.getRecord() != null)
+ return helper.getDataValue(true);
+ // default
+ Object value = super.getValue();
+ return value;
+ }
+
+ @Override
+ public Object getSubmittedValue()
+ { // Check state
+ if (this.control == null || this.inpInfo == null || helper.isReadOnly())
+ return null;
+ // Get Input Tag
+ if (getChildCount() <= 1)
+ return null;
+ // get Input Value
+ ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
+ return this.control.getInputValue(inputSepTag, this.inpInfo, true);
+ }
+
+ @Override
+ protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
+ throws ConverterException
+ { // Check state
+ if (this.control == null || this.inpInfo == null || helper.isReadOnly())
+ return null;
+ // Get Input Tag
+ if (getChildCount() <= 1)
+ return null;
+ // get Input Value
+ ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
+ return this.control.getConvertedValue(inputSepTag, this.inpInfo, newSubmittedValue);
+ }
+
+ @Override
+ public void processDecodes(FacesContext context)
+ {
+ // check UI-Data
+ if (helper.isInsideUIData() && getChildCount()>0)
+ { // update input state
+ updateControlInputState(context);
+ // Set Rendered for children (changed 2024-04-05)
+ // boolean readOnly = helper.isRecordReadOnly();
+ boolean renderValue = helper.isRenderValueComponent();
+ setRenderInput(!renderValue);
+ }
+ // default
+ super.processDecodes(context);
+ }
+
+ @Override
+ public void processValidators(FacesContext context)
+ {
+ // check UI-Data
+ if (helper.isInsideUIData() && getChildCount()>0)
+ { // update input state
+ updateControlInputState(context);
+ // No need to processValidators for children
+ setRenderInput(false);
+ }
+ // process all validators (including children)
+ super.processValidators(context);
+ }
+
+ @Override
+ public void validate(FacesContext context)
+ { // init state
+ if (initState(context) == false)
+ return;
+ // get submitted value and validate
+ if (log.isDebugEnabled())
+ log.debug("Validating input for {}.", this.inpInfo.getColumn().getName());
+
+ // Will internally call getSubmittedValue() and validateValue()
+ this.valueValidated = false;
+ super.validate(context);
+ // post check
+ if (!this.valueValidated && !helper.isReadOnly())
+ { // New since 2024-04-04
+ // Validate Record value
+ Object value = helper.getDataValue(true);
+ log.warn("No Submitted value for {}. Validating Record value of \"{}\" instead.", helper.getColumnName(), value);
+ if (value!=null)
+ validateValue(context, value);
+ }
+ }
+
+ @Override
+ public void validateValue(FacesContext context, Object value)
+ { // Validate value
+ try
+ { // Check state
+ if (this.inpInfo == null || !isValid())
+ return;
+ // validateValue
+ if (helper.beginValidateValue(context, value))
+ inpInfo.validate(value);
+ setValid(true);
+ // don't call base class!
+ // super.validateValue(context, value);
+
+ } catch (Exception e) {
+ // Value is not valid
+ if (!(e instanceof EmpireException))
+ e = new FieldIllegalValueException(helper.getColumn(), "", e);
+ // Add error message
+ helper.addErrorMessage(context, e);
+ setValid(false);
+
+ } finally {
+ this.valueValidated = true;
+ }
+ }
+
+ @Override
+ public void updateModel(FacesContext context)
+ {
+ if (initState(context) == false)
+ return;
+ // No Action
+ if (!isValid() || !isLocalValueSet())
+ return;
+ // updateModel
+ Object value = getLocalValue();
+ if (helper.beginUpdateModel(context, value))
+ { // don't call base class!
+ // super.updateModel(context);
+ log.debug("Updating model input for {}.", this.inpInfo.getColumn().getName());
+ this.inpInfo.setValue(value);
+ }
+ setValue(null);
+ setLocalValueSet(false);
+ // Post update
+ ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
+ this.control.postUpdateModel(inputSepTag, this.inpInfo, context);
+ }
+
+ public InputControl getInputControl()
+ {
+ return this.control;
+ }
+
+ public Column getInputColumn()
+ {
+ return helper.getColumn();
+ }
+
+ public boolean isInputReadOnly()
+ {
+ return helper.isRecordReadOnly();
+ }
+
+ public boolean isInputRequired()
+ {
+ return helper.isValueRequired();
+ }
+
+ protected void updateControlInputState(FacesContext context)
+ {
+ // get control
+ helper.prepareData();
+ if (control==null)
+ control = helper.getInputControl();
+ if (inpInfo==null)
+ inpInfo = helper.getInputInfo(context);
+ // update control
+ ControlSeparatorComponent inputSepTag = (ControlSeparatorComponent) getChildren().get(1);
+ control.updateInputState(inputSepTag, inpInfo, context, context.getCurrentPhaseId());
+ }
+
+ protected void setRenderInput(boolean renderInput)
+ {
+ if (getChildCount()>1 && (getChildren().get(1) instanceof InputSeparatorComponent))
+ { // Make sure all inputs are rendered
+ boolean hasChanged = false;
+ InputSeparatorComponent parent = (InputSeparatorComponent)getChildren().get(1);
+ // set rendered of children
+ for (UIComponent child : parent.getChildren())
+ { // set rendered
+ boolean rendered = (child instanceof ValueOutputComponent) ? renderInput : !renderInput;
+ if (child.isRendered()!=rendered)
+ { child.setRendered(rendered);
+ hasChanged = true;
+ }
+ }
+ // give control chance to update
+ if (hasChanged && log.isDebugEnabled())
+ log.debug("Changing UIInput readOnly state for {} to {}", helper.getColumnName(), renderInput);
+ }
+ else
+ { // Must have at least two children, for label and input
+ log.warn("Control-Tag does not have separate Label and Input components");
+ }
+ }
+
+ protected boolean getColon()
+ {
+ Object colon = getAttributes().get("colon");
+ if (colon!=null)
+ return ObjectUtils.getBoolean(colon);
+ return true;
+ }
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/FormGridTag.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/FormGridTag.java
new file mode 100644
index 00000000..3248cbd5
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/FormGridTag.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.components;
+
+import java.io.IOException;
+
+import jakarta.faces.component.UINamingContainer;
+import jakarta.faces.component.UIOutput;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.context.ResponseWriter;
+
+import org.apache.empire.commons.StringUtils;
+import org.apache.empire.jakarta.controls.InputControl;
+import org.apache.empire.jakarta.utils.ControlRenderInfo;
+import org.apache.empire.jakarta.utils.TagEncodingHelper;
+import org.apache.empire.jakarta.utils.TagEncodingHelperFactory;
+import org.apache.empire.jakarta.utils.TagStyleClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FormGridTag extends UIOutput // implements NamingContainer
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(FormGridTag.class);
+
+ /*
+ * FormGridMode
+ */
+ private enum FormGridMode
+ {
+ LEGACY(InputControl.HTML_TAG_DIV, null, InputControl.HTML_TAG_TD, InputControl.HTML_TAG_TD),
+ TABLE (InputControl.HTML_TAG_TABLE, InputControl.HTML_TAG_TR, InputControl.HTML_TAG_TD, InputControl.HTML_TAG_TD),
+ GRID (InputControl.HTML_TAG_DIV, InputControl.HTML_TAG_DIV, InputControl.HTML_TAG_DIV, InputControl.HTML_TAG_DIV),
+ FLAT (InputControl.HTML_TAG_DIV, InputControl.HTML_TAG_DIV, null, null);
+
+ public final String GRID_TAG;
+ public final String CONTROL_TAG;
+ public final String LABEL_TAG;
+ public final String INPUT_TAG;
+
+ private FormGridMode(String gridTag, String controlTag, String labelTag, String inputTag)
+ {
+ this.GRID_TAG = gridTag;
+ this.CONTROL_TAG = controlTag;
+ this.LABEL_TAG = labelTag;
+ this.INPUT_TAG = inputTag;
+ }
+
+ public static FormGridMode detect(String mode)
+ {
+ if (mode==null || mode.length()==0)
+ return LEGACY;
+ // find
+ FormGridMode[] values = values();
+ for (int i=0; i<values.length; i++)
+ {
+ if (values[i].name().equalsIgnoreCase(mode))
+ return values[i];
+ }
+ // not found
+ log.warn("FormGridMode \"{}\" not found. Using default!", mode);
+ return LEGACY;
+ }
+
+ @Override
+ public String toString()
+ {
+ return name();
+ }
+ }
+
+ protected final TagEncodingHelper helper = TagEncodingHelperFactory.create(this, TagStyleClass.FORM_GRID.get());
+
+ protected ControlRenderInfo controlRenderInfo = null;
+
+ private FormGridMode mode;
+
+ @Override
+ public String getFamily()
+ {
+ return UINamingContainer.COMPONENT_FAMILY;
+ }
+
+ @Override
+ public void encodeBegin(FacesContext context)
+ throws IOException
+ {
+ // call base
+ super.encodeBegin(context);
+
+ // tagName
+ this.mode = FormGridMode.detect(helper.getTagAttributeString("mode", FormGridMode.GRID.name()));
+
+ // render components
+ ResponseWriter writer = context.getResponseWriter();
+ writer.startElement(mode.GRID_TAG, this);
+ // id
+ helper.writeComponentId(writer);
+ // style class
+ helper.writeStyleClass(writer);
+ helper.writeAttribute(writer, InputControl.HTML_ATTR_STYLE, helper.getTagAttributeString("style"));
+ }
+
+ @Override
+ public boolean getRendersChildren()
+ {
+ return super.getRendersChildren();
+ }
+
+ @Override
+ public void encodeChildren(FacesContext context)
+ throws IOException
+ {
+ super.encodeChildren(context);
+ }
+
+ @Override
+ public void encodeEnd(FacesContext context)
+ throws IOException
+ {
+ // call base
+ super.encodeEnd(context);
+ // close
+ ResponseWriter writer = context.getResponseWriter();
+ writer.endElement(mode.GRID_TAG);
+ }
+
+ public ControlRenderInfo getControlRenderInfo()
+ {
+ // already set?
+ if (controlRenderInfo!=null)
+ return controlRenderInfo;
+ // check mode
+ if (this.mode==null)
+ this.mode = FormGridMode.detect(helper.getTagAttributeString("mode", FormGridMode.GRID.name()));
+ // autoControlId
+ Character autoControlId = null;
+ Object autoId = helper.getTagAttributeString("autoControlId");
+ if (autoId!=null)
+ { // check
+ String id = autoId.toString();
+ if ("true".equalsIgnoreCase(id))
+ autoControlId = TagEncodingHelper.PH_COLUMN_SMART;
+ else if (id.length()==1 && StringUtils.indexOfAny(id, TagEncodingHelper.ALLOWED_COLUMN_PH)>=0)
+ autoControlId = id.charAt(0);
+ else if (!"false".equalsIgnoreCase(id))
+ log.warn("FormGridTag: Invalid value \"{}\" for attribute \"autoControlId\". Allowed values are *|@|&", id);
+ }
+ // create control info
+ this.controlRenderInfo = new ControlRenderInfo(mode.CONTROL_TAG, mode.LABEL_TAG, mode.INPUT_TAG, autoControlId);
+ return controlRenderInfo;
+ }
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/InputTag.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/InputTag.java
new file mode 100644
index 00000000..5c2c8d78
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/InputTag.java
@@ -0,0 +1,435 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.components;
+
+import java.io.IOException;
+import java.util.List;
+
+import jakarta.faces.component.NamingContainer;
+import jakarta.faces.component.UIComponent;
+import jakarta.faces.component.UIInput;
+import jakarta.faces.component.visit.VisitCallback;
+import jakarta.faces.component.visit.VisitContext;
+import jakarta.faces.context.FacesContext;
+import jakarta.faces.context.ResponseWriter;
+import jakarta.faces.convert.ConverterException;
+import jakarta.faces.view.AttachedObjectHandler;
+
+import org.apache.empire.data.Column;
+import org.apache.empire.db.exceptions.FieldIllegalValueException;
+import org.apache.empire.exceptions.EmpireException;
+import org.apache.empire.jakarta.controls.InputControl;
+import org.apache.empire.jakarta.utils.TagEncodingHelper;
+import org.apache.empire.jakarta.utils.TagEncodingHelperFactory;
+import org.apache.empire.jakarta.utils.TagStyleClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class InputTag extends UIInput implements NamingContainer
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(InputTag.class);
+
+ // private static final String inpControlPropName = InputControl.class.getSimpleName();
+ // private static final String inputInfoPropName = InputControl.InputInfo.class.getSimpleName();
+ protected static final String readOnlyState = "readOnlyState";
+
+ protected final TagEncodingHelper helper = TagEncodingHelperFactory.create(this, TagStyleClass.INPUT.get());
+
+ protected InputControl control = null;
+ protected InputControl.InputInfo inpInfo = null;
+ protected boolean valueValidated = false;
+
+ /*
+ private static int itemIdSeq = 0;
+ private final int itemId;
+ */
+
+ public InputTag()
+ {
+ super();
+ // Debug stuff
+ /*
+ itemId = ++itemIdSeq;
+ if (log.isDebugEnabled())
+ log.debug("InputTag {} created", itemId);
+ */
+ }
+
+ @Override
+ public String getFamily()
+ {
+ return "jakarta.faces.NamingContainer";
+ }
+
+ protected void saveState()
+ {
+ // getStateHelper().put(inpControlPropName, control);
+ // getStateHelper().put(inputInfoPropName, inpInfo);
+ getStateHelper().put(readOnlyState, (inpInfo == null));
+ }
+
+ protected boolean initState(FacesContext context)
+ {
+ // Check visibility
+ helper.prepareData();
+ if (helper.isVisible() == false)
+ return false; // not visible
+ // Read only State
+ Boolean ros = (Boolean) getStateHelper().get(readOnlyState);
+ if (ros != null && ros.booleanValue())
+ return false;
+ // Must have children
+ if (getChildCount() == 0)
+ { log.warn("InputTag '{}' has no children. encodeBegin may not have been called yet.", getClientId());
+ return false;
+ }
+ // Init Control and inputInfo;
+ control = helper.getInputControl();
+ inpInfo = helper.getInputInfo(context);
+ return (control != null && inpInfo != null);
+ }
+
+ /**
+ * remember original clientId
+ * necessary only inside UIData
+ */
+ private String treeClientId = null;
+
+ @Override
+ public boolean visitTree(VisitContext visitContext, VisitCallback callback)
+ {
+ FacesContext context = visitContext.getFacesContext();
+ treeClientId = getClientId(context);
+ return super.visitTree(visitContext, callback);
+ }
+
+ @Override
+ public String getClientId(FacesContext context)
+ {
+ // Check if dynamic components are being created
+ if (this.treeClientId != null && control != null && control.isCreatingComponents())
+ { // return the original tree client id
+ return treeClientId;
+ }
+ // default behavior
+ return super.getClientId(context);
+ }
+
+ @Override
+ public boolean getRendersChildren()
+ {
+ // render children ourselves using
+ // encodeChildren(FacesContext context)
+ return true;
+ }
+
+ @Override
+ public void encodeBegin(FacesContext context)
+ throws IOException
+ {
+ super.encodeBegin(context);
+
+ // get Control (before checking visible)
+ helper.encodeBegin();
+
+ // create child components
+ this.control = helper.getInputControl();
+ this.inpInfo = helper.getInputInfo(context);
+ // set required
+ super.setRequired(helper.isValueRequired());
+ // create input
+ if (this.getChildCount()==0)
+ { // create input
+ control.createInput(this, inpInfo, context);
+ attachEvents(context);
+ }
+ else
+ { // update state
+ control.updateInputState(this, inpInfo, context, context.getCurrentPhaseId());
+ }
+
+ // Set Render Input
+ boolean controlVisible = helper.isVisible();
+ boolean renderValue = helper.isRenderValueComponent();
+ setRenderInput(!renderValue && controlVisible);
+ // Render when visible
+ if (controlVisible)
+ { // Render now
+ String wrapperTag = helper.writeWrapperTag(context, true, renderValue);
+ // render components
+ if (renderValue)
+ { // render value
+ String tagName = "span";
+ String styleClass = helper.getTagStyleClass(TagStyleClass.INPUT_DIS.get());
+ String tooltip = helper.getValueTooltip(helper.getTagAttributeValue("title"));
+ control.renderValue(this, tagName, styleClass, tooltip, inpInfo, context);
+ }
+ else
+ { // render input
+ control.renderInput(this, inpInfo, context);
+ }
+ // wrapperTagEnd
+ if (wrapperTag!=null)
+ { // control wrapper tag
+ ResponseWriter writer = context.getResponseWriter();
+ writer.endElement(wrapperTag);
+ }
+ }
+ saveState();
+ }
+
+ @Override
+ public void encodeChildren(FacesContext context)
+ throws IOException
+ {
+ // Ignore this
+ // super.encodeChildren(context);
+ log.debug("InputTag:encodeChildren is ignored");
+ }
+
+ @Override
+ public void encodeEnd(FacesContext context)
+ throws IOException
+ {
+ // call base
+ super.encodeEnd(context);
+ }
+
+ @Override
+ public String getId()
+ {
+ String compId = super.getId();
+ // Mojarra-Patch since Id might have been set to "null"
+ if ("null".equals(compId))
+ compId = helper.completeInputTagId(null);
+ // done
+ return compId;
+ }
+
+ @Override
+ public void setId(String id)
+ { // complete
+ id = helper.completeInputTagId(id);
+ // setId
+ super.setId(id);
+ }
+
+ @Override
+ public Object getValue()
+ {
+ // check for record
+ if (helper.getRecord() != null)
+ return helper.getDataValue(true);
+ // default
+ Object value = super.getValue();
+ return value;
+ }
+
+ @Override
+ public Object getSubmittedValue()
+ { // Check state
+ if (control == null || inpInfo == null || helper.isReadOnly())
+ return null;
+ // get Input Value
+ return control.getInputValue(this, inpInfo, true);
+ }
+
+ @Override
+ protected Object getConvertedValue(FacesContext context, Object newSubmittedValue)
+ throws ConverterException
+ { // Check state
+ if (control == null || inpInfo == null || helper.isReadOnly())
+ return null;
+ // parse and convert value
+ return this.control.getConvertedValue(this, this.inpInfo, newSubmittedValue);
+ }
+
+ @Override
+ public void processDecodes(FacesContext context)
+ {
+ // check UI-Data
+ if (helper.isInsideUIData() && getChildCount()>0)
+ { // update input state
+ updateControlInputState(context);
+ // Set Rendered for children (changed 2024-04-05)
+ // boolean readOnly = helper.isRecordReadOnly();
+ boolean renderValue = helper.isRenderValueComponent();
+ setRenderInput(!renderValue);
+ }
+ // default
+ super.processDecodes(context);
+ }
+
+ @Override
+ public void processValidators(FacesContext context)
+ {
+ // check UI-Data
+ if (helper.isInsideUIData() && getChildCount()>0)
+ { // update input state
+ updateControlInputState(context);
+ // No need to processValidators for children
+ setRenderInput(false);
+ }
+ // process all validators (including children)
+ super.processValidators(context);
+ }
+
+ @Override
+ public void validate(FacesContext context)
+ { // init state
+ if (initState(context) == false)
+ return;
+ // get submitted value and validate
+ if (log.isDebugEnabled())
+ log.debug("Validating input for {}.", inpInfo.getColumn().getName());
+
+ // Will internally call getSubmittedValue() and validateValue()
+ this.valueValidated = false;
+ super.validate(context);
+ // post check
+ if (!this.valueValidated && !helper.isReadOnly())
+ { // New since 2024-04-04
+ // Validate Record value
+ Object value = helper.getDataValue(true);
+ log.warn("No Submitted value for {}. Validating Record value of \"{}\" instead.", helper.getColumnName(), value);
+ if (value!=null)
+ validateValue(context, value);
+ }
+ }
+
+ @Override
+ public void validateValue(FacesContext context, Object value)
+ { // Validate value
+ try
+ { // Check state
+ if (inpInfo == null)
+ return;
+ // validateValue
+ if (helper.beginValidateValue(context, value))
+ inpInfo.validate(value);
+ setValid(true);
+ // don't call base class!
+ // super.validateValue(context, value);
+
+ } catch (Exception e) {
+ // Value is not valid
+ if (!(e instanceof EmpireException))
+ e = new FieldIllegalValueException(helper.getColumn(), "", e);
+ // Add error message
+ helper.addErrorMessage(context, e);
+ setValid(false);
+
+ } finally {
+ this.valueValidated = true;
+ }
+ }
+
+ @Override
+ public void updateModel(FacesContext context)
+ {
+ if (initState(context) == false)
+ return;
+ // No Action
+ if (!isValid() || !isLocalValueSet())
+ return;
+ // updateModel
+ Object value = getLocalValue();
+ if (helper.beginUpdateModel(context, value))
+ { // don't call base class!
+ // super.updateModel(context);
+ log.debug("Updating model input for {}.", this.inpInfo.getColumn().getName());
+ this.inpInfo.setValue(value);
+ }
+ setValue(null);
+ setLocalValueSet(false);
+ // Post update
+ control.postUpdateModel(this, inpInfo, context);
+ }
+
+ public Column getInputColumn()
+ {
+ return helper.getColumn();
+ }
+
+ public boolean isInputReadOnly()
+ {
+ return helper.isRecordReadOnly();
+ }
+
+ public boolean isInputRequired()
+ {
+ return helper.isValueRequired();
+ }
+
+ protected void updateControlInputState(FacesContext context)
+ {
+ // get control
+ helper.prepareData();
+ if (control==null)
+ control = helper.getInputControl();
+ if (inpInfo==null)
+ inpInfo = helper.getInputInfo(context);
+ // update control
+ control.updateInputState(this, inpInfo, context, context.getCurrentPhaseId());
+ }
+
+ protected void setRenderInput(boolean renderInput)
+ {
+ for (UIComponent child : getChildren())
+ {
+ if (child.isRendered()!=renderInput)
+ {
+ if (log.isDebugEnabled())
+ log.debug("Changing UIInput rendered state for {} to {}", helper.getColumnName(), renderInput);
+ child.setRendered(renderInput);
+ }
+ }
+ }
+
+ protected void attachEvents(FacesContext context)
+ {
+ // Events available?
+ @SuppressWarnings("unchecked")
+ List<AttachedObjectHandler> result = (List<AttachedObjectHandler>) getAttributes().get("jakarta.faces.RetargetableHandlers");
+ if (result == null)
+ {
+ return;
+ }
+ UIInput inputComponent = null;
+ for (UIComponent c : getChildren())
+ {
+ if (c instanceof UIInput)
+ { // found
+ inputComponent = (UIInput)c;
+ break;
+ }
+ }
+ if (inputComponent == null)
+ return;
+ // Attach Events
+ for (AttachedObjectHandler aoh : result)
+ {
+ aoh.applyAttachedObject(context, inputComponent);
+ }
+ // remove
+ result.clear();
+ getAttributes().remove("jakarta.faces.RetargetableHandlers");
+ }
+}
diff --git a/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/LabelTag.java b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/LabelTag.java
new file mode 100644
index 00000000..0fea3402
--- /dev/null
+++ b/empire-db-jakarta-faces/src/main/java/org/apache/empire/jakarta/components/LabelTag.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.empire.jakarta.components;
+
+import java.io.IOException;
+
+import jakarta.faces.component.UIOutput;
+import jakarta.faces.component.html.HtmlOutputLabel;
+import jakarta.faces.component.visit.VisitCallback;
+import jakarta.faces.component.visit.VisitContext;
+import jakarta.faces.context.FacesContext;
+
+import org.apache.empire.commons.ObjectUtils;
+import org.apache.empire.jakarta.utils.TagEncodingHelper;
+import org.apache.empire.jakarta.utils.TagEncodingHelperFactory;
+import org.apache.empire.jakarta.utils.TagStyleClass;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LabelTag extends UIOutput // implements NamingContainer
+{
+ // Logger
+ private static final Logger log = LoggerFactory.getLogger(LabelTag.class);
+
+ protected final TagEncodingHelper helper = TagEncodingHelperFactory.create(this, TagStyleClass.LABEL.get());
+
+ private boolean creatingComponents = false;
+
+ public LabelTag()
+ {
+ log.trace("component LabelTag created");
+ }
+
+ @Override
+ public String getFamily()
+ {
+ return "jakarta.faces.NamingContainer";
+ }
+
+ /**
+ * remember original clientId
+ * necessary only inside UIData
+ */
+ private String treeClientId = null;
+
+ @Override
... 13254 lines suppressed ...