diff --git a/.gitignore b/.gitignore
index c8bd3a8..3068568 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,6 @@
/starter-archetype/payara-starter-archetype.iml
/starter-archetype/src/main/resources/archetype-resources/.gradle
/starter-ui/payara-starter-ui.iml
+/PayaraStarterGenerator/target/
+starter-ui/nb-configuration.xml
+starter-ui/nb-configuration.xml
diff --git a/PayaraStarterGenerator/pom.xml b/PayaraStarterGenerator/pom.xml
new file mode 100644
index 0000000..8ea292f
--- /dev/null
+++ b/PayaraStarterGenerator/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ fish.payara.starter
+ payara-starter-parent
+ 1.0-beta8
+
+ payara-starter-generator
+ Payara Starter Generator
+ jar
+
+
+ org.freemarker
+ freemarker
+ 2.3.31
+ jar
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 17
+
+
+
+
+
\ No newline at end of file
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Attribute.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Attribute.java
new file mode 100644
index 0000000..2e722d9
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Attribute.java
@@ -0,0 +1,151 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.domain;
+
+import fish.payara.starter.application.util.AttributeType;
+import static fish.payara.starter.application.util.StringHelper.pluralize;
+import static fish.payara.starter.application.util.StringHelper.startCase;
+import static fish.payara.starter.application.util.StringHelper.titleCase;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class Attribute {
+
+ private final String name;
+ private final String type;
+ private boolean isPrimaryKey;
+ private boolean required;
+ private List _import = new ArrayList<>();
+ private Entity relation;
+ private boolean multi;
+
+ private final List property;
+
+ public Attribute(String name, String type, boolean isPrimaryKey, List property) {
+ this.name = name;
+ this.type = type;
+ this.isPrimaryKey = isPrimaryKey;
+ this.property = property;
+ }
+
+ public Attribute(String name, Entity relation, boolean multi, List property) {
+ this.name = name;
+ this.type = relation.getName();
+ this.relation = relation;
+ this.multi = multi;
+ this.property = property;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getStartCaseName() {
+ return startCase(name);
+ }
+
+ public String getLowerCaseName() {
+ return name.toLowerCase();
+ }
+
+ public String getTitleCaseName() {
+ return titleCase(name);
+ }
+
+ public String getLowerCasePluralizeName() {
+ return pluralize(name.toLowerCase());
+ }
+
+ public String getTitleCasePluralizeName() {
+ return pluralize(titleCase(name));
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public boolean isNumber() {
+ return AttributeType.isNumber(type);
+ }
+
+ public boolean isPrimaryKey() {
+ return isPrimaryKey;
+ }
+
+ public boolean isRequired() {
+ return required;
+ }
+
+ public boolean isMulti() {
+ return multi;
+ }
+
+ public Entity getRelation() {
+ return relation;
+ }
+
+ public List getProperty() {
+ return property;
+ }
+
+ public String getProperty(String key) {
+ for (KeyValue keyValue : property) {
+ if (keyValue.getKey().equals(key)) {
+ return keyValue.getValue();
+ }
+ }
+ return null;
+ }
+
+ public String getProperty(String key, String defaultValue) {
+ for (KeyValue keyValue : property) {
+ if (keyValue.getKey().equals(key)) {
+ return keyValue.getValue() == null ? defaultValue : keyValue.getValue();
+ }
+ }
+ return defaultValue;
+ }
+
+ public String getToolTipText() {
+ return getProperty("tooltip", "");
+ }
+
+ public boolean isToolTip() {
+ return !getToolTipText().trim().isEmpty();
+ }
+
+ public List getImports() {
+ return _import;
+ }
+
+ public boolean addImport(String e) {
+ return _import.add(e);
+ }
+
+ public boolean removeImport(String o) {
+ return _import.remove(o);
+ }
+
+ @Override
+ public String toString() {
+ return "\n\t\tAttribute{name=" + name + ", type=" + type + ", isPrimaryKey=" + isPrimaryKey + ", property=" + property + '}';
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/ERModel.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/ERModel.java
new file mode 100644
index 0000000..8a1235d
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/ERModel.java
@@ -0,0 +1,99 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.domain;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ERModel {
+
+ private final List entities = new ArrayList<>();
+ private final List relationships = new ArrayList<>();
+ private List property = Collections.EMPTY_LIST;
+
+ public void addEntity(Entity entity) {
+ entities.add(entity);
+ }
+
+ public Entity getEntity(String entityName) {
+ for (Entity entity : entities) {
+ if (entity.getName().equals(entityName)) {
+ return entity;
+ }
+ }
+ return null;
+ }
+
+ public void addRelationship(Relationship relationship) {
+ relationships.add(relationship);
+ }
+
+ public List getEntities() {
+ return entities;
+ }
+
+ public List getRelationships() {
+ return relationships;
+ }
+
+ public List getProperty() {
+ return property;
+ }
+
+ public void setProperty(List property) {
+ this.property = property;
+ }
+
+ public String getProperty(String key, String defaultValue) {
+ for (KeyValue keyValue : property) {
+ if (keyValue.getKey().equals(key)) {
+ return keyValue.getValue() == null ? defaultValue : keyValue.getValue();
+ }
+ }
+ return defaultValue;
+ }
+
+ public String getIcon() {
+ return getProperty("icon", "circle");
+ }
+
+ public String getTitle() {
+ return getProperty("title", "Jakarta EE Sample");
+ }
+
+ public String getLongTitle() {
+ return getProperty("long-title", getProperty("title", "EE Sample"));
+ }
+
+ public String getDescription() {
+ return getProperty("home-page-description", "Unlock the full potential of your application by harnessing the power of Jakarta EE");
+ }
+
+ public String getAboutUsDescription() {
+ return getProperty("about-us-page-description", "Welcome to our About Us page, where innovation meets reliability with Payara Jakarta EE. As a team passionate about delivering unparalleled solutions, we specialize in harnessing the power of Jakarta EE to create robust, scalable, and secure applications. With a deep understanding of enterprise-grade development, we are committed to crafting tailored solutions that drive business growth and exceed client expectations. Backed by years of experience and a dedication to staying at the forefront of technology, we take pride in our ability to transform ideas into reality, empowering businesses to thrive in the digital landscape. Discover more about our journey, expertise, and the vision that propels us forward.");
+ }
+
+ @Override
+ public String toString() {
+ return "ERModel{" + "\nentities=" + entities + "\n, relationships=" + relationships + '}';
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Entity.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Entity.java
new file mode 100644
index 0000000..ce9b826
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Entity.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.domain;
+
+import static fish.payara.starter.application.util.StringHelper.firstUpper;
+import static fish.payara.starter.application.util.StringHelper.pluralize;
+import static fish.payara.starter.application.util.StringHelper.startCase;
+import static fish.payara.starter.application.util.StringHelper.titleCase;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class Entity {
+
+ public String name;
+ private final List attributes = new ArrayList<>();
+ private final List property;
+
+ public Entity(String name, List property) {
+ this.name = name;
+ this.property = property;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getStartCaseName() {
+ return startCase(name);
+ }
+
+ public String getLowerCaseName() {
+ return name.toLowerCase();
+ }
+
+ public String getTitleCaseName() {
+ return titleCase(name);
+ }
+
+ public String getLowerCasePluralizeName() {
+ return pluralize(name.toLowerCase());
+ }
+
+ public String getTitleCasePluralizeName() {
+ return getProperty("title", pluralize(titleCase(name)));
+ }
+
+ public List getAttributes() {
+ return attributes;
+ }
+
+ public String getPrimaryKeyType() {
+ return attributes.stream().filter(a -> a.isPrimaryKey()).map(a -> a.getType()).findFirst().orElse("Long");
+ }
+
+ public String getPrimaryKeyName() {
+ return attributes.stream().filter(a -> a.isPrimaryKey()).map(a -> a.getName()).findFirst().orElse(null);
+ }
+
+ public String getPrimaryKeyFirstUpperName() {
+ return firstUpper(getPrimaryKeyName());
+ }
+
+ public String getDisplayName() {
+ String displayName = attributes.stream().filter(a -> Boolean.valueOf(a.getProperty("display"))).map(a -> a.getName()).findFirst().orElse(null);
+ if(displayName == null) {
+ displayName = attributes.stream().filter(a -> !a.isPrimaryKey()).map(a -> a.getName()).findFirst().orElse(null);
+
+ }
+ return displayName;
+ }
+
+ public void addAttribute(Attribute attribute) {
+ attributes.add(attribute);
+ }
+
+ public List getProperty() {
+ return property;
+ }
+
+ public KeyValue getProperty(String key) {
+ for (KeyValue keyValue : property) {
+ if (keyValue.getKey().equals(key)) {
+ return keyValue;
+ }
+ }
+ return null;
+ }
+
+ public String getProperty(String key, String defaultValue) {
+ for (KeyValue keyValue : property) {
+ if (keyValue.getKey().equals(key)) {
+ return keyValue.getValue() == null ? defaultValue : keyValue.getValue();
+ }
+ }
+ return defaultValue;
+ }
+
+ public String getIcon() {
+ return getProperty("icon", "circle");
+ }
+
+ public String getTitle() {
+ return getProperty("title", titleCase(name));
+ }
+
+ public String getDescription() {
+ return getProperty("description", "");
+ }
+
+ @Override
+ public String toString() {
+ return "\n\tEntity{" + "name=" + name + ", attributes=" + attributes + ", property=\n\t\t" + property + '}';
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/KeyValue.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/KeyValue.java
new file mode 100644
index 0000000..39f9a82
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/KeyValue.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.domain;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class KeyValue {
+
+ private final String key;
+ private final String value;
+
+ public KeyValue(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + key + "=" + value + '}';
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Relationship.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Relationship.java
new file mode 100644
index 0000000..73352d5
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/domain/Relationship.java
@@ -0,0 +1,74 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.domain;
+
+import java.util.List;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class Relationship {
+
+ private final String firstEntity;
+ private final String secondEntity;
+ private final String relationshipType;
+ private final String relationshipLabel;
+ private final List property;
+
+ public Relationship(String firstEntity, String secondEntity, String relationshipType, String relationshipLabel, List property) {
+ this.firstEntity = firstEntity;
+ this.secondEntity = secondEntity;
+ this.relationshipType = relationshipType;
+ this.relationshipLabel = relationshipLabel;
+ this.property = property;
+ }
+
+ public String getFirstEntity() {
+ return firstEntity;
+ }
+
+ public String getSecondEntity() {
+ return secondEntity;
+ }
+
+ public String getRelationshipType() {
+ return relationshipType;
+ }
+
+ public String getRelationshipLabel() {
+ return relationshipLabel;
+ }
+
+ public List getProperty() {
+ return property;
+ }
+
+ public KeyValue getProperty(String key) {
+ for (KeyValue keyValue : property) {
+ if (keyValue.getKey().equals(key)) {
+ return keyValue;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "Relationship{" + "firstEntity=" + firstEntity + ", secondEntity=" + secondEntity + ", relationshipType=" + relationshipType + ", relationshipLabel=" + relationshipLabel + '}';
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/generator/CRUDAppGenerator.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/generator/CRUDAppGenerator.java
new file mode 100644
index 0000000..39f190e
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/generator/CRUDAppGenerator.java
@@ -0,0 +1,633 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.generator;
+
+import fish.payara.starter.application.domain.Entity;
+import fish.payara.starter.application.domain.Relationship;
+import fish.payara.starter.application.domain.ERModel;
+import fish.payara.starter.application.domain.Attribute;
+import static fish.payara.starter.application.util.AttributeType.isBoolean;
+import static fish.payara.starter.application.util.AttributeType.isPrimitive;
+import static fish.payara.starter.application.util.JPAUtil.SQL_RESERVED_KEYWORDS;
+import static fish.payara.starter.application.util.JavaUtil.getIntrospectionPrefix;
+import static fish.payara.starter.application.util.JavaUtil.getMethodName;
+import static fish.payara.starter.application.util.StringHelper.firstLower;
+import static fish.payara.starter.application.util.StringHelper.firstUpper;
+import static fish.payara.starter.application.util.StringHelper.kebabCase;
+import static fish.payara.starter.application.util.StringHelper.pluralize;
+import static fish.payara.starter.application.util.StringHelper.startCase;
+import static fish.payara.starter.application.util.StringHelper.titleCase;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class CRUDAppGenerator {
+
+ private final String _package;
+ private final String domainLayer;
+ private final String repositoryLayer;
+ private final String controllerLayer;
+ private final ERModel model;
+ private static final boolean IS_LOCAL = false; // Change this flag for local vs. production
+
+ public CRUDAppGenerator(ERModel model, String _package, String domainLayer, String repositoryLayer, String controllerLayer) {
+ this._package = _package;
+ this.model = model;
+ if (domainLayer == null || domainLayer.trim().isEmpty()) {
+ domainLayer = "domain";
+ }
+ if (repositoryLayer == null || repositoryLayer.trim().isEmpty()) {
+ repositoryLayer = "service";
+ }
+ if (controllerLayer == null || controllerLayer.trim().isEmpty()) {
+ controllerLayer = "resource";
+ }
+ this.domainLayer = domainLayer;
+ this.repositoryLayer = repositoryLayer;
+ this.controllerLayer = controllerLayer;
+ }
+
+ public static void main(String[] args) {
+ String mermaidCode = "erDiagram\n" +
+" USER ||--o{ JOB_APPLICATION : applies %%{ USER[applications],JOB_APPLICATION[user] }%%\n" +
+" USER { %%{ icon[person],title[Recruitment Management System],description[A system for managing recruitment processes. Post job openings, receive applications, and manage candidates efficiently.],menu[Home, Jobs, Candidates, About Us, Contact Us] }%%\n" +
+" string userId PK %%{ htmllabel[User ID],required[true] }%%\n" +
+" string username %%{ display[true],required[true],tooltip[Username for login] }%%\n" +
+" string email %%{ tooltip[User's email address] }%%\n" +
+" }\n" +
+" JOB_APPLICATION ||--|{ JOB : applies_for %%{ JOB_APPLICATION[job],JOB[applications] }%%\n" +
+" JOB_APPLICATION { %%{ icon[file-text],title[Job Application],description[Track job applications submitted by candidates.],menu[My Applications, Jobs, About Us, Contact Us] }%%\n" +
+" int applicationId PK %%{ display[true] }%%\n" +
+" string status %%{ tooltip[Application status] }%%\n" +
+" string resume %%{ tooltip[Link to candidate's resume] }%%\n" +
+" }\n" +
+" JOB { %%{ icon[briefcase],title[Job],description[Manage job openings and applications.],menu[Jobs, Candidates, About Us, Contact Us] }%% \n" +
+" int jobId PK \n" +
+" string title %%{ display[true],required[true],tooltip[Job title] }%%\n" +
+" string description %%{ tooltip[Job description] }%%\n" +
+" date startDate %%{ tooltip[Start date of employment] }%%\n" +
+" date endDate %%{ tooltip[End date of employment] }%%\n" +
+" }\n" +
+" CANDIDATE ||--o{ JOB_APPLICATION : submits %%{ CANDIDATE[applications],JOB_APPLICATION[candidate] }%%\n" +
+" CANDIDATE { %%{ icon[person],title[Candidate],description[Manage candidate profiles and applications.],menu[Candidates, Jobs, About Us, Contact Us] }%% \n" +
+" int candidateId PK \n" +
+" string name %%{ display[true],required[true],tooltip[Candidate's name] }%%\n" +
+" string email %%{ tooltip[Candidate's email address] }%%\n" +
+" string phone %%{ tooltip[Candidate's phone number] }%%\n" +
+" }\n" +
+" INTERVIEW ||--|{ CANDIDATE : schedules %%{ INTERVIEW[candidate],CANDIDATE[interviews] }%%\n" +
+" INTERVIEW { %%{ icon[calendar],title[Interview],description[Schedule and manage interviews with candidates.],menu[Interviews, Candidates, About Us, Contact Us] }%% \n" +
+" int interviewId PK \n" +
+" date date %%{ display[true],tooltip[Interview date] }%%\n" +
+" string location %%{ tooltip[Interview location] }%%\n" +
+" }\n" +
+" RECRUITER ||--o{ INTERVIEW : schedules %%{ RECRUITER[interviews],INTERVIEW[recruiter] }%%\n" +
+" RECRUITER { %%{ icon[person],title[Recruiter],description[Manage recruiter profiles and interview schedules.],menu[Recruiters, Interviews, About Us, Contact Us] }%% \n" +
+" int recruiterId PK \n" +
+" string name %%{ display[true],required[true],tooltip[Recruiter's name] }%%\n" +
+" string department %%{ tooltip[Recruiter's department] }%%\n" +
+" }\n" +
+" OFFER ||--o{ CANDIDATE : extends %%{ OFFER[candidate],CANDIDATE[offer] }%%\n" +
+" OFFER { %%{ icon[document],title[Job Offer],description[Create and manage job offers extended to candidates.],menu[Offers, Candidates, About Us, Contact Us] }%% \n" +
+" int offerId PK \n" +
+" string status %%{ tooltip[Offer status] }%%\n" +
+" float salary %%{ tooltip[Offered salary] }%%\n" +
+" }\n" +
+" FEEDBACK ||--|{ INTERVIEW : provides %%{ FEEDBACK[interview],INTERVIEW[feedback] }%%\n" +
+" FEEDBACK { %%{ icon[comment],title[Interview Feedback],description[Provide feedback on candidate interviews.],menu[Feedback, Interviews, About Us, Contact Us] }%% \n" +
+" int feedbackId PK \n" +
+" string comments %%{ display[true],tooltip[Interviewer's comments] }%%\n" +
+" int rating %%{ tooltip[Interview rating] }%%\n" +
+" }\n" +
+"%%{ icon[briefcase],title[Recruitment Management System],home-page-description[A system for managing recruitment processes. Post job openings, receive applications, and manage candidates efficiently.],about-us-page-description[Explore our recruitment management system and streamline your hiring process. Connect with talented candidates and make informed decisions.],menu[Home, Jobs, Candidates, About Us, Contact Us] }%% \n" +
+"";
+
+ ERDiagramParser parser = new ERDiagramParser();
+ ERModel erModel = parser.parse(mermaidCode);
+
+ String _package = "fish.payara.example";
+ String domainLayer = "domain";
+ String repositoryLayer = "service";
+ String controllerLayer = "resource";
+ CRUDAppGenerator generator = new CRUDAppGenerator(erModel, _package, domainLayer, repositoryLayer, controllerLayer);
+ try {
+ generator.generate(new File("D:\\HelloWorld"), true, true, true, true); // Output directory
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void generate(File projectDir, boolean generateJPA, boolean generateRepository, boolean generateController, boolean generateWeb) throws IOException {
+
+ File java = getDir(projectDir, "src/main/java");
+ File resources = getDir(projectDir, "src/main/resources");
+ File metainf = getDir(resources, "META-INF");
+ File webapp = getDir(projectDir, "src/main/webapp");
+ File webinf = getDir(webapp, "WEB-INF");
+ if (generateJPA) {
+ for (Entity entity : model.getEntities()) {
+ generateJPAClass(_package, model, entity, java);
+ }
+ Map dataModel = new HashMap<>();
+ dataModel.put("appPU", model.getProperty("title", "app") + "PU");
+ generate("template/descriptor", "persistence.xml.ftl", "persistence.xml", dataModel, metainf);
+
+ if (generateRepository) {
+ for (Entity entity : model.getEntities()) {
+ generateEntityRepository(_package, entity, java);
+ }
+ generate("template/descriptor", "beans.xml.ftl", "beans.xml", dataModel, webinf);
+ generateRepositoryBase(dataModel, _package, java);
+ if (generateController) {
+ for (Entity entity : model.getEntities()) {
+ generateEntityController(_package, entity, java);
+ }
+ generateRestBase(dataModel, _package, java);
+ if (generateWeb) {
+ for (Entity entity : model.getEntities()) {
+ generateFrontend(entity, webapp);
+ }
+ generateFrontendBase(model, webapp);
+ }
+ }
+ }
+ }
+ }
+
+ private void generateFrontendBase(ERModel model, File outputDir) {
+ Map dataModel = new HashMap<>();
+ dataModel.put("model", model);
+ generate("template/html", "app.html.ftl", "main.html", dataModel, outputDir);
+ generate("template/html", "home.html.ftl", "home.html", dataModel, outputDir);
+ generate("template/html", "about-us.html.ftl", "about-us.html", dataModel, outputDir);
+ }
+
+ private void generateFrontend(Entity entity, File outputDir) {
+ Map dataModel = new HashMap<>();
+ dataModel.put("entity", entity);
+ dataModel.put("entityNameLowerCase", entity.name.toLowerCase());
+ dataModel.put("entityNameTitleCase", titleCase(entity.name));
+ dataModel.put("entityNameTitleCasePluralize", pluralize(titleCase(entity.name)));
+ dataModel.put("entityNameLowerCasePluralize", pluralize(entity.name.toLowerCase()));
+ generate("template/html", "entity.html.ftl", dataModel.get("entityNameLowerCase") + ".html", dataModel, outputDir);
+ }
+
+ private void generateEntityController(String _package, Entity entity, File outputDir) {
+ // Configure FreeMarker
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
+ try {
+
+ if (IS_LOCAL) {
+ // Local development - load templates from the file system
+ cfg.setDirectoryForTemplateLoading(new File("src/main/resources/template/rest"));
+ } else {
+ // Production - load templates from the classpath
+ cfg.setClassLoaderForTemplateLoading(
+ Thread.currentThread().getContextClassLoader(),
+ "template/rest"
+ );
+ }
+ cfg.setDefaultEncoding("UTF-8");
+
+ // Load the template
+ Template template = cfg.getTemplate("EntityController.java.ftl");
+
+ // Create the data model
+ String repositoryPackage = _package + "." + repositoryLayer;
+ String controllerPackage = _package + "." + controllerLayer;
+
+ String entityInstance = firstLower(entity.getName());
+ String entityNameSpinalCased = kebabCase(entityInstance);
+ Map dataModel = new HashMap<>();
+ dataModel.put("package", controllerPackage);
+ dataModel.put("entity", entity);
+ dataModel.put("EntityClass", entity.getName());
+ dataModel.put("EntityClassPlural", pluralize(firstUpper(entity.getName())));
+ dataModel.put("EntityClass_FQN", _package + "."+ domainLayer+"." + entity.getName());
+ dataModel.put("entityInstance", entityInstance);
+ dataModel.put("entityInstancePlural", pluralize(entityInstance));
+ dataModel.put("entityTranslationKey", entityInstance);
+
+ String repositoryFileName = entity.getName() + firstUpper(repositoryLayer);
+ String controllerFileName = entity.getName() + firstUpper(controllerLayer);
+
+ dataModel.put("controllerClass", controllerFileName);
+ dataModel.put("controllerClassHumanized", startCase(controllerFileName));
+ dataModel.put("entityApiUrl", entityNameSpinalCased);
+
+ dataModel.put("EntityRepository", repositoryFileName);
+ dataModel.put("entityRepository", firstLower(repositoryFileName));
+ dataModel.put("EntityRepository_FQN", repositoryPackage + "." + repositoryFileName);
+ dataModel.put("EntityRepository_package", repositoryPackage);
+ dataModel.put("EntityRepositorySuffix", firstUpper(repositoryLayer));
+
+ boolean dto = false;
+ dataModel.put("instanceType", dto ? entity.getName() + "DTO" : entity.getName());
+ dataModel.put("instanceName", dto ? entityInstance + "DTO" : entityInstance);
+
+ dataModel.put("pagination", "no");
+ dataModel.put("fieldsContainNoOwnerOneToOne", false);
+ dataModel.put("metrics", false);
+ dataModel.put("openAPI", false);
+ dataModel.put("applicationPath", "resources");
+
+ String pkName = entity.getPrimaryKeyName();
+ String pkType = entity.getPrimaryKeyType();
+ dataModel.put("pkName", firstLower(pkName));
+// System.out.println("getIntrospectionPrefix " + getIntrospectionPrefix(isBoolean(pkType)));
+// System.out.println("pkType " + pkType);
+// System.out.println("pkName " + pkName);
+ dataModel.put("pkGetter", getMethodName(getIntrospectionPrefix(isBoolean(pkType)), pkName));
+ dataModel.put("pkSetter", getMethodName("set", pkName));
+ dataModel.put("pkType", pkType);
+ dataModel.put("isPKPrimitive", isPrimitive(pkType));
+
+ // Output file path
+ File outputPackageDir = new File(outputDir, controllerPackage.replace(".", File.separator));
+ if (!outputPackageDir.exists()) {
+ outputPackageDir.mkdirs();
+ }
+ File outputFile = new File(outputPackageDir, dataModel.get("controllerClass") + ".java");
+
+ // Write the generated file
+ try (FileWriter writer = new FileWriter(outputFile)) {
+ template.process(dataModel, writer);
+ }
+
+ } catch (IOException | TemplateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void generateEntityRepository(String _package, Entity entity, File outputDir) {
+ // Configure FreeMarker
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
+ try {
+ if (IS_LOCAL) {
+ // Local development - load templates from the file system
+ cfg.setDirectoryForTemplateLoading(new File("src/main/resources/template/repository"));
+ } else {
+ // Production - load templates from the classpath
+ cfg.setClassLoaderForTemplateLoading(
+ Thread.currentThread().getContextClassLoader(),
+ "template/repository"
+ );
+ }
+ cfg.setDefaultEncoding("UTF-8");
+
+ // Load the template
+ Template template = cfg.getTemplate("EntityRepository.java.ftl");
+
+ // Create the data model
+ String repositoryPackage = _package + "." + repositoryLayer;
+ Map dataModel = new HashMap<>();
+ dataModel.put("package", repositoryPackage);
+ dataModel.put("cdi", true);
+ dataModel.put("named", false);
+ dataModel.put("entityInstance", "exampleEntityRepository");
+ dataModel.put("EntityClass", entity.getName());
+ dataModel.put("EntityRepository", entity.getName() + firstUpper(repositoryLayer));
+ dataModel.put("EntityClass_FQN", _package + "."+domainLayer+"." + entity.getName());
+ dataModel.put("EntityPKClass", entity.getPrimaryKeyType());
+ dataModel.put("EntityPKClass_FQN", "");
+ dataModel.put("AbstractRepository", "Abstract" + firstUpper(repositoryLayer));
+ dataModel.put("AbstractRepository_FQN", _package + "." + repositoryLayer + "." + "Abstract" + firstUpper(repositoryLayer));
+
+ // Output file path
+ File outputPackageDir = new File(outputDir, repositoryPackage.replace(".", File.separator));
+ if (!outputPackageDir.exists()) {
+ outputPackageDir.mkdirs();
+ }
+ File outputFile = new File(outputPackageDir, dataModel.get("EntityRepository") + ".java");
+
+ // Write the generated file
+ try (FileWriter writer = new FileWriter(outputFile)) {
+ template.process(dataModel, writer);
+ }
+
+ } catch (IOException | TemplateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void generateRestBase(Map dataModel, String _package, File outputDir) {
+
+ dataModel.put("package", _package + "." + controllerLayer);
+ generate("template/rest", "RestConfiguration.java.ftl", "RestConfiguration.java", dataModel, outputDir);
+
+ dataModel.put("frontendAppName", "appName");
+ generate("template/rest", "HeaderUtil.java.ftl", "HeaderUtil.java", dataModel, outputDir);
+ }
+
+ private void generateRepositoryBase(Map dataModel, String _package, File outputDir) {
+
+ dataModel.put("package", _package + "." + repositoryLayer + ".producer");
+ generate("template/service/producer", "EntityManagerProducer.java.ftl", "EntityManagerProducer.java", dataModel, outputDir);
+
+ dataModel.put("package", _package + "." + repositoryLayer);
+ dataModel.put("cdi", true);
+ dataModel.put("AbstractRepository", "Abstract" + firstUpper(repositoryLayer));
+ generate("template/repository", "AbstractRepository.java.ftl", "Abstract" + firstUpper(repositoryLayer) + ".java", dataModel, outputDir);
+ }
+
+ private void generate(String templatePath, String templateName, String outputFileName, Map dataModel, File outputDir) {
+ // Configure FreeMarker
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
+ try {
+ if (IS_LOCAL) {
+ // Local development - load templates from the file system
+ cfg.setDirectoryForTemplateLoading(new File("src/main/resources/" + templatePath));
+ } else {
+ // Production - load templates from the classpath
+ cfg.setClassLoaderForTemplateLoading(
+ Thread.currentThread().getContextClassLoader(),
+ templatePath
+ );
+ }
+ cfg.setDefaultEncoding("UTF-8");
+
+ // Load the template
+ Template template = cfg.getTemplate(templateName);
+
+ // Output file path
+ File outputPackageDir = outputDir;
+ if (dataModel.get("package") != null) {
+ outputPackageDir = new File(outputDir, ((String) dataModel.get("package")).replace(".", File.separator));
+ if (!outputPackageDir.exists()) {
+ outputPackageDir.mkdirs();
+ }
+ }
+
+ File outputFile = new File(outputPackageDir, outputFileName);
+
+ // Write the generated file
+ try (FileWriter writer = new FileWriter(outputFile)) {
+ template.process(dataModel, writer);
+ }
+
+ } catch (IOException | TemplateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private String escapeReservedKeyword(String name) {
+ return SQL_RESERVED_KEYWORDS.contains(name.toUpperCase()) ? "\\\"" + name + "\\\"" : name;
+ }
+
+ private void generateJPAClass(String _package, ERModel model, Entity entity, File outputDir) throws IOException {
+ List relationships = model.getRelationships();
+ String className = entity.getName();
+ StringBuilder sbHeader = new StringBuilder();
+ StringBuilder sbfunc = new StringBuilder();
+ String entityPackage = _package + "." + domainLayer;
+ sbHeader.append("package ").append(entityPackage).append(";\n\n");
+ sbHeader.append("import jakarta.persistence.*;\n");
+
+ StringBuilder sbBody = new StringBuilder();
+ // Generate named queries
+ sbBody.append(generateNamedQueries(entity));
+ String entityName = entity.getName();
+ String escapedEntityName = escapeReservedKeyword(entityName);
+ if (entityName.length() < escapedEntityName.length()) {
+ sbBody.append("@Table(name = \"").append(escapedEntityName).append("\")\n");
+ }
+ sbBody.append("@Entity\n");
+ sbBody.append("public class ").append(className).append(" {\n\n");
+
+ Set _imports = new HashSet<>();
+ for (Attribute attribute : entity.getAttributes()) {
+ if (attribute.isPrimaryKey()) {
+ sbBody.append(" @Id\n");
+ sbBody.append(" @GeneratedValue(strategy = GenerationType.AUTO)\n");
+ }
+ String attributeName = attribute.getName();
+ String escapedAttributeName = escapeReservedKeyword(attributeName);
+ if (attributeName.length() < escapedAttributeName.length()) {
+ sbBody.append(" @Column(name = \"").append(escapedAttributeName).append("\")\n");
+ }
+ sbBody.append(" private ").append(attribute.getType()).append(" ").append(attribute.getName()).append(";\n\n");
+ _imports.addAll(attribute.getImports());
+ }
+
+ sbfunc.append("\n // Getters and setters\n\n");
+ for (Attribute attribute : entity.getAttributes()) {
+ String type = attribute.getType();
+ String name = attribute.getName();
+ String capitalized = name.substring(0, 1).toUpperCase() + name.substring(1);
+ sbfunc.append(" public ").append(type).append(" get").append(capitalized).append("() {\n");
+ sbfunc.append(" return ").append(name).append(";\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(capitalized).append("(").append(type).append(" ").append(name).append(") {\n");
+ sbfunc.append(" this.").append(name).append(" = ").append(name).append(";\n");
+ sbfunc.append(" }\n\n");
+ }
+
+ for (Relationship relationship : relationships) {
+ if (relationship.getFirstEntity().equals(className)) {
+ appendRelationship(sbBody, sbfunc, _imports, model, entity, relationship, true);
+ } else if (relationship.getSecondEntity().equals(className)) {
+ appendRelationship(sbBody, sbfunc, _imports, model, entity, relationship, false);
+ }
+ }
+
+ for (String _import : _imports) {
+ sbHeader.append("import ").append(_import).append(";\n");
+ }
+ sbHeader.append("\n");
+ sbHeader.append(sbBody);
+ sbHeader.append(sbfunc);
+ sbHeader.append("}\n");
+
+ File outputPackageDir = new File(outputDir, entityPackage.replace(".", File.separator));
+ if (!outputPackageDir.exists()) {
+ outputPackageDir.mkdirs();
+ }
+ File outputFile = new File(outputPackageDir, className + ".java");
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile))) {
+ writer.write(sbHeader.toString());
+ }
+ }
+
+ private String generateNamedQueries(Entity entity) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("@NamedQueries({\n");
+
+ for (Attribute attribute : entity.getAttributes()) {
+ String capitalized = attribute.getName().substring(0, 1).toUpperCase() + attribute.getName().substring(1);
+ String queryName = entity.getName() + ".findBy" + capitalized;
+ String queryString = "SELECT e FROM " + entity.getName() + " e WHERE e." + attribute.getName() + " = :" + attribute.getName();
+ sb.append(" @NamedQuery(name = \"").append(queryName).append("\", query = \"").append(queryString).append("\"),\n");
+ }
+
+ // Remove the last comma
+ sb.setLength(sb.length() - 2);
+
+ sb.append("\n})\n");
+ return sb.toString();
+ }
+
+ private void appendRelationship(StringBuilder sb, StringBuilder sbfunc, Set _imports, ERModel model, Entity entity, Relationship relationship, boolean isFirstEntity) {
+ String relationshipType = relationship.getRelationshipType();
+ String firstEntity = relationship.getFirstEntity();
+ String secondEntity = relationship.getSecondEntity();
+ Entity firstEntityObj = model.getEntity(firstEntity);
+ Entity secondEntityObj = model.getEntity(secondEntity);
+
+ if (isFirstEntity) {
+ switch (relationshipType) {
+ case "||--||": // Exactly one to exactly one
+ case "||--o|": // Exactly one to zero or one
+ case "|o--||": // Zero or one to exactly one
+ entity.getAttributes().add(new Attribute(secondEntity.toLowerCase(), secondEntityObj, false, relationship.getProperty()));
+ sbfunc.append(" public ").append(secondEntity).append(" get").append(secondEntity).append("() {\n");
+ sbfunc.append(" return ").append(secondEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(secondEntity).append("(").append(secondEntity).append(" ").append(secondEntity.toLowerCase()).append(") {\n");
+ sbfunc.append(" this.").append(secondEntity.toLowerCase()).append(" = ").append(secondEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sb.append(" @OneToOne(mappedBy = \"").append(firstEntity.toLowerCase()).append("\")\n");
+ sb.append(" private ").append(secondEntity).append(" ").append(secondEntity.toLowerCase()).append(";\n");
+ break;
+ case "||--|{": // Exactly one to one or more
+ case "||--o{": // Exactly one to zero or more
+// entity.getAttributes().add(new Attribute(secondEntity.toLowerCase(), secondEntityObj, true, relationship.getProperty()));
+ sbfunc.append(" public List<").append(secondEntity).append("> get").append(secondEntity).append("() {\n");
+ sbfunc.append(" return ").append(secondEntity.toLowerCase()).append("s;\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(secondEntity).append("(List<").append(secondEntity).append("> ").append(secondEntity.toLowerCase()).append("s) {\n");
+ sbfunc.append(" this.").append(secondEntity.toLowerCase()).append("s = ").append(secondEntity.toLowerCase()).append("s;\n");
+ sbfunc.append(" }\n\n");
+ _imports.add("jakarta.json.bind.annotation.JsonbTransient");
+ _imports.add("java.util.List");
+ sb.append(" @JsonbTransient\n");
+ sb.append(" @OneToMany(mappedBy = \"").append(firstEntity.toLowerCase()).append("\")\n");
+ sb.append(" private List<").append(secondEntity).append("> ").append(secondEntity.toLowerCase()).append("s;\n");
+ break;
+ case "}|--||": // One or more to exactly one
+ case "}o--||": // Zero or more to exactly one
+ entity.getAttributes().add(new Attribute(secondEntity.toLowerCase(), secondEntityObj, false, relationship.getProperty()));
+ sbfunc.append(" public ").append(secondEntity).append(" get").append(secondEntity).append("() {\n");
+ sbfunc.append(" return ").append(secondEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(secondEntity).append("(").append(secondEntity).append(" ").append(secondEntity.toLowerCase()).append(") {\n");
+ sbfunc.append(" this.").append(secondEntity.toLowerCase()).append(" = ").append(secondEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sb.append(" @ManyToOne(mappedBy = \"").append(firstEntity.toLowerCase()).append("\")\n");
+ sb.append(" private ").append(secondEntity).append(" ").append(secondEntity.toLowerCase()).append(";\n");
+ break;
+ case "}o--o{": // Zero or more to zero or more
+ case "}|--o{": // One or more to zero or more
+ case "}o--|{": // Zero or more to one or more
+ case "}|--|{": // One or more to one or more
+// entity.getAttributes().add(new Attribute(secondEntity.toLowerCase(), secondEntityObj, true, relationship.getProperty()));
+ sbfunc.append(" public List<").append(secondEntity).append("> get").append(secondEntity).append("() {\n");
+ sbfunc.append(" return ").append(secondEntity.toLowerCase()).append("s;\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(secondEntity).append("(List<").append(secondEntity).append("> ").append(secondEntity.toLowerCase()).append("s) {\n");
+ sbfunc.append(" this.").append(secondEntity.toLowerCase()).append("s = ").append(secondEntity.toLowerCase()).append("s;\n");
+ sbfunc.append(" }\n\n");
+ _imports.add("jakarta.json.bind.annotation.JsonbTransient");
+ _imports.add("java.util.List");
+ sb.append(" @JsonbTransient\n");
+ sb.append(" @ManyToMany(mappedBy = \"").append(firstEntity.toLowerCase()).append("\")\n");
+ sb.append(" private List<").append(secondEntity).append("> ").append(secondEntity.toLowerCase()).append("s;\n");
+ break;
+ }
+ } else {
+ switch (relationshipType) {
+ case "||--||": // Exactly one to exactly one
+ case "||--o|": // Exactly one to zero or one
+ case "|o--||": // Zero or one to exactly one
+// entity.getAttributes().add(new Attribute(firstEntity.toLowerCase(), firstEntityObj, false, relationship.getProperty()));
+ sbfunc.append(" public ").append(firstEntity).append(" get").append(firstEntity).append("() {\n");
+ sbfunc.append(" return ").append(firstEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(firstEntity).append("(").append(firstEntity).append(" ").append(firstEntity.toLowerCase()).append(") {\n");
+ sbfunc.append(" this.").append(firstEntity.toLowerCase()).append(" = ").append(firstEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ _imports.add("jakarta.json.bind.annotation.JsonbTransient");
+ sb.append(" @JsonbTransient\n");
+ sb.append(" @OneToOne\n");
+ sb.append(" @JoinColumn(name = \"").append(firstEntity.toLowerCase()).append("_id\")\n");
+ sb.append(" private ").append(firstEntity).append(" ").append(firstEntity.toLowerCase()).append(";\n");
+ break;
+ case "||--|{": // Exactly one to one or more
+ case "||--o{": // Exactly one to zero or more
+ entity.getAttributes().add(new Attribute(firstEntity.toLowerCase(), firstEntityObj, false, relationship.getProperty()));
+ sbfunc.append(" public ").append(firstEntity).append(" get").append(firstEntity).append("() {\n");
+ sbfunc.append(" return ").append(firstEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(firstEntity).append("(").append(firstEntity).append(" ").append(firstEntity.toLowerCase()).append(") {\n");
+ sbfunc.append(" this.").append(firstEntity.toLowerCase()).append(" = ").append(firstEntity.toLowerCase()).append(";\n");
+ sbfunc.append(" }\n\n");
+ sb.append(" @ManyToOne\n");
+ sb.append(" @JoinColumn(name = \"").append(firstEntity.toLowerCase()).append("_id\")\n");
+ sb.append(" private ").append(firstEntity).append(" ").append(firstEntity.toLowerCase()).append(";\n");
+ break;
+// case "}|--||": // One or more to exactly one
+// case "}o--||": // Zero or more to exactly one
+// sb.append(" @OneToMany\n");
+// sb.append(" @JoinColumn(name = \"").append(firstEntity.toLowerCase()).append("_id\")\n");
+// sb.append(" private ").append(firstEntity).append(" ").append(firstEntity.toLowerCase()).append(";\n");
+// break;
+ case "}o--o{": // Zero or more to zero or more
+ case "}|--o{": // One or more to zero or more
+ case "}o--|{": // Zero or more to one or more
+ case "}|--|{": // One or more to one or more
+// entity.getAttributes().add(new Attribute(firstEntity.toLowerCase(), firstEntityObj, true, relationship.getProperty()));
+ sbfunc.append(" public List<").append(firstEntity).append("> get").append(firstEntity).append("() {\n");
+ sbfunc.append(" return ").append(firstEntity.toLowerCase()).append("s;\n");
+ sbfunc.append(" }\n\n");
+ sbfunc.append(" public void set").append(firstEntity).append("(List<").append(firstEntity).append("> ").append(firstEntity.toLowerCase()).append("s) {\n");
+ sbfunc.append(" this.").append(firstEntity.toLowerCase()).append("s = ").append(firstEntity.toLowerCase()).append("s;\n");
+ sbfunc.append(" }\n\n");
+ _imports.add("jakarta.json.bind.annotation.JsonbTransient");
+ _imports.add("java.util.List");
+ sb.append(" @JsonbTransient\n");
+ sb.append(" @ManyToMany\n");
+ sb.append(" @JoinColumn(name = \"").append(firstEntity.toLowerCase()).append("_id\")\n");
+ sb.append(" private List<").append(firstEntity).append("> ").append(firstEntity.toLowerCase()).append("s;\n");
+ break;
+ }
+ }
+ }
+
+ private File getDir(String parent, String path) {
+ File outputDir = new File(parent, path);
+ if (!outputDir.exists()) {
+ outputDir.mkdirs();
+ }
+ return outputDir;
+ }
+
+ private File getDir(File parent, String path) {
+ File outputDir = new File(parent, path);
+ if (!outputDir.exists()) {
+ outputDir.mkdirs();
+ }
+ return outputDir;
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/generator/ERDiagramParser.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/generator/ERDiagramParser.java
new file mode 100644
index 0000000..d4f81b3
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/generator/ERDiagramParser.java
@@ -0,0 +1,125 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.generator;
+
+import fish.payara.starter.application.domain.Entity;
+import fish.payara.starter.application.domain.Relationship;
+import fish.payara.starter.application.domain.ERModel;
+import fish.payara.starter.application.domain.Attribute;
+import fish.payara.starter.application.domain.KeyValue;
+import static fish.payara.starter.application.util.AttributeType.LOCAL_DATE;
+import static fish.payara.starter.application.util.AttributeType.LOCAL_DATE_TIME;
+import static fish.payara.starter.application.util.AttributeType.getWrapperType;
+import static fish.payara.starter.application.util.StringHelper.titleCase;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ERDiagramParser {
+
+ private final static Pattern RELATIONSHIP_PATTERN = Pattern.compile("^(\\w+)\\s*(\\|\\|--o\\{)\\s*(\\w+)\\s*:\\s*(.+?)(?:\\s*%%\\{(.+?)\\}%%)?");
+ private final static Pattern ENTITY_PATTERN = Pattern.compile("^(\\w+)\\s*\\{\\s*(?:%%\\{(.+?)\\}%%)?");
+ private final static Pattern ATTRIBUTE_PATTERN = Pattern.compile("^\\s*(\\w+)\\s+(\\w+)(?:\\s+(PK|FK))?(?:\\s*%%\\{(.+?)\\}%%)?");
+
+ public ERModel parse(String mermaidCode) {
+ ERModel erModel = new ERModel();
+ String[] lines = mermaidCode.split("\n");
+
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i].trim();
+ if (line.startsWith("erDiagram")) {
+ // .. skip
+ } else if (RELATIONSHIP_PATTERN.matcher(line).find()) {
+ Matcher matcher = RELATIONSHIP_PATTERN.matcher(line);
+ if (matcher.find()) {
+ String firstEntity = matcher.group(1);
+ String relationshipType = matcher.group(2);
+ String secondEntity = matcher.group(3);
+ String relationshipDetail = matcher.group(4).trim();
+ String metadata = matcher.group(5);
+ erModel.addRelationship(new Relationship(titleCase(firstEntity), titleCase(secondEntity), relationshipType, relationshipDetail, parseKeyValuePairs(metadata)));
+ }
+ } else if (ENTITY_PATTERN.matcher(line).find()) {
+ Matcher matcher = ENTITY_PATTERN.matcher(line);
+ if (matcher.find()) {
+ String entityName = matcher.group(1);
+ String entityMetadata = matcher.group(2);
+ Entity entity = new Entity(titleCase(entityName), parseKeyValuePairs(entityMetadata));
+ while (i < lines.length - 1) {
+ line = lines[++i].trim();
+ if (line.equals("}")) {
+ break;
+ }
+ Matcher attrMatcher = ATTRIBUTE_PATTERN.matcher(line);
+ if (attrMatcher.find()) {
+ String type = attrMatcher.group(1);
+ String name = attrMatcher.group(2);
+ String keyType = attrMatcher.group(3);
+ boolean isPrimaryKey = "PK".equals(keyType);
+ boolean isForeignKey = "FK".equals(keyType);
+ String attrMetadata = attrMatcher.group(4);
+ if (!isForeignKey) {
+ type = getWrapperType(mapToJavaType(type));
+ Attribute attribute = new Attribute(name, type, isPrimaryKey, parseKeyValuePairs(attrMetadata));
+ if(type.equals("LocalDate")) {
+ attribute.addImport(LOCAL_DATE);
+ } else if(type.equals("LocalDateTime")) {
+ attribute.addImport(LOCAL_DATE_TIME);
+ }
+ entity.addAttribute(attribute);
+ }
+ }
+ }
+ erModel.addEntity(entity);
+ }
+ } else if (line.startsWith("%%{") && line.endsWith("}%%")) {
+ String globalMetadata = line.substring(3, line.length() - 3).trim();
+ erModel.setProperty(parseKeyValuePairs(globalMetadata));
+ }
+ }
+ return erModel;
+ }
+ private String mapToJavaType(String type) {
+ switch (type) {
+ case "string":
+ return "String";
+ case "int":
+ return "int";
+ case "float":
+ return "float";
+ case "date":
+ return "LocalDate";
+ case "datetime":
+ return "LocalDateTime";
+ default:
+ return "String";
+ }
+ }
+ private List parseKeyValuePairs(String metadata) {
+ List keyValues = new ArrayList<>();
+ if(metadata != null) {
+ Pattern keyValuePattern = Pattern.compile("([\\w-]+)\\[(.+?)\\]");
+ Matcher matcher = keyValuePattern.matcher(metadata.trim());
+ while (matcher.find()) {
+ String key = matcher.group(1);
+ String value = matcher.group(2);
+ keyValues.add(new KeyValue(key, value));
+ }
+ }
+ return keyValues;
+ }
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/AttributeType.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/AttributeType.java
new file mode 100644
index 0000000..bba3200
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/AttributeType.java
@@ -0,0 +1,288 @@
+/**
+ * Copyright 2013-2022 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.util;
+
+import static fish.payara.starter.application.util.AttributeType.Type.ARRAY;
+import static fish.payara.starter.application.util.AttributeType.Type.OTHER;
+import static fish.payara.starter.application.util.AttributeType.Type.PRIMITIVE;
+import static fish.payara.starter.application.util.AttributeType.Type.PRIMITIVE_ARRAY;
+import static fish.payara.starter.application.util.AttributeType.Type.WRAPPER;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import static java.util.stream.Collectors.toMap;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class AttributeType {
+
+ public static final String BYTE = "byte";
+ public static final String SHORT = "short";
+ public static final String INT = "int";
+ public static final String LONG = "long";
+ public static final String FLOAT = "float";
+ public static final String DOUBLE = "double";
+ public static final String BOOLEAN = "boolean";
+ public static final String CHAR = "char";
+
+ public static final String BYTE_WRAPPER = "Byte";
+ public static final String SHORT_WRAPPER = "Short";
+ public static final String INT_WRAPPER = "Integer";
+ public static final String LONG_WRAPPER = "Long";
+ public static final String FLOAT_WRAPPER = "Float";
+ public static final String DOUBLE_WRAPPER = "Double";
+ public static final String BOOLEAN_WRAPPER = "Boolean";
+ public static final String CHAR_WRAPPER = "Character";
+
+ public static final String BOOLEAN_WRAPPER_FQN = "java.lang.Boolean";
+
+ public static final String BIGDECIMAL = "java.math.BigDecimal";
+ public static final String BIGINTEGER = "java.math.BigInteger";
+ public static final String STRING = "String";
+ public static final String STRING_FQN = "java.lang.String";
+ public static final String CALENDAR = "java.util.Calendar";
+ public static final String DATE = "java.util.Date";
+ public static final String GREGORIAN_CALENDAR = "java.util.GregorianCalendar";
+ public static final String TIME_ZONE = "java.util.TimeZone";
+ public static final String SIMPLE_TIME_ZONE = "java.util.SimpleTimeZone";
+ public static final String SQL_DATE = "java.sql.Date";
+ public static final String SQL_TIME = "java.sql.Time";
+ public static final String SQL_TIMESTAMP = "java.sql.Timestamp";
+ public static final String BYTE_ARRAY = "byte[]";
+ public static final String BYTE_WRAPPER_ARRAY = "Byte[]";
+ public static final String CHAR_ARRAY = "char[]";
+ public static final String CHAR_WRAPPER_ARRAY = "Character[]";
+ public static final String UUID = "java.util.UUID";
+ public static final String URL = "java.net.URL";
+ public static final String URI = "java.net.URI";
+ public static final String BYTE_BUFFER = "java.nio.ByteBuffer";
+
+ public static final String INSTANT = "java.time.Instant";
+ public static final String DURATION = "java.time.Duration";
+ public static final String PERIOD = "java.time.Period";
+ public static final String LOCAL_DATE = "java.time.LocalDate";
+ public static final String LOCAL_DATE_TIME = "java.time.LocalDateTime";
+ public static final String LOCAL_TIME = "java.time.LocalTime";
+ public static final String MONTH_DAY = "java.time.MonthDay";
+ public static final String OFFSET_DATE_TIME = "java.time.OffsetDateTime";
+ public static final String OFFSET_TIME = "java.time.OffsetTime";
+ public static final String YEAR = "java.time.Year";
+ public static final String YEAR_MONTH = "java.time.YearMonth";
+ public static final String ZONED_DATE_TIME = "java.time.ZonedDateTime";
+ public static final String ZONE_ID = "java.time.ZoneId";
+ public static final String ZONE_OFFSET = "java.time.ZoneOffset";
+ public static final String HIJRAH_DATE = "java.time.chrono.HijrahDate";
+ public static final String JAPANESE_DATE = "java.time.chrono.JapaneseDate";
+ public static final String MINGUO_DATE = "java.time.chrono.MinguoDate";
+ public static final String THAI_BUDDHIST_DATE = "java.time.chrono.ThaiBuddhistDate";
+
+ private static final Map WRAPPER_DATA_TYPES = new HashMap<>();
+
+ static {
+ WRAPPER_DATA_TYPES.put(BYTE_WRAPPER, BYTE);
+ WRAPPER_DATA_TYPES.put(SHORT_WRAPPER, SHORT);
+ WRAPPER_DATA_TYPES.put(INT_WRAPPER, INT);
+ WRAPPER_DATA_TYPES.put(LONG_WRAPPER, LONG);
+ WRAPPER_DATA_TYPES.put(FLOAT_WRAPPER, FLOAT);
+ WRAPPER_DATA_TYPES.put(DOUBLE_WRAPPER, DOUBLE);
+ WRAPPER_DATA_TYPES.put(BOOLEAN_WRAPPER, BOOLEAN);
+ WRAPPER_DATA_TYPES.put(CHAR_WRAPPER, CHAR);
+ }
+
+ public static String getPrimitiveType(String wrapperType) {
+ boolean array = false;
+ if (isArray(wrapperType)) {
+ array = true;
+ wrapperType = wrapperType.substring(0, wrapperType.length() - 2);
+ }
+ String primitiveType = WRAPPER_DATA_TYPES.get(wrapperType);
+ if (primitiveType != null) {
+ return primitiveType + (array ? "[]" : "");
+ } else {
+ return wrapperType + (array ? "[]" : "");
+ }
+ }
+
+ private static final Map PRIMITIVE_DATA_TYPES
+ = WRAPPER_DATA_TYPES.entrySet()
+ .stream()
+ .collect(toMap(Entry::getValue, Entry::getKey));
+
+ public static String getWrapperType(String primitiveType) {
+ boolean array = false;
+ if (isArray(primitiveType)) {
+ array = true;
+ primitiveType = primitiveType.substring(0, primitiveType.length() - 2);
+ }
+ String wrapperType = PRIMITIVE_DATA_TYPES.get(primitiveType);
+ if (wrapperType != null) {
+ return wrapperType + (array ? "[]" : "");
+ } else {
+ return primitiveType + (array ? "[]" : "");
+ }
+ }
+
+ public static enum Type {
+ PRIMITIVE, WRAPPER, ARRAY, PRIMITIVE_ARRAY, STRING, OTHER;
+ }
+
+ public static boolean isJavaType(String type) {
+ if (isArray(type)) {
+ type = getArrayType(type);
+ }
+ return STRING.equals(type)
+ || isPrimitive(type)
+ || isWrapper(type)
+ || type.startsWith("java.lang")
+ || type.startsWith("java.math")
+ || type.startsWith("java.net")
+ || type.startsWith("java.nio")
+ || type.startsWith("java.util")
+ || type.startsWith("java.sql")
+ || type.startsWith("java.time");
+ }
+
+ public static boolean isGenericType(String type) {
+ return type.length() == 1
+ && Character.isUpperCase(type.charAt(0));
+ }
+
+ public static Type getType(String type) {
+ if (isWrapper(type)) {
+ return WRAPPER;
+ } else if (isPrimitive(type)) {
+ return PRIMITIVE;
+ } else if (isArray(type)) {
+ if (isPrimitiveArray(type)) {
+ return PRIMITIVE_ARRAY;
+ } else {
+ return ARRAY;
+ }
+ } else if (String.class.getSimpleName().equals(type) || String.class.getCanonicalName().equals(type)) {
+ return Type.STRING;
+ } else {
+ return OTHER;
+ }
+
+ }
+
+ public static boolean isBoolean(String type) {
+ return BOOLEAN.equals(type) || BOOLEAN_WRAPPER.equals(type) || BOOLEAN_WRAPPER_FQN.equals(type);
+ }
+
+ public static boolean isText(String type) {
+ return STRING.equals(type)
+ || STRING_FQN.equals(type)
+ || CHAR_WRAPPER.equals(type)
+ || CHAR.equals(type)
+ || URL.equals(type)
+ || URI.equals(type);
+ }
+
+ public static boolean isNumber(String type) {
+ return BYTE.equals(type)
+ || BYTE_WRAPPER.equals(type)
+ || SHORT.equals(type)
+ || SHORT_WRAPPER.equals(type)
+ || INT.equals(type)
+ || INT_WRAPPER.equals(type)
+ || LONG.equals(type)
+ || LONG_WRAPPER.equals(type)
+ || FLOAT.equals(type)
+ || FLOAT_WRAPPER.equals(type)
+ || DOUBLE.equals(type)
+ || DOUBLE_WRAPPER.equals(type)
+ || BIGDECIMAL.equals(type)
+ || BIGINTEGER.equals(type);
+ }
+
+ public static boolean isDate(String type) {
+ return CALENDAR.equals(type)
+ || DATE.equals(type)
+ || GREGORIAN_CALENDAR.equals(type)
+ || TIME_ZONE.equals(type)
+ || SIMPLE_TIME_ZONE.equals(type)
+ || SQL_DATE.equals(type)
+ || PERIOD.equals(type)
+ || LOCAL_DATE.equals(type)
+ || LOCAL_DATE_TIME.equals(type)
+ || ZONE_ID.equals(type)
+ || ZONE_OFFSET.equals(type);
+ }
+
+ public static boolean isDateTime(String type) {
+ return LOCAL_DATE_TIME.equals(type)
+ || OFFSET_DATE_TIME.equals(type)
+ || ZONED_DATE_TIME.equals(type)
+ || SQL_TIMESTAMP.equals(type);
+ }
+
+ public static boolean isTime(String type) {
+ return SQL_TIME.equals(type)
+ || INSTANT.equals(type)
+ || DURATION.equals(type)
+ || LOCAL_TIME.equals(type)
+ || OFFSET_TIME.equals(type);
+ }
+
+ public static boolean isPrimitive(String type) {
+ return PRIMITIVE_DATA_TYPES.containsKey(type);
+ }
+
+ public static boolean isWrapper(String type) {
+ return WRAPPER_DATA_TYPES.containsKey(type);
+ }
+
+ public static boolean isPrimitiveArray(String type) {
+ int length = type.length();
+ if (isArray(type)) {
+ String premitiveType = type.substring(0, length - 2);
+ return isPrimitive(premitiveType);
+ } else {
+ return false;
+ }
+ }
+
+ public static boolean isWrapperArray(String type) {
+ return WRAPPER_DATA_TYPES.containsKey(getArrayType(type)) && isArray(type);
+ }
+
+ public static String getArrayType(String type) {
+ return type.substring(0, type.length() - 2).trim();
+ }
+
+ public static boolean isArray(String type) {
+ if (StringHelper.isEmpty(type) || type.length() < 3) {
+ return false;
+ }
+ return type.charAt(type.length() - 2) == '[' && type.charAt(type.length() - 1) == ']';
+ }
+
+ public static boolean isPrecision(String type) {
+ return isDouble(type) || isFloat(type);
+ }
+
+ public static boolean isDouble(String type) {
+ return DOUBLE_WRAPPER.equals(type) || DOUBLE.equals(type);
+ }
+
+ public static boolean isFloat(String type) {
+ return FLOAT_WRAPPER.equals(type) || FLOAT.equals(type);
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/Constants.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/Constants.java
new file mode 100644
index 0000000..e30ca8c
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/Constants.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright 2013-2022 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.util;
+
+import javax.lang.model.element.Modifier;
+
+public class Constants {
+
+ public static final String WEB_INF = "WEB-INF";
+ public static final String META_INF = "META-INF";
+ public static final String LOGGER = "java.util.logging.Logger";
+ public static final String POST_CONSTRUCT = "jakarta.annotation.PostConstruct";
+
+ public static final String NAMED = "jakarta.inject.Named";
+ public static final String RESOURCE_SUFFIX = "Resource";
+
+ public static final String XML_TRANSIENT_ANNOTATION = "XmlTransient";
+ public static final String XML_ROOT_ELEMENT_ANNOTATION = "XmlRootElement";
+ public static final String XML_ELEMENT_ANNOTATION = "XmlElement";
+ public static final String XML_ATTRIBUTE_ANNOTATION = "XmlAttribute";
+ public static final String URI_TYPE = "java.net.URI";
+ public static final String XML_ANNOTATION_PACKAGE = "jakarta.xml.bind.annotation.";
+ public static final String XML_ROOT_ELEMENT = XML_ANNOTATION_PACKAGE + XML_ROOT_ELEMENT_ANNOTATION;
+ public static final String XML_ELEMENT = XML_ANNOTATION_PACKAGE + XML_ELEMENT_ANNOTATION;
+ public static final String XML_ATTRIBUTE = XML_ANNOTATION_PACKAGE + XML_ATTRIBUTE_ANNOTATION;
+ public static final String XML_TRANSIENT = XML_ANNOTATION_PACKAGE + XML_TRANSIENT_ANNOTATION;
+
+ public static final String VOID = "void";
+
+ public static final String COLLECTION = "Collection";
+ public static final String COLLECTION_TYPE = "java.util.Collection";
+ public static final String COLLECTIONS_TYPE = "java.util.Collections";
+ public static final String LIST_TYPE = "java.util.List";
+ public static final String SET_TYPE = "java.util.Set";
+ public static final String ARRAY_LIST_TYPE = "java.util.ArrayList";
+ public static final String HASH_SET_TYPE = "java.util.HashSet";
+
+ public static final String REQUEST_SCOPE = "jakarta.enterprise.context.RequestScoped";
+
+ public static final Modifier[] PUBLIC = new Modifier[]{Modifier.PUBLIC};
+ public static final Modifier[] PRIVATE = new Modifier[]{Modifier.PRIVATE};
+ public static final Modifier[] PUBLIC_STATIC_FINAL = new Modifier[]{Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL};
+
+ public static final String LANG_PACKAGE = "java.lang";
+ public static final String JAVA_EXT = "java";
+ public static final String JAVA_EXT_SUFFIX = ".java";
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/Inflector.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/Inflector.java
new file mode 100644
index 0000000..34889b7
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/Inflector.java
@@ -0,0 +1,857 @@
+/*
+ * 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 fish.payara.starter.application.util;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ *
+ * API for performing inflections (pluralization, singularization, and so on) on
+ * various strings. These inflections will be useful in code generators that
+ * convert things like database table names into Java class names.
+ *
+ *
+ * The getInstance() method returns a singleton instance of this
+ * class with a default set of rules, which can then be customized. Rules added
+ * during customization will take precedence over the standard ones. Use the
+ * addIrregular(), addPlural(),
+ * addSingular(), and addUncountable() methods to add
+ * additional rules ot the default ones.
+ *
+ *
+ * IMPLEMENTATION NOTE - The default implementation is intended
+ * to be functionally compatible with the Inflector::inflections
+ * class in Ruby on Rails. The gsub() method on Ruby strings
+ * matches regular expressions anywhere in the input. However, nearly all of the
+ * actual patterns used in this module use $ at the end to match
+ * the end of the input string (so that only the last word in a multiple word
+ * phrase will be singularized or pluralized). Therefore, the Java versions of
+ * the regular expressions have been modified to capture all text before the
+ * interesting characters at the end, and emit them as part of the result, so
+ * that the entire string can be matched against a pattern once.
+ * Return a fully configured {@link Inflector} instance that can be used for
+ * performing transformations.
+ */
+ public static Inflector getInstance() {
+
+ if (instance == null) {
+ instance = new Inflector();
+ }
+ return instance;
+
+ }
+
+ // ---------------------------------------------------------- Public Methods
+ /**
+ *
+ * Convert strings to EmbeddedCamelCase. Embedded underscores
+ * will be removed.
+ *
+ * @param word Word to be converted
+ */
+ public String camelize(String word) {
+
+ return camelize(word, false);
+
+ }
+
+ /**
+ *
+ * Convert word strings consisting of lower case letters and underscore
+ * characters between words into embeddedCamelCase or
+ * EmbeddedCamelCase, depending on the lower flag.
+ * Embedded underscores will be removed. Embedded '/' characters will be
+ * replaced by '.', making this method useful in converting path-like names
+ * into fully qualified classnames.
+ *
+ *
+ * IMPLEMENTATION DIFFERENCE - The Rails version of this
+ * method also converts '/' characters to '::' because that reflects the
+ * normal syntax for fully qualified names in Ruby.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"foo_bar", false
+ *
"FooBar"
+ *
+ *
+ *
"foo_bar", true
+ *
"fooBar"
+ *
+ *
+ *
"foo_bar/baz", false
+ *
"FooBar.Baz"
+ *
+ *
+ *
"foo_bar/baz", true
+ *
"fooBar.Baz"
+ *
+ *
+ *
+ * @param word Word to be converted
+ * @param flag Flag indicating that the initial character should be lower
+ * cased instead of upper cased
+ */
+ public String camelize(String word, boolean flag) {
+ if (word.length() == 0) {
+ return word;
+ }
+
+ StringBuffer sb = new StringBuffer(word.length());
+ if (flag) {
+ sb.append(Character.toLowerCase(word.charAt(0)));
+ } else {
+ sb.append(Character.toUpperCase(word.charAt(0)));
+ }
+ boolean capitalize = false;
+ for (int i = 1; i < word.length(); i++) {
+ char ch = word.charAt(i);
+ if (capitalize) {
+ sb.append(Character.toUpperCase(ch));
+ capitalize = false;
+ } else if (ch == '_') {
+ capitalize = true;
+ } else if (ch == '/') {
+ capitalize = true;
+ sb.append('.');
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+
+ }
+
+ /**
+ *
+ * Create and return a simple class name that corresponds to a addPlural
+ * table name. Any leading schema name will be trimmed.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"foo_bars"
+ *
"FooBar"
+ *
+ *
+ *
"baz"
+ *
"Baz"
+ *
+ *
+ *
+ * @param tableName Table name to be converted
+ */
+ public String classify(String tableName) {
+
+ int period = tableName.lastIndexOf('.');
+ if (period >= 0) {
+ tableName = tableName.substring(period + 1);
+ }
+ return camelize(singularize(tableName));
+
+ }
+
+ /**
+ *
+ * Replace underscores in the specified word with dashes.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"foo_bar"
+ *
"foo-bar"
+ *
+ *
+ *
"baz"
+ *
"baz"
+ *
+ *
+ *
+ * @param word Word to be converted
+ */
+ public String dasherize(String word) {
+
+ return word.replace('_', '-');
+
+ }
+
+ /**
+ *
+ * Remove any package name from a fully qualified class name, returning only
+ * the simple classname.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"java.util.Map"
+ *
"Map"
+ *
+ *
+ *
"String"
+ *
"String"
+ *
+ *
+ *
+ * @param className Fully qualified class name to be converted
+ */
+ public String demodulize(String className) {
+
+ int period = className.lastIndexOf('.');
+ if (period >= 0) {
+ return className.substring(period + 1);
+ } else {
+ return className;
+ }
+
+ }
+
+ /**
+ *
+ * Create and return a foreign key name from a class name, separating the
+ * "id" suffix with an underscore.
+ * Create and return a foreign key name from a class name, optionally
+ * inserting an underscore before the "id" portion.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"com.mymodel.Order", false
+ *
"orderid"
+ *
+ *
+ *
"com.mymodel.Order", true
+ *
"order_id"
+ *
+ *
+ *
"Message", false
+ *
"messageid"
+ *
+ *
+ *
"Message", true
+ *
"message_id"
+ *
+ *
+ *
+ * @param className Class name for which to create a foreign key
+ * @param underscore Flag indicating whether an underscore should be emitted
+ * between the class name and the "id" suffix
+ */
+ public String foreignKey(String className, boolean underscore) {
+
+ return underscore(demodulize(className) + (underscore ? "_id" : "id"));
+
+ }
+
+ /**
+ *
+ * Capitalize the first word in a lower cased and underscored string, turn
+ * underscores into spaces, and string any trailing "_id". Like
+ * titleize(), this is meant for creating pretty output, and is
+ * not intended for code generation.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"employee_salary"
+ *
"Employee salary"
+ *
+ *
+ *
"author_id"
+ *
"Author"
+ *
+ *
+ *
+ * @param words Word string to be converted
+ */
+ public String humanize(String words) {
+
+ if (words.endsWith("_id")) {
+ words = words.substring(0, words.length() - 3);
+ }
+ StringBuffer sb = new StringBuffer(words.length());
+ sb.append(Character.toUpperCase(words.charAt(0)));
+ for (int i = 1; i < words.length(); i++) {
+ char ch = words.charAt(i);
+ if (ch == '_') {
+ sb.append(' ');
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+
+ }
+
+ /**
+ *
+ * Turn a number into a corresponding ordinal string used to denote the
+ * position in an ordered sequence.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
1
+ *
"1st"
+ *
+ *
+ *
2
+ *
"2nd"
+ *
+ *
+ *
3
+ *
"3rd"
+ *
+ *
+ *
4
+ *
"rth"
+ *
+ *
+ *
1002
+ *
"1002nd"
+ *
+ *
+ *
2012
+ *
"2012th"
+ *
+ *
+ *
+ * @param number Number to be converted
+ */
+ public String ordinalize(int number) {
+
+ int modulo = number % 100;
+ if ((modulo >= 11) && (modulo <= 13)) {
+ return "" + number + "th";
+ }
+ switch (number % 10) {
+ case 1:
+ return "" + number + "st";
+ case 2:
+ return "" + number + "nd";
+ case 3:
+ return "" + number + "rd";
+ default:
+ return "" + number + "th";
+ }
+
+ }
+
+ /**
+ *
+ * Return a addPlural version of the specified (addSingular) word.
+ *
+ *
+ * @param word Singular word to be converted
+ */
+ public String pluralize(String word) {
+
+ // Scan uncountables and leave alone
+ for (int i = 0; i < uncountables.size(); i++) {
+ if (uncountables.get(i).equals(word)) {
+ return word;
+ }
+ }
+
+ // Scan our patterns for a match and return the correct replacement
+ for (int i = 0; i < plurals.size(); i++) {
+ Replacer replacer = (Replacer) plurals.get(i);
+ if (replacer.matches(word)) {
+ return replacer.replacement();
+ }
+ }
+
+ // Return the original string unchanged
+ return word;
+
+ }
+
+ /**
+ *
+ * Return a addSingular version of the specified (addPlural) word.
+ *
+ *
+ * @param word Plural word to be converted
+ */
+ public String singularize(String word) {
+
+ // Scan uncountables and leave alone
+ for (int i = 0; i < uncountables.size(); i++) {
+ if (uncountables.get(i).equals(word)) {
+ return word;
+ }
+ }
+
+ // Scan our patterns for a match and return the correct replacement
+ for (int i = 0; i < singulars.size(); i++) {
+ Replacer replacer = (Replacer) singulars.get(i);
+ if (replacer.matches(word)) {
+ return replacer.replacement();
+ }
+ }
+
+ // Return the original string unchanged
+ return word;
+
+ }
+
+ /**
+ *
+ * Convert the simple name of a model class into the corresponding name of a
+ * database table, by uncamelizing, inserting underscores, and pluralizing
+ * the last word.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"RawScaledScorer"
+ *
"raw_scaled_scorers"
+ *
+ *
+ *
"fancyCategory"
+ *
"fancy_categories"
+ *
+ *
+ *
+ * @param className Class name to be converted
+ */
+ public String tableize(String className) {
+
+ return pluralize(underscore(className));
+
+ }
+
+ /**
+ *
+ * Capitalize all the words, and replace some characters in the string to
+ * create a nicer looking title. This is meant for creating pretty output,
+ * and is not intended for code generation.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"the honeymooners"
+ *
"The Honeymooners"
+ *
+ *
+ *
"x-men: the last stand"
+ *
"X Men: The Last Stand"
+ *
+ *
+ *
+ * @param words Word string to be converted
+ */
+ public String titleize(String words) {
+
+ StringBuffer sb = new StringBuffer(words.length());
+ boolean capitalize = true; // To get the first character right
+ for (int i = 0; i < words.length(); i++) {
+ char ch = words.charAt(i);
+ if (Character.isWhitespace(ch)) {
+ sb.append(' ');
+ capitalize = true;
+ } else if (ch == '-') {
+ sb.append(' ');
+ capitalize = true;
+ } else if (capitalize) {
+ sb.append(Character.toUpperCase(ch));
+ capitalize = false;
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+
+ }
+
+ /**
+ *
+ * The reverse of camelize(), makes an underscored form from
+ * the expression in the string. Changes "." to "/" to convert fully
+ * qualified class names into paths.
+ *
+ *
+ *
+ *
Input
+ *
Output
+ *
+ *
+ *
"FooBar"
+ *
"foo_bar"
+ *
+ *
+ *
"fooBar"
+ *
"foo_bar"
+ *
+ *
+ *
"FooBar.Baz"
+ *
"foo_bar/baz"
+ *
+ *
+ *
"FooBar.Baz"
+ *
"foo_bar/baz"
+ *
+ *
+ *
+ * @param word Camel cased word to be converted
+ */
+ public String underscore(String word) {
+
+ StringBuffer sb = new StringBuffer(word.length() + 5);
+ boolean uncapitalize = false;
+ for (int i = 0; i < word.length(); i++) {
+ char ch = word.charAt(i);
+ if (uncapitalize) {
+ sb.append(Character.toLowerCase(ch));
+ uncapitalize = false;
+ } else if (ch == '.') {
+ sb.append('/');
+ uncapitalize = true;
+ } else if (Character.isUpperCase(ch)) {
+ if (i > 0) {
+ sb.append('_');
+ }
+ sb.append(Character.toLowerCase(ch));
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+
+ }
+
+ // --------------------------------------------------- Customization Methods
+ /**
+ *
+ * Add the addSingular and addPlural forms of words that cannot be converted
+ * using the normal rules.
+ *
+ *
+ * @param singular Singular form of the word
+ * @param plural Plural form of the word
+ */
+ public void addIrregular(String singular, String plural) {
+
+ addPlural("(.*)(" + singular.substring(0, 1) + ")" + singular.substring(1) + "$",
+ "\\1\\2" + plural.substring(1));
+ addSingular("(.*)(" + plural.substring(0, 1) + ")" + plural.substring(1) + "$",
+ "\\1\\2" + singular.substring(1));
+
+ }
+
+ /**
+ *
+ * Add a match pattern and replacement rule for converting addPlural forms
+ * to addSingular forms. By default, matches will be case insensitive.
+ * Add a match pattern and replacement rule for converting addSingular forms
+ * to addPlural forms.
+ *
+ *
+ * @param match Match pattern regular expression
+ * @param rule Replacement rule
+ * @param insensitive Flag indicating this match should be case insensitive
+ */
+ public void addSingular(String match, String rule, boolean insensitive) {
+
+ singulars.add(0, new Replacer(match, rule, insensitive));
+
+ }
+
+ /**
+ *
+ * Add a word that cannot be converted between addSingular and
+ * addPlural.
+ *
+ *
+ * @param word Word to be added
+ */
+ public void addUncountable(String word) {
+
+ uncountables.add(0, word.toLowerCase());
+
+ }
+
+ // --------------------------------------------------------- Private Classes
+ /**
+ *
+ * Internal class that uses a regular expression matcher to both match the
+ * specified regular expression to a specified word, and (if successful)
+ * perform the appropriate substitutions.
+ * Return true if our regular expression pattern matches
+ * the specified input. If it does, save necessary state information so
+ * that the replacement() method will return appropriate
+ * results based on the rule specified to our
+ * constructor.
+ * Return a replacement string based on the rule that was
+ * specified to our constructor. This method MUST
+ * only be called when the matches() method has returned
+ * true.
+ */
+ public String replacement() {
+
+ StringBuffer sb = new StringBuffer();
+ boolean group = false;
+ for (int i = 0; i < rule.length(); i++) {
+ char ch = rule.charAt(i);
+ if (group) {
+ sb.append(matcher.group(Character.digit(ch, 10)));
+ group = false;
+ } else if (ch == '\\') {
+ group = true;
+ } else {
+ sb.append(ch);
+ }
+ }
+ return sb.toString();
+
+ }
+
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/JPAUtil.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/JPAUtil.java
new file mode 100644
index 0000000..7f87729
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/JPAUtil.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.util;
+
+import java.util.Set;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class JPAUtil {
+
+ public static final Set SQL_RESERVED_KEYWORDS = Set.of(
+ "ADD", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "BACKUP", "BETWEEN", "BY", "CASE", "CHECK",
+ "COLUMN", "CONSTRAINT", "CREATE", "DATABASE", "DEFAULT", "DELETE", "DESC", "DISTINCT", "DROP",
+ "EXEC", "EXISTS", "FOREIGN", "FROM", "FULL", "GROUP", "HAVING", "IN", "INDEX", "INNER", "INSERT",
+ "INTO", "IS", "JOIN", "LEFT", "LIKE", "LIMIT", "NOT", "NULL", "ON", "OR", "ORDER", "OUTER",
+ "PRIMARY", "PROCEDURE", "RIGHT", "ROWNUM", "SELECT", "SET", "TABLE", "TOP", "TRUNCATE", "UNION",
+ "UNIQUE", "UPDATE", "VALUES", "VIEW", "WHERE"
+ );
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/JavaUtil.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/JavaUtil.java
new file mode 100644
index 0000000..338161b
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/JavaUtil.java
@@ -0,0 +1,239 @@
+/**
+ * Copyright 2013-2022 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.util;
+
+import static fish.payara.starter.application.util.StringHelper.EMPTY;
+import static fish.payara.starter.application.util.StringHelper.isNotBlank;
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class JavaUtil {
+
+ public static boolean isJava9() {
+ return getJavaVersion() >= 9;
+ }
+
+ public static double getJavaVersion() {
+ return Double.parseDouble(ManagementFactory.getRuntimeMXBean().getSpecVersion());
+ }
+
+// public static String getUniqueClassName(String candidateName, FileObject targetFolder) {
+// return org.openide.filesystems.FileUtil.findFreeFileName(targetFolder, candidateName, JAVA_EXT); //NOI18N
+// }
+ public static Class> getPrimitiveType(String typeName) {
+ return Lazy.primitiveTypes.get(typeName);
+ }
+
+ private static class Lazy {
+
+ private static final Map> primitiveTypes = new HashMap>();
+
+ static {
+ primitiveTypes.put("int", Integer.class);
+ primitiveTypes.put("int[]", int[].class);
+ primitiveTypes.put("java.lang.Integer[]", Integer[].class);
+ primitiveTypes.put("boolean", Boolean.class);
+ primitiveTypes.put("boolean[]", boolean[].class);
+ primitiveTypes.put("java.lang.Boolean[]", Boolean[].class);
+ primitiveTypes.put("byte", Byte.class);
+ primitiveTypes.put("byte[]", byte[].class);
+ primitiveTypes.put("java.lang.Byte[]", Byte[].class);
+ primitiveTypes.put("char", Character.class);
+ primitiveTypes.put("char[]", char[].class);
+ primitiveTypes.put("java.lang.Character[]", Character[].class);
+ primitiveTypes.put("double", Double.class);
+ primitiveTypes.put("double[]", double[].class);
+ primitiveTypes.put("java.lang.Double[]", Double[].class);
+ primitiveTypes.put("float", Float.class);
+ primitiveTypes.put("float[]", float[].class);
+ primitiveTypes.put("java.lang.Float[]", Float[].class);
+ primitiveTypes.put("long", Long.class);
+ primitiveTypes.put("long[]", long[].class);
+ primitiveTypes.put("java.lang.Long[]", Long[].class);
+ primitiveTypes.put("short", Short.class);
+ primitiveTypes.put("short[]", short[].class);
+ primitiveTypes.put("java.lang.Short[]", Short[].class);
+ }
+ }
+
+ public static boolean isMap(String _className) {
+ boolean valid = false;
+ try {
+ if (_className != null && !_className.trim().isEmpty()) {
+ if (java.util.Map.class.isAssignableFrom(Class.forName(_className.trim()))) {
+ valid = true;
+ }
+ }
+ } catch (ClassNotFoundException ex) {
+ //skip allow = false;
+ }
+ return valid;
+ }
+
+ public static boolean isGetterMethod(String methodName) {
+ return (methodName.startsWith("get") && methodName.length() > 3)
+ || (methodName.startsWith("is") && methodName.length() > 2);
+ }
+
+ public static boolean isSetterMethod(String methodName) {
+ return (methodName.startsWith("set") && methodName.length() > 3);
+ }
+
+ public static boolean isBeanMethod(String methodName) {
+ return isGetterMethod(methodName) || isSetterMethod(methodName);
+ }
+
+ public static boolean isAddMethod(String methodName) {
+ return methodName.startsWith("add") && methodName.length() > 3;
+ }
+
+ public static boolean isRemoveMethod(String methodName) {
+ return methodName.startsWith("remove") && methodName.length() > 6;
+ }
+
+ public static boolean isHelperMethod(String methodName) {
+ return isAddMethod(methodName) || isRemoveMethod(methodName);
+ }
+
+ public static String getIntrospectionPrefix(boolean booleanTypeAttribute) {
+ if (booleanTypeAttribute) {
+ return "is";
+ } else {
+ return "get";
+ }
+ }
+
+ /**
+ * a derived methodName from variableName Eg nickname -> getNickname /
+ * setNickname
+ */
+ public static String getMethodName(String type, String fieldName) {
+ String methodName;
+ if (fieldName.charAt(0) == '_') {
+ char ch = Character.toUpperCase(fieldName.charAt(1));
+ methodName = Character.toString(ch) + fieldName.substring(2);
+ } else {
+ char ch = Character.toUpperCase(fieldName.charAt(0));
+ methodName = Character.toString(ch) + fieldName.substring(1);
+ }
+ if (type != null) {
+ methodName = type + methodName;
+ }
+ return methodName;
+ }
+
+ public static String getFieldName(String methodName) {
+ String fieldName;
+ if (methodName.startsWith("get") || methodName.startsWith("set")) {
+ fieldName = methodName.substring(3);
+ } else if (methodName.startsWith("is")) {
+ fieldName = methodName.substring(2);
+ } else {
+ return null;
+ }
+ fieldName = StringHelper.firstLower(fieldName);
+ return fieldName;
+ }
+
+ public static String getFieldNameFromDelegatorMethod(String methodName) {
+ String fieldName;
+ if (methodName.startsWith("add") && methodName.length() > 3) {
+ fieldName = methodName.substring(3);
+ } else if (methodName.startsWith("remove") && methodName.length() > 6) {
+ fieldName = methodName.substring(6);
+ } else {
+ return null;
+ }
+ fieldName = StringHelper.firstLower(fieldName);
+ return fieldName;
+ }
+
+ public static String removeBeanMethodPrefix(String methodName) {
+ if (methodName.startsWith("get")) {
+ methodName = methodName.replaceFirst("get", EMPTY);
+ }
+ if (methodName.startsWith("set")) {
+ methodName = methodName.replaceFirst("set", EMPTY);
+ }
+ if (methodName.startsWith("is")) {
+ methodName = methodName.replaceFirst("is", EMPTY);
+ }
+ return methodName;
+ }
+
+ public static String mergePackage(String package1, String package2) {
+ if (isNotBlank(package1) && isNotBlank(package2)) {
+ return package1 + '.' + package2;
+ } else if (isNotBlank(package1)) {
+ return package1;
+ } else if (isNotBlank(package2)) {
+ return package2;
+ }
+ return EMPTY;
+ }
+
+ public static Predicate not(Predicate predicate) {
+ return predicate.negate();
+ }
+
+ public static Map convertToMap(Object object) {
+ Map result = new HashMap<>();
+ try {
+ BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
+ for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
+ Method reader = pd.getReadMethod();
+ if (reader != null) {
+ result.put(pd.getName(), reader.invoke(object));
+ }
+ }
+
+ for (Field field : getAllFields(object.getClass())) {
+ if (!result.containsKey(field.getName())) {
+ field.setAccessible(true);
+ result.put(field.getName(), field.get(object));
+ }
+ }
+ } catch (IntrospectionException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
+ ex.printStackTrace();
+ }
+ return result;
+ }
+
+ private static List getAllFields(Class clazz) {
+ List fields = new ArrayList<>();
+ do {
+ Collections.addAll(fields, clazz.getDeclaredFields());
+ clazz = clazz.getSuperclass();
+ } while (clazz != null);
+ return fields;
+ }
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/StringHelper.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/StringHelper.java
new file mode 100644
index 0000000..94efa7e
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/StringHelper.java
@@ -0,0 +1,490 @@
+/**
+ * Copyright 2013-2022 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package fish.payara.starter.application.util;
+
+import java.util.function.Predicate;
+
+public final class StringHelper {
+
+ //(\\s+) space
+ //(?<=[a-z])(?=[A-Z]) => eclipseRCPExt -> eclipse / RCPExt
+ //(?<=[A-Z])(?=[A-Z][a-z]) => eclipseRCPExt -> eclipse / RCP / Ext
+ public final static String NATURAL_TEXT_SPLITTER = "(\\s+)|(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])";
+
+ public static String firstLower(String string) {
+ if (isBlank(string)) {
+ return EMPTY;
+ }
+ boolean makeFirstLower = string.length() < 2 || (!Character.isUpperCase(string.charAt(1)));
+ return makeFirstLower ? string.substring(0, 1).toLowerCase() + string.substring(1) : string;
+ }
+
+ public static String firstUpper(String string) {
+ if (isBlank(string)) {
+ return EMPTY;
+ }
+ return string.length() > 1 ? string.substring(0, 1).toUpperCase() + string.substring(1) : string.toUpperCase();
+ }
+
+ public static String titleCase(String string) {
+ if (isBlank(string)) {
+ return EMPTY;
+ }
+ return string.length() > 1 ? string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase() : string.toUpperCase();
+ }
+
+ /**
+ * Converts `string` to [start case]
+ *
+ * @param content
+ * @return
+ * @example
+ *
+ * startCase('--foo-bar--') => 'Foo Bar' startCase('fooBar') => 'Foo Bar'
+ * startCase('__FOO_BAR__') => 'FOO BAR'
+ */
+ public static String startCase(String content) {
+ StringBuilder result = new StringBuilder();
+ content = content.replaceFirst("[^a-zA-Z0-9]+", EMPTY);
+ for (String word : content.replaceAll("[^a-zA-Z0-9]", " ").split(NATURAL_TEXT_SPLITTER)) {
+ result.append(firstUpper(word)).append(" ");
+ }
+ result.setLength(result.length() - 1);
+ return result.toString();
+ }
+
+ /**
+ * Converts `string` to [snake case]
+ *
+ * @param content
+ * @return
+ * @example
+ *
+ * Foo Bar > 'foo_bar', fooBar > 'foo_bar', --FOO-BAR-- > 'foo_bar'
+ */
+ public static String snakeCase(String content) {
+ StringBuilder result = new StringBuilder();
+ content = content.replaceFirst("[^a-zA-Z0-9]+", EMPTY);
+ for (String word : content.replaceAll("[^a-zA-Z0-9]", " ").split(NATURAL_TEXT_SPLITTER)) {
+ result.append(word.toLowerCase()).append("_");
+ }
+ result.setLength(result.length() - 1);
+ return result.toString();
+ }
+
+ /**
+ * Converts `string` to [kebab case]
+ *
+ * @param content
+ * @return
+ * @example
+ *
+ * 'Foo Bar > 'foo-bar', 'fooBar' > 'foo-bar', '__FOO_BAR__' > 'foo-bar'
+ */
+ public static String kebabCase(String content) {
+ StringBuilder result = new StringBuilder();
+ content = content.replaceFirst("[^a-zA-Z0-9]+", EMPTY);
+ for (String word : content.replaceAll("[^a-zA-Z0-9]", " ").split(NATURAL_TEXT_SPLITTER)) {
+ result.append(word.toLowerCase()).append("-");
+ }
+ result.setLength(result.length() - 1);
+ return result.toString();
+ }
+
+ /**
+ * Removes leading and trailing whitespace or specified characters from
+ * `string`.
+ *
+ * @param content
+ * @param trimmer
+ * @return
+ * @example
+ *
+ * _.trim(' abc ', ' '); // => 'abc'
+ *
+ * _.trim('_abc_', '_'); // => 'abc'
+ *
+ */
+ public static String trim(String content, char trimmer) {
+ char value[] = content.toCharArray();
+ int len = value.length;
+ int st = 0;
+ char[] val = value;
+ /* avoid getfield opcode */
+
+ while ((st < len) && (val[st] == trimmer)) {
+ st++;
+ }
+ while ((st < len) && (val[len - 1] == trimmer)) {
+ len--;
+ }
+ return ((st > 0) || (len < value.length)) ? content.substring(st, len) : content;
+ }
+
+ /**
+ * Converts `string` to [camel case]
+ *
+ * @param content
+ * @return
+ * @example
+ *
+ * 'Foo Bar > 'fooBar', '--foo-bar--' > 'fooBar', '__FOO_BAR__ > 'fooBar'
+ */
+ public static String camelCase(String content) {
+ StringBuilder result = new StringBuilder();
+// content = content.replaceFirst("[^a-zA-Z0-9]+", EMPTY);//issue job-history => jobhistory
+ int i = 0;
+ for (String word : content.replaceAll("[^a-zA-Z0-9]", " ").split(NATURAL_TEXT_SPLITTER)) {
+ word = word.toLowerCase();
+ if (i == 0) {
+ result.append(word);
+ } else {
+ result.append(firstUpper(word));
+ }
+ i++;
+ }
+ return result.toString();
+ }
+
+ /**
+ *
+ * @param input
+ * @return
+ * @example
+ *
+ * BankAccount => Bank Account Bank_Account => Bank_Account
+ */
+ public static String toNatural(String input) {
+ String natural = EMPTY;
+ Character lastChar = null;
+ for (Character curChar : input.toCharArray()) {
+ if (lastChar == null) {
+ // First character
+ lastChar = Character.toUpperCase(curChar);
+ natural = natural + lastChar;
+
+ } else {
+ if (Character.isLowerCase(lastChar)
+ && (Character.isUpperCase(curChar)) || Character.isDigit(curChar)) {
+ natural = natural + " " + curChar;
+ } else {
+ natural = natural + curChar;
+ }
+ lastChar = curChar;
+ }
+
+ }
+ return natural;
+ }
+
+ /**
+ *
+ * @param input
+ * @return
+ * @example
+ *
+ * BankAccount => BANK_ACCOUNT Bank_Account => BANK_ACCOUNT
+ */
+ public static String toConstant(String input) {
+ String constant = EMPTY;
+ Character lastChar = null;
+ for (Character curChar : input.toCharArray()) {
+ if (lastChar == null) {
+ // First character
+ lastChar = Character.toUpperCase(curChar);
+ constant = constant + lastChar;
+
+ } else {
+ if (Character.isLowerCase(lastChar)
+ && (Character.isUpperCase(curChar) || Character.isDigit(curChar))) {
+ constant = constant + '_' + curChar;
+ } else {
+ constant = constant + Character.toUpperCase(curChar);
+ }
+ lastChar = curChar;
+ }
+
+ }
+ return constant;
+ }
+
+ public static String getNext(String name, Predicate checkExist) {
+ return getNext(name, checkExist, false);
+ }
+
+ public static String getNext(String name, Predicate checkExist, boolean increment) {
+ int index = 0;
+ String nextName;
+ if (increment) {
+ nextName = name + ++index;
+ } else {
+ nextName = name;
+ }
+ boolean isExist = true;
+ while (isExist) {
+ if (checkExist.test(nextName)) {
+ isExist = true;
+ nextName = name + ++index;
+ } else {
+ return nextName;
+ }
+ }
+ return nextName;
+ }
+
+ public static String singularize(String name) {
+ return Inflector.getInstance().singularize(name);
+ }
+
+ public static final String COLLECTION = "Collection";
+
+ public static String pluralize(String name) {
+ String pluralName = Inflector.getInstance().pluralize(name);
+
+ if (name.equals(pluralName)) {
+ return name + COLLECTION;
+ } else {
+ return pluralName;
+ }
+ }
+
+ public static String padRight(String s, int n) {
+ return String.format("%1$-" + n + "s", s);
+ }
+
+ public static String padLeft(String s, int n) {
+ return String.format("%1$" + n + "s", s);
+ }
+
+ public static boolean compareNonWhitespaces(String v1, String v2) {
+ return v1 != null && v2 != null
+ && v1.replaceAll("\\s+", "")
+ .equals(v2.replaceAll("\\s+", ""));
+ }
+
+ public static final String SPACE = " ";
+
+ public static final String EMPTY = "";
+
+ public static final String LF = "\n";
+
+ public static final String CR = "\r";
+
+ private static final int PAD_LIMIT = 8192;
+
+ public static boolean isBlank(final CharSequence cs) {
+ int strLen;
+ if (cs == null || (strLen = cs.length()) == 0) {
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if (!Character.isWhitespace(cs.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isNotBlank(final CharSequence cs) {
+ return !isBlank(cs);
+ }
+
+ public static boolean isEmpty(Object str) {
+ return (str == null || "".equals(str));
+ }
+
+ public static boolean isNotEmpty(final CharSequence cs) {
+ return !isEmpty(cs);
+ }
+
+ public static boolean equals(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1.length() != cs2.length()) {
+ return false;
+ }
+ if (cs1 instanceof String && cs2 instanceof String) {
+ return cs1.equals(cs2);
+ }
+ // Step-wise comparison
+ final int length = cs1.length();
+ for (int i = 0; i < length; i++) {
+ if (cs1.charAt(i) != cs2.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean equalsIgnoreCase(final CharSequence cs1, final CharSequence cs2) {
+ if (cs1 == cs2) {
+ return true;
+ }
+ if (cs1 == null || cs2 == null) {
+ return false;
+ }
+ if (cs1.length() != cs2.length()) {
+ return false;
+ }
+ return regionMatches(cs1, true, 0, cs2, 0, cs1.length());
+ }
+
+ public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) {
+ if (str == null || searchStr == null) {
+ return false;
+ }
+ final int len = searchStr.length();
+ final int max = str.length() - len;
+ for (int i = 0; i <= max; i++) {
+ if (regionMatches(str, true, i, searchStr, 0, len)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static String deleteWhitespace(final String str) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ final int sz = str.length();
+ final char[] chs = new char[sz];
+ int count = 0;
+ for (int i = 0; i < sz; i++) {
+ if (!Character.isWhitespace(str.charAt(i))) {
+ chs[count++] = str.charAt(i);
+ }
+ }
+ if (count == sz) {
+ return str;
+ }
+ return new String(chs, 0, count);
+ }
+
+ public static String trim(final String str) {
+ return str == null ? null : str.trim();
+ }
+
+ public static String leftPad(final String str, final int size) {
+ return leftPad(str, size, ' ');
+ }
+
+ public static String leftPad(final String str, final int size, final char padChar) {
+ if (str == null) {
+ return null;
+ }
+ final int pads = size - str.length();
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (pads > PAD_LIMIT) {
+ return leftPad(str, size, String.valueOf(padChar));
+ }
+ return repeat(padChar, pads).concat(str);
+ }
+
+ public static String leftPad(final String str, final int size, String padStr) {
+ if (str == null) {
+ return null;
+ }
+ if (isEmpty(padStr)) {
+ padStr = SPACE;
+ }
+ final int padLen = padStr.length();
+ final int strLen = str.length();
+ final int pads = size - strLen;
+ if (pads <= 0) {
+ return str; // returns original String when possible
+ }
+ if (padLen == 1 && pads <= PAD_LIMIT) {
+ return leftPad(str, size, padStr.charAt(0));
+ }
+
+ if (pads == padLen) {
+ return padStr.concat(str);
+ } else if (pads < padLen) {
+ return padStr.substring(0, pads).concat(str);
+ } else {
+ final char[] padding = new char[pads];
+ final char[] padChars = padStr.toCharArray();
+ for (int i = 0; i < pads; i++) {
+ padding[i] = padChars[i % padLen];
+ }
+ return new String(padding).concat(str);
+ }
+ }
+
+ public static String repeat(final char ch, final int repeat) {
+ if (repeat <= 0) {
+ return EMPTY;
+ }
+ final char[] buf = new char[repeat];
+ for (int i = repeat - 1; i >= 0; i--) {
+ buf[i] = ch;
+ }
+ return new String(buf);
+ }
+
+ static boolean regionMatches(final CharSequence cs, final boolean ignoreCase, final int thisStart,
+ final CharSequence substring, final int start, final int length) {
+ if (cs instanceof String && substring instanceof String) {
+ return ((String) cs).regionMatches(ignoreCase, thisStart, (String) substring, start, length);
+ }
+ int index1 = thisStart;
+ int index2 = start;
+ int tmpLen = length;
+
+ // Extract these first so we detect NPEs the same as the java.lang.String version
+ final int srcLen = cs.length() - thisStart;
+ final int otherLen = substring.length() - start;
+
+ // Check for invalid parameters
+ if (thisStart < 0 || start < 0 || length < 0) {
+ return false;
+ }
+
+ // Check that the regions are long enough
+ if (srcLen < length || otherLen < length) {
+ return false;
+ }
+
+ while (tmpLen-- > 0) {
+ final char c1 = cs.charAt(index1++);
+ final char c2 = substring.charAt(index2++);
+
+ if (c1 == c2) {
+ continue;
+ }
+
+ if (!ignoreCase) {
+ return false;
+ }
+
+ // The same check as in String.regionMatches():
+ if (Character.toUpperCase(c1) != Character.toUpperCase(c2)
+ && Character.toLowerCase(c1) != Character.toLowerCase(c2)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/StringUtils.java b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/StringUtils.java
new file mode 100644
index 0000000..488647a
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/java/fish/payara/starter/application/util/StringUtils.java
@@ -0,0 +1,29 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package fish.payara.starter.application.util;
+
+/**
+ *
+ * @author Gaurav Gupta
+ */
+public class StringUtils {
+
+ public static String toCamelCase(String input) {
+ StringBuilder result = new StringBuilder();
+ boolean nextUpperCase = true;
+
+ for (char ch : input.toCharArray()) {
+ if (ch == '_' || ch == '-') {
+ nextUpperCase = true;
+ } else if (nextUpperCase) {
+ result.append(Character.toUpperCase(ch));
+ nextUpperCase = false;
+ } else {
+ result.append(Character.toLowerCase(ch));
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/PayaraStarterGenerator/src/main/resources/template/descriptor/beans.xml.ftl b/PayaraStarterGenerator/src/main/resources/template/descriptor/beans.xml.ftl
new file mode 100644
index 0000000..1a307d3
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/descriptor/beans.xml.ftl
@@ -0,0 +1,22 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+
+
+
diff --git a/PayaraStarterGenerator/src/main/resources/template/descriptor/persistence.xml.ftl b/PayaraStarterGenerator/src/main/resources/template/descriptor/persistence.xml.ftl
new file mode 100644
index 0000000..790a8f1
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/descriptor/persistence.xml.ftl
@@ -0,0 +1,28 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+
+
+
+ java:comp/DefaultDataSource
+ false
+
+
+
+
+
diff --git a/PayaraStarterGenerator/src/main/resources/template/html/about-us.html.ftl b/PayaraStarterGenerator/src/main/resources/template/html/about-us.html.ftl
new file mode 100644
index 0000000..9d27bd9
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/html/about-us.html.ftl
@@ -0,0 +1,19 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+
+
About Us
+
${model.getAboutUsDescription()}
+
\ No newline at end of file
diff --git a/PayaraStarterGenerator/src/main/resources/template/html/app.html.ftl b/PayaraStarterGenerator/src/main/resources/template/html/app.html.ftl
new file mode 100644
index 0000000..753e31a
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/html/app.html.ftl
@@ -0,0 +1,117 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+
+
+
+
+
+ Entity Management
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PayaraStarterGenerator/src/main/resources/template/html/entity.html.ftl b/PayaraStarterGenerator/src/main/resources/template/html/entity.html.ftl
new file mode 100644
index 0000000..e50d010
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/html/entity.html.ftl
@@ -0,0 +1,258 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+
+
${entity.getTitle()}
+
${entity.getDescription()}
+
+
+
+
+
+
+ <#list entity.attributes as attribute>
+
${attribute.getStartCaseName()}
+ #list>
+
Actions
+
+
+
+
+
+
+
+
+
+
+
+
+
Add ${entity.getTitle()}
+
+
+
+
+
+
+
+
+
+
diff --git a/PayaraStarterGenerator/src/main/resources/template/html/home.html.ftl b/PayaraStarterGenerator/src/main/resources/template/html/home.html.ftl
new file mode 100644
index 0000000..d3db51e
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/html/home.html.ftl
@@ -0,0 +1,19 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+
+
${model.getLongTitle()}
+
${model.getDescription()}
+
diff --git a/PayaraStarterGenerator/src/main/resources/template/repository/AbstractRepository.java.ftl b/PayaraStarterGenerator/src/main/resources/template/repository/AbstractRepository.java.ftl
new file mode 100644
index 0000000..11780b8
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/repository/AbstractRepository.java.ftl
@@ -0,0 +1,153 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+package ${package};
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.NoResultException;
+import jakarta.persistence.Query;
+import jakarta.persistence.TypedQuery;
+import jakarta.persistence.criteria.CriteriaQuery;
+import jakarta.persistence.criteria.Root;
+<#if cdi>import jakarta.transaction.Transactional;
+import static jakarta.transaction.Transactional.TxType.REQUIRED;
+import static jakarta.transaction.Transactional.TxType.SUPPORTS;#if>
+
+<#if cdi>@Transactional(SUPPORTS)#if>
+public abstract class ${AbstractRepository} {
+
+ private final Class entityClass;
+
+ public ${AbstractRepository}(Class entityClass) {
+ this.entityClass = entityClass;
+ }
+
+ protected abstract EntityManager getEntityManager();
+
+ <#if cdi>@Transactional(REQUIRED)#if>
+ public void create(E entity) {
+ getEntityManager().persist(entity);
+ }
+
+ <#if cdi>@Transactional(REQUIRED)#if>
+ public E edit(E entity) {
+ return getEntityManager().merge(entity);
+ }
+
+ <#if cdi>@Transactional(REQUIRED)#if>
+ public void remove(E entity) {
+ getEntityManager().remove(getEntityManager().merge(entity));
+ }
+
+ public P getIdentifier(E entity) {
+ return (P)getEntityManager().getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(entity);
+ }
+
+ public E find(P id) {
+ return getEntityManager().find(entityClass, id);
+ }
+
+ public List findAll() {
+ CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
+ cq.select(cq.from(entityClass));
+ return getEntityManager().createQuery(cq).getResultList();
+ }
+
+ public List findRange(int startPosition, int size) {
+ return findRange(startPosition, size, null);
+ }
+
+ public List findRange(int startPosition, int size, String entityGraph) {
+ CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
+ cq.select(cq.from(entityClass));
+ Query q = getEntityManager().createQuery(cq);
+ q.setMaxResults(size);
+ q.setFirstResult(startPosition);
+ if (entityGraph != null) {
+ q.setHint("jakarta.persistence.loadgraph", getEntityManager().getEntityGraph(entityGraph));
+ }
+ return q.getResultList();
+ }
+
+ public int count() {
+ CriteriaQuery criteriaQuery = getEntityManager().getCriteriaBuilder().createQuery();
+ Root root = criteriaQuery.from(entityClass);
+ criteriaQuery.select(getEntityManager().getCriteriaBuilder().count(root));
+ Query query = getEntityManager().createQuery(criteriaQuery);
+ return ((Long) query.getSingleResult()).intValue();
+ }
+
+ public Optional findSingleByNamedQuery(String namedQueryName) {
+ return findOrEmpty(() -> getEntityManager().createNamedQuery(namedQueryName, entityClass).getSingleResult());
+ }
+
+ public Optional findSingleByNamedQuery(String namedQueryName, Map parameters) {
+ return findSingleByNamedQuery(namedQueryName, null, parameters);
+ }
+
+ public Optional findSingleByNamedQuery(String namedQueryName, String entityGraph, Map parameters) {
+ Set> rawParameters = parameters.entrySet();
+ TypedQuery query = getEntityManager().createNamedQuery(namedQueryName, entityClass);
+ rawParameters.forEach(entry -> query.setParameter(entry.getKey(), entry.getValue()));
+ if(entityGraph != null){
+ query.setHint("jakarta.persistence.loadgraph", getEntityManager().getEntityGraph(entityGraph));
+ }
+ return findOrEmpty(query::getSingleResult);
+ }
+
+ public List findByNamedQuery(String namedQueryName) {
+ return findByNamedQuery(namedQueryName, -1);
+ }
+
+ public List findByNamedQuery(String namedQueryName, Map parameters) {
+ return findByNamedQuery(namedQueryName, parameters, -1);
+ }
+
+ public List findByNamedQuery(String namedQueryName, int resultLimit) {
+ return findByNamedQuery(namedQueryName, Collections.EMPTY_MAP, resultLimit);
+ }
+
+ public List findByNamedQuery(String namedQueryName, Map parameters, int resultLimit) {
+ Set> rawParameters = parameters.entrySet();
+ Query query = getEntityManager().createNamedQuery(namedQueryName);
+ if (resultLimit > 0) {
+ query.setMaxResults(resultLimit);
+ }
+ rawParameters.forEach(entry -> query.setParameter(entry.getKey(), entry.getValue()));
+ return query.getResultList();
+ }
+
+ public static Optional findOrEmpty(final DaoRetriever retriever) {
+ try {
+ return Optional.of(retriever.retrieve());
+ } catch (NoResultException ex) {
+ //log
+ }
+ return Optional.empty();
+ }
+
+ @FunctionalInterface
+ public interface DaoRetriever {
+
+ E retrieve() throws NoResultException;
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/resources/template/repository/EntityRepository.java.ftl b/PayaraStarterGenerator/src/main/resources/template/repository/EntityRepository.java.ftl
new file mode 100644
index 0000000..30ff19f
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/repository/EntityRepository.java.ftl
@@ -0,0 +1,41 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+package ${package};
+
+<#if cdi>import jakarta.enterprise.context.Dependent;#if><#if !cdi>import jakarta.ejb.Stateless;#if>
+<#if named>import jakarta.inject.Named;#if>
+import jakarta.persistence.EntityManager;
+import jakarta.inject.Inject;
+import ${EntityClass_FQN};
+<#if EntityPKClass_FQN!="">import ${EntityPKClass_FQN};#if>
+
+<#if cdi>@Dependent#if><#if !cdi>@Stateless#if>
+<#if named>@Named("${entityInstance}")#if>
+public class ${EntityRepository} extends ${AbstractRepository}<${EntityClass}, ${EntityPKClass}> {
+
+ @Inject
+ private EntityManager em;
+
+ @Override
+ protected EntityManager getEntityManager() {
+ return em;
+ }
+
+ public ${EntityRepository}() {
+ super(${EntityClass}.class);
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/resources/template/rest/EntityController.java.ftl b/PayaraStarterGenerator/src/main/resources/template/rest/EntityController.java.ftl
new file mode 100644
index 0000000..a98ad02
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/rest/EntityController.java.ftl
@@ -0,0 +1,222 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+package ${package};
+
+import ${EntityClass_FQN};
+import ${EntityRepository_FQN};
+<#list entity.attributes as attribute>
+ <#if attribute.relation??>
+ <#if attribute.multi>
+ <#else>
+import ${EntityRepository_package}.${attribute.getTitleCaseName()}${EntityRepositorySuffix};
+ #if>
+ <#else>
+ #if>
+#list>
+import jakarta.inject.Inject;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.DELETE;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.PathParam;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;<#if pagination != "no">
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Response.ResponseBuilder;
+import ${Page_FQN};
+import ${PaginationUtil_FQN};#if><#if metrics>
+import org.eclipse.microprofile.metrics.annotation.Timed;#if>
+import org.eclipse.microprofile.faulttolerance.Timeout;<#if openAPI>
+import org.eclipse.microprofile.openapi.annotations.Operation;
+import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;#if>
+
+/**
+ * REST controller for managing ${EntityClass}.
+ */
+@Path("/api/${entityApiUrl}")
+public class ${controllerClass} {
+
+ private static final Logger LOG = Logger.getLogger(${controllerClass}.class.getName());
+
+ @Inject
+ private ${EntityRepository} ${entityRepository};
+
+ <#list entity.attributes as attribute>
+ <#if attribute.relation??>
+ <#if attribute.multi>
+ <#else>
+ @Inject
+ private ${attribute.getTitleCaseName()}${EntityRepositorySuffix} ${attribute.name}${EntityRepositorySuffix};
+ #if>
+ <#else>
+ #if>
+ #list>
+
+ private static final String ENTITY_NAME = "${entityTranslationKey}";
+
+ /**
+ * POST : Create a new ${entityInstance}.
+ *
+ * @param ${instanceName} the ${instanceName} to create
+ * @return the Response with status 201 (Created) and with body the
+ * new ${instanceName}, or with status 400 (Bad Request) if the ${entityInstance} has already
+ * an ID
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ <#if metrics>@Timed#if>
+ <#if openAPI>@Operation(summary = "create a new ${entityInstance}", description = "Create a new ${entityInstance}")
+ @APIResponse(responseCode = "201", description = "Created")
+ @APIResponse(responseCode = "400", description = "Bad Request")#if>
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response create${EntityClass}(${instanceType} ${instanceName}) throws URISyntaxException {
+ LOG.log(Level.FINE, "REST request to save ${EntityClass} : {}", ${instanceName});
+ <#list entity.attributes as attribute>
+ <#if attribute.relation??>
+ <#if attribute.multi>
+ <#else>
+ if (${instanceName}.get${attribute.relation.getTitleCaseName()}() != null && ${instanceName}.get${attribute.relation.getTitleCaseName()}().get${attribute.relation.getPrimaryKeyFirstUpperName()}() != null) {
+ ${instanceName}.set${attribute.relation.getTitleCaseName()}(${attribute.name}${EntityRepositorySuffix}.find(${instanceName}.get${attribute.relation.getTitleCaseName()}().get${attribute.relation.getPrimaryKeyFirstUpperName()}()));
+ } else {
+ ${instanceName}.set${attribute.relation.getTitleCaseName()}(null);
+ }
+ #if>
+ <#else>
+ #if>
+ #list>
+ ${entityRepository}.create(${instanceName});
+ return HeaderUtil.createEntityCreationAlert(Response.created(new URI("/${applicationPath}/api/${entityApiUrl}/" + ${instanceName}.${pkGetter}())),
+ ENTITY_NAME, <#if isPKPrimitive>String.valueOf(${instanceName}.${pkGetter}())<#elseif pkType == "String">${instanceName}.${pkGetter}()<#else>${instanceName}.${pkGetter}().toString()#if>)
+ .entity(${instanceName}).build();
+ }
+
+ /**
+ * PUT : Updates an existing ${entityInstance}.
+ *
+ * @param ${instanceName} the ${instanceName} to update
+ * @return the Response with status 200 (OK) and with body the updated ${instanceName},
+ * or with status 400 (Bad Request) if the ${instanceName} is not valid,
+ * or with status 500 (Internal Server Error) if the ${instanceName} couldn't be updated
+ * @throws URISyntaxException if the Location URI syntax is incorrect
+ */
+ <#if metrics>@Timed#if>
+ <#if openAPI>@Operation(summary = "update ${entityInstance}", description = "Updates an existing ${entityInstance}")
+ @APIResponse(responseCode = "200", description = "OK")
+ @APIResponse(responseCode = "400", description = "Bad Request")
+ @APIResponse(responseCode = "500", description = "Internal Server Error")#if>
+ @PUT
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response update${EntityClass}(${instanceType} ${instanceName}) throws URISyntaxException {
+ LOG.log(Level.FINE, "REST request to update ${EntityClass} : {}", ${instanceName});
+ <#list entity.attributes as attribute>
+ <#if attribute.relation??>
+ <#if attribute.multi>
+ <#else>
+ if (${instanceName}.get${attribute.relation.getTitleCaseName()}() != null && ${instanceName}.get${attribute.relation.getTitleCaseName()}().get${attribute.relation.getPrimaryKeyFirstUpperName()}() != null) {
+ ${instanceName}.set${attribute.relation.getTitleCaseName()}(${attribute.name}${EntityRepositorySuffix}.find(${instanceName}.get${attribute.relation.getTitleCaseName()}().get${attribute.relation.getPrimaryKeyFirstUpperName()}()));
+ } else {
+ ${instanceName}.set${attribute.relation.getTitleCaseName()}(null);
+ }
+ #if>
+ <#else>
+ #if>
+ #list>
+ ${entityRepository}.edit(${instanceName});
+ return HeaderUtil.createEntityUpdateAlert(Response.ok(), ENTITY_NAME, <#if isPKPrimitive>String.valueOf(${instanceName}.${pkGetter}())<#else>${instanceName}.${pkGetter}().toString()#if>)
+ .entity(${instanceName}).build();
+ }
+
+ /**
+ * GET : get all the ${entityInstancePlural}.
+ <#if pagination!= "no">* @param page the pagination information
+ * @param size the pagination size information
+ <#elseif fieldsContainNoOwnerOneToOne>* @param filter the filter of the request#if>
+ * @return the Response with status 200 (OK) and the list of ${entityInstancePlural} in body
+ <#if pagination!= "no">* @throws URISyntaxException if there is an error to generate the pagination HTTP headers#if>
+ */
+ <#if metrics>@Timed#if>
+ <#if openAPI>@Operation(summary = "get all the ${entityInstancePlural}")
+ @APIResponse(responseCode = "200", description = "OK")#if>
+ @GET
+ @Produces(MediaType.APPLICATION_JSON)
+ @Timeout
+ <#if pagination == "no">
+ public List<${instanceType}> getAll${EntityClassPlural}() {
+ LOG.log(Level.FINE, "REST request to get all ${EntityClassPlural}");
+ List<${EntityClass}> ${entityInstancePlural} = ${entityRepository}.findAll();
+ return ${entityInstancePlural};
+ }
+ <#else>
+ public Response getAll${EntityClassPlural}(@QueryParam("page") int page, @QueryParam("size") int size) throws URISyntaxException {
+ LOG.log(Level.FINE, "REST request to get all ${EntityClassPlural}");
+ List<${EntityClass}> ${entityInstancePlural} = ${entityRepository}.findRange(page * size, size);
+ ResponseBuilder builder = Response.ok(${entityInstancePlural});
+ PaginationUtil.generatePaginationHttpHeaders(builder, new Page(page, size, ${entityRepository}.count()), "/${applicationPath}/api/${entityApiUrl}");
+ return builder.build();
+ }
+ #if>
+
+ /**
+ * GET /:${pkName} : get the "${pkName}" ${entityInstance}.
+ *
+ * @param ${pkName} the ${pkName} of the ${instanceName} to retrieve
+ * @return the Response with status 200 (OK) and with body the ${instanceName}, or with status 404 (Not Found)
+ */
+ <#if metrics>@Timed#if>
+ <#if openAPI>@Operation(summary = "get the ${entityInstance}")
+ @APIResponse(responseCode = "200", description = "OK")
+ @APIResponse(responseCode = "404", description = "Not Found")#if>
+ @GET
+ @Path("/{${pkName}}")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response get${EntityClass}(@PathParam("${pkName}") ${pkType} ${pkName}) {
+ LOG.log(Level.FINE, "REST request to get ${EntityClass} : {}", ${pkName});
+ ${instanceType} ${instanceName} = ${entityRepository}.find(${pkName});
+ return Optional.ofNullable(${instanceName})
+ .map(result -> Response.status(Response.Status.OK).entity(${instanceName}).build())
+ .orElse(Response.status(Response.Status.NOT_FOUND).build());
+ }
+
+ /**
+ * DELETE /:${pkName} : remove the "${pkName}" ${entityInstance}.
+ *
+ * @param ${pkName} the ${pkName} of the ${instanceName} to delete
+ * @return the Response with status 200 (OK)
+ */
+ <#if metrics>@Timed#if>
+ <#if openAPI>@Operation(summary = "remove the ${entityInstance}" )
+ @APIResponse(responseCode = "200", description = "OK")
+ @APIResponse(responseCode = "404", description = "Not Found")#if>
+ @DELETE
+ @Path("/{${pkName}}")
+ public Response remove${EntityClass}(@PathParam("${pkName}") ${pkType} ${pkName}) {
+ LOG.log(Level.FINE, "REST request to delete ${EntityClass} : {}", ${pkName});
+ ${entityRepository}.remove(${entityRepository}.find(${pkName}));
+ return HeaderUtil.createEntityDeletionAlert(Response.ok(), ENTITY_NAME, <#if isPKPrimitive>String.valueOf(${pkName})<#else>${pkName}.toString()#if>).build();
+ }
+
+}
diff --git a/PayaraStarterGenerator/src/main/resources/template/rest/HeaderUtil.java.ftl b/PayaraStarterGenerator/src/main/resources/template/rest/HeaderUtil.java.ftl
new file mode 100644
index 0000000..3d5e14b
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/rest/HeaderUtil.java.ftl
@@ -0,0 +1,49 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+package ${package};
+
+import jakarta.ws.rs.core.Response.ResponseBuilder;
+
+/**
+ * Utility class for HTTP headers creation.
+ *
+ */
+public class HeaderUtil {
+
+ public static ResponseBuilder createAlert(ResponseBuilder builder, String message, String param) {
+ builder.header("X-app-alert", message);
+ builder.header("X-app-params", param);
+ return builder;
+ }
+
+ public static ResponseBuilder createEntityCreationAlert(ResponseBuilder builder, String entityName, String param) {
+ return createAlert(builder, "${frontendAppName}." + entityName + ".created", param);
+ }
+
+ public static ResponseBuilder createEntityUpdateAlert(ResponseBuilder builder, String entityName, String param) {
+ return createAlert(builder, "${frontendAppName}." + entityName + ".updated", param);
+ }
+
+ public static ResponseBuilder createEntityDeletionAlert(ResponseBuilder builder, String entityName, String param) {
+ return createAlert(builder, "${frontendAppName}." + entityName + ".deleted", param);
+ }
+
+ public static ResponseBuilder createFailureAlert(ResponseBuilder builder, String entityName, String errorKey, String defaultMessage) {
+ builder.header("X-app-error", "error." + errorKey);
+ builder.header("X-app-params", entityName);
+ return builder;
+ }
+}
diff --git a/PayaraStarterGenerator/src/main/resources/template/rest/RestConfiguration.java.ftl b/PayaraStarterGenerator/src/main/resources/template/rest/RestConfiguration.java.ftl
new file mode 100644
index 0000000..797c2bd
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/rest/RestConfiguration.java.ftl
@@ -0,0 +1,27 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+package ${package};
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+/**
+ * Configures RESTful Web Services for the application.
+ */
+@ApplicationPath("resources")
+public class RestConfiguration extends Application {
+
+}
diff --git a/PayaraStarterGenerator/src/main/resources/template/service/producer/EntityManagerProducer.java.ftl b/PayaraStarterGenerator/src/main/resources/template/service/producer/EntityManagerProducer.java.ftl
new file mode 100644
index 0000000..ba3c987
--- /dev/null
+++ b/PayaraStarterGenerator/src/main/resources/template/service/producer/EntityManagerProducer.java.ftl
@@ -0,0 +1,38 @@
+<#--
+ Copyright 2024 the original author or authors from the Jeddict project (https://jeddict.github.io/).
+
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ use this file except in compliance with the License. You may obtain a copy of
+ the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ License for the specific language governing permissions and limitations under
+ the License.
+-->
+<#if package??>package ${package};#if>
+
+import jakarta.enterprise.inject.Produces;
+import jakarta.enterprise.context.RequestScoped;
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.PersistenceContext;
+
+/**
+ * Producer for injectable EntityManager
+ *
+ */
+@RequestScoped
+public class EntityManagerProducer {
+
+ @PersistenceContext(unitName = "${appPU}")
+ private EntityManager em;
+
+ @Produces
+ public EntityManager getEntityManager(){
+ return em;
+ }
+
+}
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 89f98ea..6eb5f0a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,6 +72,7 @@
starter-archetypestarter-ui
+ PayaraStarterGenerator
diff --git a/starter-ui/nb-configuration.xml b/starter-ui/nb-configuration.xml
index 11fb4d1..0f49d90 100644
--- a/starter-ui/nb-configuration.xml
+++ b/starter-ui/nb-configuration.xml
@@ -13,7 +13,7 @@ You can copy and paste the single properties, into the pom.xml file and the IDE
That way multiple projects can share the same settings (useful for formatting rules for example).
Any value defined here will override the pom.xml file value but is only applicable to the current project.
-->
- pfv5ee8ide
+ JDK_17
diff --git a/starter-ui/nbactions.xml b/starter-ui/nbactions.xml
new file mode 100644
index 0000000..f4167c4
--- /dev/null
+++ b/starter-ui/nbactions.xml
@@ -0,0 +1,10 @@
+
+
+
+ CUSTOM-payara-micro:dev
+ payara-micro:dev
+
+ payara-micro:dev
+
+
+
diff --git a/starter-ui/pom.xml b/starter-ui/pom.xml
index bc3d27c..ad4a996 100644
--- a/starter-ui/pom.xml
+++ b/starter-ui/pom.xml
@@ -59,7 +59,6 @@
provided
-
org.apache.mavenmaven-embedder
@@ -87,6 +86,31 @@
slf4j-jdk142.0.16
+
+ ${project.groupId}
+ payara-starter-generator
+ ${project.version}
+
+
+ dev.langchain4j
+ langchain4j-open-ai
+ 0.30.0
+
+
+ dev.langchain4j
+ langchain4j-easy-rag
+ 0.30.0
+
+
+ com.theokanning.openai-gpt3-java
+ service
+ 0.18.2
+
+
+ org.eclipse.microprofile.config
+ microprofile-config-api
+ 3.1
+
@@ -125,7 +149,7 @@
maven-compiler-plugin3.13.0
- 11
+ 17
@@ -165,7 +189,12 @@
fish.payara.maven.pluginspayara-micro-maven-plugin
- 2.2
+ 2.4
+
+
+ fish.payara.maven.plugins
+ payara-cloud-maven-plugin
+ 1.0-Alpha2
diff --git a/starter-ui/src/main/java/fish/payara/starter/ERDiagramChat.java b/starter-ui/src/main/java/fish/payara/starter/ERDiagramChat.java
new file mode 100644
index 0000000..b1e1fa5
--- /dev/null
+++ b/starter-ui/src/main/java/fish/payara/starter/ERDiagramChat.java
@@ -0,0 +1,226 @@
+package fish.payara.starter;
+
+import dev.langchain4j.service.SystemMessage;
+
+public interface ERDiagramChat {
+
+ @SystemMessage("""
+You are an API server that responds in a Mermaid erDiagram format.
+Don't say anything else. Respond only with the erDiagram.
+
+Entity Relationship Diagrams
+An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types).
+Comments can be added using %%{ comments here }%%
+So after Defining relation add comment with variable name for JPA Entities of that relation seprated by comma.
+1- Add comment after erDiagram ends with application's bootstrap icon,title, website home-page description,about-us page description(5-10 lines), Top bar menu options(e.g Home, Product, Serices, contact us, about us, ) to show on final website of application.
+ 1(a) -Add Top bar menu options only to global comment after erDiagram ends not After Defining Entity
+ 1(b) -Add website home-page description,about-us page description(5-10 lines) only to global comment after erDiagram ends not After Defining Entity
+2- After Defining Entity
+ Each must have one primary key PK attribute
+ Add comment relate to bootstrap icon (e.g people, person, cart, cash, house), entity title and entity description to show on final website of application.
+3- After Defining Attribute:
+3(a) Add htmllabel if variable name is not descriptive itself. Add it only if attribute is not slef descriptive.
+ Valid Example: string custNumber %%{ htmllabel[Customer Number] }%%
+ Valid Example: date dob %%{ htmllabel[Date of Birth] }%%)
+ HTML label should not be the same as the attribute name by spliting with space; it should provide more meaning and description.
+ Invalid Example: date appointmentDate %%{ htmllabel[Appointment Date] }%% - Not required for appointmentDate as it is self descriptive
+ Invalid Example: string phoneNumber %%{ htmllabel[Phone Number] }%% - Not required for phoneNumber as it is self descriptive
+ Invalid Example: string doctorId PK %%{ htmllabel[Doctor ID] }%% - Not required for doctorId as it is self descriptive
+3(b) Each entity must have only one label or name field. Add a 'display[true]' property to the comment that will serve as the label for the entity.
+ Do not add the 'display[true]' property to more than one attribute.
+ Look for attributes that start or end with 'name' and add the 'display[true]' property to one of them.
+ If no attribute is found that starts or ends with 'name', then decide which other attribute can represent the label.
+3(c) Add 'required[true]' property if attribute is required for entity.
+ Do not add to more than 50% of attribute in entity.
+ Do not add to Primary key PK.
+ Invalid Example: string doctorId PK %%{ required[true] }%%
+3(d) Add 'tooltip[description of attribute]' to attribute to show it on html input UI which will help end user.
+ tooltip must be added to all attributes except FK or PK primary key.
+
+Example:
+CUSTOMER { %%{ icon[people],title[abc],description[xyz] }%%
+ string name %%{ display[true],required[true] }%%
+ long custNumber PK %%{ htmllabel[Customer Number],required[true] }%%
+}
+4- After Defining relation add comment with variable name for JPA Entities of that relation.
+Example:
+CUSTOMER ||--o{ ORDER : places %%{ CUSTOMER[orders],ORDER[customer] }%%
+In this Example, CUSTOMER and ORDER entity have relatationship where as comment defines variable name in JPA Entity classes, suggest varaible name related to label which is places in this example.
+
+Note that practitioners of ER modelling almost always refer to entity types simply as entities. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract instance of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
+
+Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
+
+Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.
+
+ER diagrams can be used for various purposes, ranging from abstract logical models devoid of any implementation details, through to physical models of relational database tables. It can be useful to include attribute definitions on ER diagrams to aid comprehension of the purpose and meaning of entities. These do not necessarily need to be exhaustive; often a small subset of attributes is enough. Mermaid allows them to be defined in terms of their type and name.
+
+Code:
+erDiagram
+ CUSTOMER ||--o{ ORDER : places %%{ CUSTOMER[orders],ORDER[customer] }%%
+ CUSTOMER { %%{ icon[people],title[abc],description[xyz] }%%
+ long custNumber PK %%{ htmllabel[Customer Number],required[true] }%%
+ string name %%{ display[true],required[true],tooltip[description of attribute] }%%
+ string sector %%{ tooltip[description of attribute] }%%
+ }
+ ORDER ||--|{ LINE-ITEM : contains %%{ ORDER[items],LINE-ITEM[order] }%%
+ ORDER { %%{ icon[cart],title[abc],description[xyz] }%%
+ int orderNumber PK %%{ display[true] }%%
+ string deliveryAddress %%{ tooltip[description of attribute] }%%
+ }
+ LINE-ITEM { %%{ icon[folder],title[abc],description[xyz] }%%
+ string productCode PK
+ string productName %%{ display[true],required[true],tooltip[description of attribute] }%%
+ int quantity %%{ tooltip[description of attribute] }%%
+ float pricePerUnit %%{ tooltip[description of attribute] }%%
+ }
+%%{ icon[person],title[abc],home-page-description[xyz],about-us-page-description[xyz],menu[home, services, about us, contact us] }%%
+
+
+When including attributes on ER diagrams, you must decide whether to include foreign keys as attributes. This probably depends on how closely you are trying to represent relational table structures. If your diagram is a logical model which is not meant to imply a relational implementation, then it is better to leave these out because the associative relationships already convey the way that entities are associated. For example, a JSON data structure can implement a one-to-many relationship without the need for foreign key properties, using arrays. Similarly an object-oriented programming language may use pointers or references to collections. Even for models that are intended for relational implementation, you might decide that inclusion of foreign key attributes duplicates information already portrayed by the relationships, and does not add meaning to entities. Ultimately, it's your choice.
+
+
+Syntax
+Entities and Relationships
+Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts:
+
+ [ : ]
+Where:
+
+first-entity is the name of an entity. Names must begin with an alphabetic character or an underscore (from v10.5.0+), and may also contain digits and hyphens.
+relationship describes the way that both entities inter-relate. See below.
+second-entity is the name of the other entity.
+relationship-label describes the relationship from the perspective of the first entity.
+Ensure the relationship label is always a single word:
+
+ [ : ]
+
+If you want the relationship label to be more than one word, you must use double quotes around the phrase
+If you don't want a label at all on a relationship, you must use an empty double-quoted string
+
+For example:
+
+ PROPERTY ||--|{ ROOM : contains
+This statement can be read as a property contains one or more rooms, and a room is part of one and only one property. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
+
+Only the first-entity part of a statement is mandatory. This makes it possible to show an entity with no relationships, which can be useful during iterative construction of diagrams. If any other parts of a statement are specified, then all parts are mandatory.
+
+Relationship Syntax
+The relationship part of each statement can be broken down into three sub-components:
+
+the cardinality of the first entity with respect to the second,
+whether the relationship confers identity on a 'child' entity
+the cardinality of the second entity with respect to the first
+Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a PROPERTY can have one or more ROOM instances associated to it, whereas a ROOM can only be associated with one PROPERTY. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities. Ensure all relationships use valid cardinality markers:
+
+Value (left) Value (right) Meaning
+|o o| Zero or one
+|| || Exactly one
+}o o{ Zero or more (no upper limit)
+}| |{ One or more (no upper limit)
+
+Valid Cardinality Symbols for left:
+Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+|o: Zero or one
+||: Exactly one
+}o: Zero or more
+}|: One or more
+
+Valid Cardinality Symbols for right:
+Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+o|: Zero or one
+||: Exactly one
+o{: Zero or more
+|{: One or more
+
+Identification
+Relationships may be classified as either identifying or non-identifying and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on NAMED-DRIVERs. In modelling this we might start out by observing that a CAR can be driven by many PERSON instances, and a PERSON can drive many CARs - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: PERSON }|..|{ CAR : "driver". Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a NAMED-DRIVER cannot exist without both a PERSON and a CAR - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
+
+Aliases
+
+Value Alias for
+to identifying
+optionally to non-identifying
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ PERSON ||--o{ NAMED-DRIVER : is
+
+
+Attributes
+Attributes can be defined for entities by specifying the entity name followed by a block containing multiple type name pairs, where a block is delimited by an opening { and a closing }. The attributes are rendered inside the entity boxes. For example:
+
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows %%{ CAR[drivers],Driver[vehicle] }%%
+ CAR { %%{ icon[car-front],title[abc],description[xyz] }%%
+ string registrationNumber %%{ display[true] }%%
+ string make %%{ tooltip[description of attribute] }%%
+ string model %%{ tooltip[description of attribute] }%%
+ }
+ PERSON ||--o{ NAMED-DRIVER : is %%{ Person[drivers],Driver[owner] }%%
+ PERSON { %%{ icon[people],title[abc],description[xyz] }%%
+ string firstName %%{ display[true] }%%
+ string lastName %%{ tooltip[description of attribute] }%%
+ int age %%{ tooltip[description of attribute] }%%
+ }
+%%{ icon[pin-map],title[abc],home-page-description[xyz],about-us-page-description[xyz],menu[home, services, about us, contact us] }%%
+
+
+The type values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The name values follow a similar format to type, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
+
+Entity Name Aliases (v10.5.0+)
+An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
+
+Code:
+erDiagram
+ p[Person] {
+ string firstName
+ string lastName
+ }
+ a["Customer Account"] {
+ string email
+ }
+ p ||--o| a : has
+
+
+Attribute Keys and Comments
+Attributes may also have a key or comment defined. Keys can be PK, FK or UK, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., PK, FK).. A comment is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
+
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows %%{ CAR[drivers],Driver[vehicle] }%%
+ CAR { %%{ icon[car-front],title[abc],description[xyz] }%%
+ string registrationNumber %%{ display[true] }%%
+ string make
+ string model
+ string[] parts
+ }
+ PERSON ||--o{ NAMED-DRIVER : is %%{ Person[drivers],Driver[owner] }%%
+ PERSON { %%{ icon[people],title[abc],description[xyz] }%%
+ string driversLicense PK "The license #"
+ string(99) firstName "Only 99 characters are allowed" %%{ display[true] }%%
+ string lastName
+ string phone UK
+ int age
+ }
+ NAMED-DRIVER { %%{ icon[person],title[abc],description[xyz] }%%
+ string carRegistrationNumber PK, FK
+ string driverLicence FK %%{ display[true] }%%
+ }
+%%{ icon[pin-map],title[abc],home-page-description[xyz],about-us-page-description[xyz],menu[home, services, about us, contact us] }%%
+
+
+The user will provide you with a prompt of er diagram context they have. Based on the given prompt containing application usecase,
+suggest erDiagram.
+Whatever the prompt is, look for erDiagram and make your suggestions based on that and any other context you can understand from the prompt.
+
+Remember you are an API server, you only respond in erDiagram.
+
+
+Don't add anything else in the end after you respond with the erDiagram.
+ """)
+ String generateERDiagram(String userMessage);
+}
diff --git a/starter-ui/src/main/java/fish/payara/starter/ERDiagramEnhanceChat.java b/starter-ui/src/main/java/fish/payara/starter/ERDiagramEnhanceChat.java
new file mode 100644
index 0000000..2463b37
--- /dev/null
+++ b/starter-ui/src/main/java/fish/payara/starter/ERDiagramEnhanceChat.java
@@ -0,0 +1,190 @@
+package fish.payara.starter;
+
+import dev.langchain4j.service.SystemMessage;
+
+public interface ERDiagramEnhanceChat {
+
+ @SystemMessage("""
+You are an API server that responds in a Mermaid erDiagram format.
+Don't say anything else. Respond only with the erDiagram.
+
+Entity Relationship Diagrams
+An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types).
+1- After Defining Entity
+ Each must have one primary key PK attribute
+
+Note that practitioners of ER modelling almost always refer to entity types simply as entities. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract instance of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
+
+Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
+
+Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.
+
+ER diagrams can be used for various purposes, ranging from abstract logical models devoid of any implementation details, through to physical models of relational database tables. It can be useful to include attribute definitions on ER diagrams to aid comprehension of the purpose and meaning of entities. These do not necessarily need to be exhaustive; often a small subset of attributes is enough. Mermaid allows them to be defined in terms of their type and name.
+
+Code:
+erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ CUSTOMER {
+ long custNumber PK
+ string name
+ string sector
+ }
+ ORDER ||--|{ LINE-ITEM : contains
+ ORDER {
+ int orderNumber PK
+ string deliveryAddress
+ }
+ LINE-ITEM {
+ string productCode PK
+ string productName
+ int quantity
+ float pricePerUnit
+ }
+
+When including attributes on ER diagrams, you must decide whether to include foreign keys as attributes. This probably depends on how closely you are trying to represent relational table structures. If your diagram is a logical model which is not meant to imply a relational implementation, then it is better to leave these out because the associative relationships already convey the way that entities are associated. For example, a JSON data structure can implement a one-to-many relationship without the need for foreign key properties, using arrays. Similarly an object-oriented programming language may use pointers or references to collections. Even for models that are intended for relational implementation, you might decide that inclusion of foreign key attributes duplicates information already portrayed by the relationships, and does not add meaning to entities. Ultimately, it's your choice.
+
+
+Syntax
+Entities and Relationships
+Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts:
+
+ [ : ]
+Where:
+
+first-entity is the name of an entity. Names must begin with an alphabetic character or an underscore (from v10.5.0+), and may also contain digits and hyphens.
+relationship describes the way that both entities inter-relate. See below.
+second-entity is the name of the other entity.
+relationship-label describes the relationship from the perspective of the first entity.
+Ensure the relationship label is always a single word:
+
+ [ : ]
+
+If you want the relationship label to be more than one word, you must use double quotes around the phrase
+If you don't want a label at all on a relationship, you must use an empty double-quoted string
+
+For example:
+
+ PROPERTY ||--|{ ROOM : contains
+This statement can be read as a property contains one or more rooms, and a room is part of one and only one property. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
+
+Only the first-entity part of a statement is mandatory. This makes it possible to show an entity with no relationships, which can be useful during iterative construction of diagrams. If any other parts of a statement are specified, then all parts are mandatory.
+
+Relationship Syntax
+The relationship part of each statement can be broken down into three sub-components:
+
+the cardinality of the first entity with respect to the second,
+whether the relationship confers identity on a 'child' entity
+the cardinality of the second entity with respect to the first
+Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a PROPERTY can have one or more ROOM instances associated to it, whereas a ROOM can only be associated with one PROPERTY. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities. Ensure all relationships use valid cardinality markers:
+
+Value (left) Value (right) Meaning
+|o o| Zero or one
+|| || Exactly one
+}o o{ Zero or more (no upper limit)
+}| |{ One or more (no upper limit)
+
+Valid Cardinality Symbols for left:
+Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+|o: Zero or one
+||: Exactly one
+}o: Zero or more
+}|: One or more
+
+Valid Cardinality Symbols for right:
+Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+o|: Zero or one
+||: Exactly one
+o{: Zero or more
+|{: One or more
+
+Identification
+Relationships may be classified as either identifying or non-identifying and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on NAMED-DRIVERs. In modelling this we might start out by observing that a CAR can be driven by many PERSON instances, and a PERSON can drive many CARs - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: PERSON }|..|{ CAR : "driver". Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a NAMED-DRIVER cannot exist without both a PERSON and a CAR - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
+
+Aliases
+
+Value Alias for
+to identifying
+optionally to non-identifying
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ PERSON ||--o{ NAMED-DRIVER : is
+
+
+Attributes
+Attributes can be defined for entities by specifying the entity name followed by a block containing multiple type name pairs, where a block is delimited by an opening { and a closing }. The attributes are rendered inside the entity boxes. For example:
+
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ CAR {
+ string registrationNumber
+ string make
+ string model
+ }
+ PERSON ||--o{ NAMED-DRIVER : is
+ PERSON {
+ string firstName
+ string lastName
+ int age
+ }
+
+
+
+The type values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The name values follow a similar format to type, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
+
+Entity Name Aliases (v10.5.0+)
+An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
+
+Code:
+erDiagram
+ p[Person] {
+ string firstName
+ string lastName
+ }
+ a["Customer Account"] {
+ string email
+ }
+ p ||--o| a : has
+
+
+Attribute Keys and Comments
+Attributes may also have a key or comment defined. Keys can be PK, FK or UK, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., PK, FK).. A comment is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
+
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ CAR {
+ string registrationNumber
+ string make
+ string model
+ string[] parts
+ }
+ PERSON ||--o{ NAMED-DRIVER : is
+ PERSON {
+ string driversLicense PK "The license #"
+ string(99) firstName "Only 99 characters are allowed"
+ string lastName
+ string phone UK
+ int age
+ }
+ NAMED-DRIVER {
+ string carRegistrationNumber PK, FK
+ string driverLicence FK
+ }
+
+
+
+The user will provide you with a prompt of er diagram context they have. Based on the given prompt containing application usecase,
+suggest erDiagram.
+Whatever the prompt is, look for erDiagram and make your suggestions based on that and any other context you can understand from the prompt.
+
+Remember you are an API server, you only respond in erDiagram.
+
+
+Don't add anything else in the end after you respond with the erDiagram.
+ """)
+ String generateERDiagram(String userMessage);
+}
diff --git a/starter-ui/src/main/java/fish/payara/starter/ERDiagramPlainChat.java b/starter-ui/src/main/java/fish/payara/starter/ERDiagramPlainChat.java
new file mode 100644
index 0000000..9c0145a
--- /dev/null
+++ b/starter-ui/src/main/java/fish/payara/starter/ERDiagramPlainChat.java
@@ -0,0 +1,247 @@
+package fish.payara.starter;
+
+import dev.langchain4j.service.SystemMessage;
+import dev.langchain4j.service.UserMessage;
+
+public interface ERDiagramPlainChat {
+
+ @SystemMessage("""
+You are an API server that responds in a Mermaid erDiagram format.
+Don't say anything else. Respond only with the erDiagram.
+
+Entity Relationship Diagrams
+An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types).
+1- After Defining Entity
+ Each must have one primary key PK attribute
+
+Note that practitioners of ER modelling almost always refer to entity types simply as entities. For example the CUSTOMER entity type would be referred to simply as the CUSTOMER entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract instance of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
+
+Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
+
+Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.
+
+ER diagrams can be used for various purposes, ranging from abstract logical models devoid of any implementation details, through to physical models of relational database tables. It can be useful to include attribute definitions on ER diagrams to aid comprehension of the purpose and meaning of entities. These do not necessarily need to be exhaustive; often a small subset of attributes is enough. Mermaid allows them to be defined in terms of their type and name.
+
+Code:
+erDiagram
+ CUSTOMER ||--o{ ORDER : places
+ CUSTOMER {
+ long custNumber PK
+ string name
+ string sector
+ }
+ ORDER ||--|{ LINE-ITEM : contains
+ ORDER {
+ int orderNumber PK
+ string deliveryAddress
+ }
+ LINE-ITEM {
+ string productCode PK
+ string productName
+ int quantity
+ float pricePerUnit
+ }
+
+When including attributes on ER diagrams, you must decide whether to include foreign keys as attributes. This probably depends on how closely you are trying to represent relational table structures. If your diagram is a logical model which is not meant to imply a relational implementation, then it is better to leave these out because the associative relationships already convey the way that entities are associated. For example, a JSON data structure can implement a one-to-many relationship without the need for foreign key properties, using arrays. Similarly an object-oriented programming language may use pointers or references to collections. Even for models that are intended for relational implementation, you might decide that inclusion of foreign key attributes duplicates information already portrayed by the relationships, and does not add meaning to entities. Ultimately, it's your choice.
+
+
+Syntax
+Entities and Relationships
+Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts:
+
+ [ : ]
+Where:
+
+first-entity is the name of an entity. Names must begin with an alphabetic character or an underscore (from v10.5.0+), and may also contain digits and hyphens.
+relationship describes the way that both entities inter-relate. See below.
+second-entity is the name of the other entity.
+relationship-label describes the relationship from the perspective of the first entity.
+Ensure the relationship label is always a single word:
+
+ [ : ]
+
+If you want the relationship label to be more than one word, you must use double quotes around the phrase
+If you don't want a label at all on a relationship, you must use an empty double-quoted string
+
+For example:
+
+ PROPERTY ||--|{ ROOM : contains
+This statement can be read as a property contains one or more rooms, and a room is part of one and only one property. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
+
+Only the first-entity part of a statement is mandatory. This makes it possible to show an entity with no relationships, which can be useful during iterative construction of diagrams. If any other parts of a statement are specified, then all parts are mandatory.
+
+Relationship Syntax
+The relationship part of each statement can be broken down into three sub-components:
+
+the cardinality of the first entity with respect to the second,
+whether the relationship confers identity on a 'child' entity
+the cardinality of the second entity with respect to the first
+Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a PROPERTY can have one or more ROOM instances associated to it, whereas a ROOM can only be associated with one PROPERTY. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities. Ensure all relationships use valid cardinality markers:
+
+Value (left) Value (right) Meaning
+|o o| Zero or one
+|| || Exactly one
+}o o{ Zero or more (no upper limit)
+}| |{ One or more (no upper limit)
+
+Valid Cardinality Symbols for left:
+Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+|o: Zero or one
+||: Exactly one
+}o: Zero or more
+}|: One or more
+
+Valid Cardinality Symbols for right:
+Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+o|: Zero or one
+||: Exactly one
+o{: Zero or more
+|{: One or more
+
+Identification
+Relationships may be classified as either identifying or non-identifying and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on NAMED-DRIVERs. In modelling this we might start out by observing that a CAR can be driven by many PERSON instances, and a PERSON can drive many CARs - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: PERSON }|..|{ CAR : "driver". Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a NAMED-DRIVER cannot exist without both a PERSON and a CAR - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
+
+Aliases
+
+Value Alias for
+to identifying
+optionally to non-identifying
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ PERSON ||--o{ NAMED-DRIVER : is
+
+
+Attributes
+Attributes can be defined for entities by specifying the entity name followed by a block containing multiple type name pairs, where a block is delimited by an opening { and a closing }. The attributes are rendered inside the entity boxes. For example:
+
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ CAR {
+ string registrationNumber
+ string make
+ string model
+ }
+ PERSON ||--o{ NAMED-DRIVER : is
+ PERSON {
+ string firstName
+ string lastName
+ int age
+ }
+
+
+
+The type values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The name values follow a similar format to type, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
+
+Entity Name Aliases (v10.5.0+)
+An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
+
+Code:
+erDiagram
+ p[Person] {
+ string firstName
+ string lastName
+ }
+ a["Customer Account"] {
+ string email
+ }
+ p ||--o| a : has
+
+
+Attribute Keys and Comments
+Attributes may also have a key or comment defined. Keys can be PK, FK or UK, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., PK, FK).. A comment is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
+
+Code:
+erDiagram
+ CAR ||--o{ NAMED-DRIVER : allows
+ CAR {
+ string registrationNumber
+ string make
+ string model
+ string[] parts
+ }
+ PERSON ||--o{ NAMED-DRIVER : is
+ PERSON {
+ string driversLicense PK "The license #"
+ string(99) firstName "Only 99 characters are allowed"
+ string lastName
+ string phone UK
+ int age
+ }
+ NAMED-DRIVER {
+ string carRegistrationNumber PK, FK
+ string driverLicence FK
+ }
+
+
+
+The user will provide you with a prompt of er diagram context they have. Based on the given prompt containing application usecase, suggest erDiagram.
+Whatever the prompt is, look for erDiagram and make your suggestions based on that and any other context you can understand from the prompt.
+
+Remember you are an API server, you only respond in erDiagram.
+
+
+Don't add anything else in the end after you respond with the erDiagram.
+ """)
+ String generateERDiagram(String userMessage);
+
+ @SystemMessage("""
+ You are an API server that enlarges an ER diagram by adding more entities, relationships, and attributes.
+ **Do not remove existing entities; only extend and increase the diagram size by adding new entities, relationships, and attributes.**
+ You must always increase the entity count by at least one or more with every enlargement.
+ Entities must always have attributes.
+ Respond only in Mermaid erDiagram format.
+ Ensure the diagram is free from comments.
+
+ Relationship Syntax
+ The relationship part of each statement can be broken down into three sub-components:
+
+ the cardinality of the first entity with respect to the second,
+ whether the relationship confers identity on a 'child' entity
+ the cardinality of the second entity with respect to the first
+ Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a PROPERTY can have one or more ROOM instances associated to it, whereas a ROOM can only be associated with one PROPERTY. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities. Ensure all relationships use valid cardinality markers:
+
+ Value (left) Value (right) Meaning
+ |o o| Zero or one
+ || || Exactly one
+ }o o{ Zero or more (no upper limit)
+ }| |{ One or more (no upper limit)
+
+ Valid Cardinality Symbols for left:
+ Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+ |o: Zero or one
+ ||: Exactly one
+ }o: Zero or more
+ }|: One or more
+
+ Valid Cardinality Symbols for right:
+ Each cardinality symbol is composed of two characters that define the minimum and maximum relationships:
+
+ o|: Zero or one
+ ||: Exactly one
+ o{: Zero or more
+ |{: One or more
+
+ """)
+ String enlargeERDiagram(@UserMessage String erDiagram, @UserMessage String diagramName);
+
+ @SystemMessage("""
+ You are an API server that slightly reduces an ER diagram by simplifying less relevant details.
+ You must not decrease the entity count by more than one with every shrink.
+ Focus on minimizing complexity while preserving the main structure of the ER diagram.
+
+ When shrinking, follow these guidelines:
+ - Reduce one less relevant entity if needed, but not more than one.
+ - Simplify relationships to prevent clutter.
+ - Retain key entity names and primary relationships.
+ - Entities must always have few attributes.
+
+ Respond only in Mermaid erDiagram format.
+ """)
+ String shrinkERDiagram(@UserMessage String erDiagram, @UserMessage String diagramName);
+
+}
diff --git a/starter-ui/src/main/java/fish/payara/starter/ERDiagramResource.java b/starter-ui/src/main/java/fish/payara/starter/ERDiagramResource.java
new file mode 100644
index 0000000..63970cb
--- /dev/null
+++ b/starter-ui/src/main/java/fish/payara/starter/ERDiagramResource.java
@@ -0,0 +1,62 @@
+/*
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
+ * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template
+ */
+package fish.payara.starter;
+
+//import fish.payara.ai.GptService;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/er-diagram")
+public class ERDiagramResource {
+
+ @Inject
+ private LangChainChatService langChainChatService;
+
+ @GET
+ @Path("/generate")
+ public String generateERDiagram(@QueryParam("request") String request) {
+ if (request != null && !request.isBlank()) {
+ return langChainChatService.generateERDiagramSuggestion(request);
+ } else {
+ throw new BadRequestException("Please provide the request");
+ }
+ }
+
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Path(value = "/enlarge")
+ public String enlargeERDiagramSize(@QueryParam(value = "name") String diagramName, String erDiagram) {
+ try {
+ if (erDiagram != null && !erDiagram.isBlank()
+ && diagramName != null && !diagramName.isBlank()) {
+ return langChainChatService.enlargeERDiagramSuggestion(diagramName, erDiagram);
+ } else {
+ throw new BadRequestException("Please provide the diagram");
+ }
+ } catch(Exception e) {
+ e.printStackTrace();
+ throw e;
+ }
+ }
+
+ @POST
+ @Consumes(MediaType.TEXT_PLAIN)
+ @Path("/shrink")
+ public String shrinkERDiagramSize(@QueryParam("name") String diagramName, String erDiagram) {
+ if (erDiagram != null && !erDiagram.isBlank()
+ && diagramName != null && !diagramName.isBlank()) {
+ return langChainChatService.shrinkERDiagramSuggestion(diagramName, erDiagram);
+ } else {
+ throw new BadRequestException("Please provide the diagram");
+ }
+ }
+
+}
diff --git a/starter-ui/src/main/java/fish/payara/starter/LangChainChatService.java b/starter-ui/src/main/java/fish/payara/starter/LangChainChatService.java
new file mode 100644
index 0000000..aa8606c
--- /dev/null
+++ b/starter-ui/src/main/java/fish/payara/starter/LangChainChatService.java
@@ -0,0 +1,34 @@
+package fish.payara.starter;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.inject.Inject;
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import dev.langchain4j.service.AiServices;
+
+@ApplicationScoped
+public class LangChainChatService {
+
+ @Inject
+ OpenAiChatModel model;
+
+ ERDiagramPlainChat erDiagramChat;
+
+ @PostConstruct
+ void init() {
+ erDiagramChat = AiServices.create(ERDiagramPlainChat.class, model);
+ }
+
+ public String generateERDiagramSuggestion(String userMessage) {
+ return erDiagramChat.generateERDiagram(userMessage);
+ }
+
+ public String enlargeERDiagramSuggestion(String diagramName, String erDiagram) {
+ return erDiagramChat.enlargeERDiagram(erDiagram, diagramName);
+ }
+
+ public String shrinkERDiagramSuggestion(String diagramName, String erDiagram) {
+ return erDiagramChat.shrinkERDiagram(erDiagram, diagramName);
+ }
+
+}
diff --git a/starter-ui/src/main/java/fish/payara/starter/OpenAIFactory.java b/starter-ui/src/main/java/fish/payara/starter/OpenAIFactory.java
new file mode 100644
index 0000000..fa1ea54
--- /dev/null
+++ b/starter-ui/src/main/java/fish/payara/starter/OpenAIFactory.java
@@ -0,0 +1,53 @@
+package fish.payara.starter;
+
+import java.time.Duration;
+
+import dev.langchain4j.model.openai.OpenAiChatModel;
+import jakarta.enterprise.context.ApplicationScoped;
+import jakarta.enterprise.inject.Produces;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import com.theokanning.openai.service.OpenAiService;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
+import static java.time.Duration.ofSeconds;
+
+@ApplicationScoped
+public class OpenAIFactory {
+
+ @Inject
+ @ConfigProperty(name = "OPEN_API_KEY")
+ String apiKey;
+ @Inject
+ @ConfigProperty(name = "gpt.model")
+ String gptModel;
+
+
+ @Inject
+ @ConfigProperty(name = "model.temperature")
+ Double temperature;
+ @Inject
+ @ConfigProperty(name = "openai.timeout")
+ int apiTimeout;
+
+ @Produces
+ @Singleton
+ public OpenAiService produceService() {
+ return new OpenAiService(apiKey,
+ Duration.ofSeconds(apiTimeout));
+ }
+
+ @Produces
+ @Singleton
+ public OpenAiChatModel produceModel() {
+ return OpenAiChatModel.builder()
+ .apiKey(apiKey)
+ // .responseFormat("json_object")
+ .modelName(gptModel)
+ .temperature(temperature)
+ .timeout(ofSeconds(60))
+ .logRequests(true)
+ .logResponses(true)
+ .build();
+ }
+
+}
diff --git a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationConfiguration.java b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationConfiguration.java
index 14893bb..2397d56 100644
--- a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationConfiguration.java
+++ b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationConfiguration.java
@@ -1,6 +1,6 @@
/*
*
- * Copyright (c) 2023-24 Payara Foundation and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023-2024 Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
@@ -39,8 +39,6 @@
package fish.payara.starter.resources;
import jakarta.json.bind.annotation.JsonbProperty;
-import java.util.HashMap;
-import java.util.Map;
/**
*
@@ -158,6 +156,16 @@ public class ApplicationConfiguration {
@JsonbProperty(AUTH)
private String auth = "none";
+
+
+ private String erDiagram = null;
+ private boolean generateJPA = true;
+ private boolean generateRepository = true;
+ private boolean generateRest = true;
+ private boolean generateWeb = true;
+ private String jpaSubpackage = "domain";
+ private String repositorySubpackage = "service";
+ private String restSubpackage = "resource";
public String getBuild() {
return build;
@@ -378,6 +386,70 @@ public String getAuth() {
public void setAuth(String auth) {
this.auth = auth;
}
+
+ public String getErDiagram() {
+ return erDiagram;
+ }
+
+ public void setErDiagram(String erDiagram) {
+ this.erDiagram = erDiagram;
+ }
+
+ public boolean isGenerateJPA() {
+ return generateJPA;
+ }
+
+ public void setGenerateJPA(boolean generateJPA) {
+ this.generateJPA = generateJPA;
+ }
+
+ public boolean isGenerateRepository() {
+ return generateRepository;
+ }
+
+ public void setGenerateRepository(boolean generateRepository) {
+ this.generateRepository = generateRepository;
+ }
+
+ public boolean isGenerateRest() {
+ return generateRest;
+ }
+
+ public void setGenerateRest(boolean generateRest) {
+ this.generateRest = generateRest;
+ }
+
+ public boolean isGenerateWeb() {
+ return generateWeb;
+ }
+
+ public void setGenerateWeb(boolean generateWeb) {
+ this.generateWeb = generateWeb;
+ }
+
+ public String getJpaSubpackage() {
+ return jpaSubpackage;
+ }
+
+ public void setJpaSubpackage(String jpaSubpackage) {
+ this.jpaSubpackage = jpaSubpackage;
+ }
+
+ public String getRepositorySubpackage() {
+ return repositorySubpackage;
+ }
+
+ public void setRepositorySubpackage(String repositorySubpackage) {
+ this.repositorySubpackage = repositorySubpackage;
+ }
+
+ public String getRestSubpackage() {
+ return restSubpackage;
+ }
+
+ public void setRestSubpackage(String restSubpackage) {
+ this.restSubpackage = restSubpackage;
+ }
public static int compareVersions(String version1, String version2) {
String[] v1Components = version1.split("\\.");
diff --git a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java
index 4d84456..1dcc8f0 100644
--- a/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java
+++ b/starter-ui/src/main/java/fish/payara/starter/resources/ApplicationGenerator.java
@@ -1,6 +1,6 @@
/*
*
- * Copyright (c) 2024 Payara Foundation and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023-2024 Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
@@ -38,6 +38,9 @@
*/
package fish.payara.starter.resources;
+import fish.payara.starter.application.domain.ERModel;
+import fish.payara.starter.application.generator.CRUDAppGenerator;
+import fish.payara.starter.application.generator.ERDiagramParser;
import static fish.payara.starter.resources.ApplicationConfiguration.ADD_PAYARA_API;
import static fish.payara.starter.resources.ApplicationConfiguration.ARTIFACT_ID;
import static fish.payara.starter.resources.ApplicationConfiguration.AUTH;
@@ -113,6 +116,26 @@ public Future generate(ApplicationConfiguration appProperties) {
LOGGER.info("Creating a compressed application bundle.");
applicationDir = new File(workingDirectory, appProperties.getArtifactId());
+ if (appProperties.getErDiagram() != null) {
+ ERDiagramParser parser = new ERDiagramParser();
+ ERModel erModel = parser.parse(appProperties.getErDiagram());
+
+ CRUDAppGenerator generator = new CRUDAppGenerator(erModel,
+ appProperties.getPackageName(),
+ appProperties.getJpaSubpackage(),
+ appProperties.getRepositorySubpackage(),
+ appProperties.getRestSubpackage()
+ );
+ try {
+ generator.generate(applicationDir,
+ appProperties.isGenerateJPA(),
+ appProperties.isGenerateRepository(),
+ appProperties.isGenerateRest(),
+ appProperties.isGenerateWeb());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
return zipDirectory(applicationDir, workingDirectory);
} catch (IOException ie) {
throw new RuntimeException("Failed to generate application.", ie);
diff --git a/starter-ui/src/main/resources/META-INF/microprofile-config.properties b/starter-ui/src/main/resources/META-INF/microprofile-config.properties
new file mode 100644
index 0000000..9f8683b
--- /dev/null
+++ b/starter-ui/src/main/resources/META-INF/microprofile-config.properties
@@ -0,0 +1,11 @@
+#vaadin.whitelisted-packages=com.vaadin,org.vaadin,dev.hilla,com.example.application
+# OpenAI API key
+# Timeout in seconds for requests to OpenAI. Set to zero to disable the timeout.
+openai.timeout=45
+gpt.model=gpt-3.5-turbo
+#gpt.model=gpt-4
+gpt.image.mode=dall-e-3
+model.temperature=0.7
+cache.enabled=false
+
+
diff --git a/starter-ui/src/main/webapp/WEB-INF/beans.xml b/starter-ui/src/main/webapp/WEB-INF/beans.xml
new file mode 100644
index 0000000..4ca8195
--- /dev/null
+++ b/starter-ui/src/main/webapp/WEB-INF/beans.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/starter-ui/src/main/webapp/assets/main.js b/starter-ui/src/main/webapp/assets/main.js
index 7702110..8d5f76f 100644
--- a/starter-ui/src/main/webapp/assets/main.js
+++ b/starter-ui/src/main/webapp/assets/main.js
@@ -68,7 +68,7 @@ function hideAndDeselectIncludeTests() {
}
// Get all the input fields in the form
-const formInputs = document.querySelectorAll('form input, form select');
+const formInputs = document.querySelectorAll('form input, form select, form textarea');
const form = document.getElementById('appForm');
const inputToConfigurationMap = {
@@ -118,7 +118,7 @@ form.addEventListener('submit', function (event) {
},
body: JSON.stringify(jsonObject),
};
-
+debugger;
fetch('resources/starter', requestOptions)
.then(response => {
if (response.status === 200) {
diff --git a/starter-ui/src/main/webapp/assets/styles.css b/starter-ui/src/main/webapp/assets/styles.css
index 97594dd..07f107b 100644
--- a/starter-ui/src/main/webapp/assets/styles.css
+++ b/starter-ui/src/main/webapp/assets/styles.css
@@ -1,6 +1,6 @@
/*
*
- * Copyright (c) 2023 Payara Foundation and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023-2024 Payara Foundation and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
@@ -256,4 +256,4 @@ input[type="submit"]:hover {
.form-check-label {
padding-top: 7px;
-}
\ No newline at end of file
+}
diff --git a/starter-ui/src/main/webapp/erDiagram/Auction Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Auction Management System.mmd
new file mode 100644
index 0000000..9accfbc
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Auction Management System.mmd
@@ -0,0 +1,42 @@
+erDiagram
+ AUCTION ||--o{ ITEM : contains %%{ AUCTION[items],ITEM[auction] }%%
+ AUCTION { %%{ icon[auction],title[Auction Management System],description[Manage auctions and bidding processes efficiently. Track auction details, items, and bids.] }%%
+ int auctionId PK
+ string name %%{ display[true],required[true],tooltip[Auction name] }%%
+ date startDate %%{ tooltip[Auction start date] }%%
+ date endDate %%{ tooltip[Auction end date] }%%
+ string status %%{ tooltip[Auction status] }%%
+ }
+ ITEM { %%{ icon[item],title[Item],description[Manage auction items and product listings.] }%%
+ int itemId PK
+ string name %%{ display[true],required[true],tooltip[Item name] }%%
+ string description %%{ tooltip[Item description] }%%
+ float startingPrice %%{ tooltip[Starting price] }%%
+ string category %%{ tooltip[Item category] }%%
+ }
+ BIDDER ||--o{ BID : places %%{ BIDDER[bids],BID[bidder] }%%
+ BIDDER { %%{ icon[bidder],title[Bidder],description[Manage bidder information and bidding activity.] }%%
+ int bidderId PK
+ string name %%{ display[true],required[true],tooltip[Bidder's name] }%%
+ string email %%{ tooltip[Bidder's email address] }%%
+ string phone %%{ tooltip[Bidder's phone number] }%%
+ }
+ BID { %%{ icon[bid],title[Bid],description[Track bidding activity and auction bids.] }%%
+ int bidId PK
+ float amount %%{ display[true],required[true],tooltip[Bid amount] }%%
+ date bidTime %%{ tooltip[Bid time] }%%
+ }
+ PAYMENT ||--o{ BID : makes %%{ PAYMENT[bids],BID[payments] }%%
+ PAYMENT { %%{ icon[payment],title[Payment],description[Manage payment transactions for bids.] }%%
+ int paymentId PK
+ float amount %%{ display[true],required[true],tooltip[Payment amount] }%%
+ date paymentDate %%{ tooltip[Payment date] }%%
+ string method %%{ tooltip[Payment method] }%%
+ }
+ SHIPPING ||--o{ ITEM : delivers %%{ SHIPPING[items],ITEM[shipping] }%%
+ SHIPPING { %%{ icon[shipping],title[Shipping],description[Manage shipping and delivery for auction items.] }%%
+ int shippingId PK
+ date deliveryDate %%{ display[true],required[true],tooltip[Delivery date] }%%
+ string trackingNumber %%{ tooltip[Tracking number] }%%
+ }
+%%{ icon[auction],title[Auction Management System],home-page-description[Manage auctions and bidding processes efficiently. Track auction details, items, and bids.],about-us-page-description[Explore our auction management system and streamline your auction operations. Manage items, bidders, and bids seamlessly.],menu[Home, Auctions, Items, Bidders, Reports, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Customer Relationship Management.mmd b/starter-ui/src/main/webapp/erDiagram/Customer Relationship Management.mmd
new file mode 100644
index 0000000..c7c69df
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Customer Relationship Management.mmd
@@ -0,0 +1,32 @@
+erDiagram
+ CUSTOMER ||--o{ CONTACT : has %%{ CUSTOMER[contacts],CONTACT[customer] }%%
+ CUSTOMER { %%{ icon[people],title[Customer],description[Represents customers in the customer relationship management system.] }%%
+ int customerID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string email
+ string phone
+ string address
+ }
+ CONTACT { %%{ icon[envelope],title[Contact],description[Represents contacts associated with customers in the CRM system.] }%%
+ int contactID PK %%{ required[true] }%%
+ string fullName %%{ display[true],required[true] }%%
+ string email
+ string phone
+ string position
+ }
+ OPPORTUNITY ||--o{ CUSTOMER : belongs_to %%{ OPPORTUNITY[customer],CUSTOMER[opportunities] }%%
+ OPPORTUNITY { %%{ icon[handshake],title[Opportunity],description[Represents sales opportunities associated with customers in the CRM system.] }%%
+ int opportunityID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ float amount %%{ required[true] }%%
+ datetime closeDate %%{ required[true] }%%
+ string stage %%{ required[true] }%%
+ }
+ TASK ||--o{ CUSTOMER : relates_to %%{ TASK[customer],CUSTOMER[tasks] }%%
+ TASK { %%{ icon[tasks],title[Task],description[Represents tasks associated with customers in the CRM system.] }%%
+ int taskID PK %%{ required[true] }%%
+ string title %%{ display[true],required[true] }%%
+ datetime dueDate %%{ required[true] }%%
+ string status %%{ required[true] }%%
+ }
+%%{ icon[users],title[Customer Relationship Management],home-page-description[Represents a customer relationship management system managing customers, contacts, opportunities, and tasks.],about-us-page-description[This system helps in managing relationships with customers and tracking sales opportunities.],menu[home, customers, contacts, opportunities, tasks, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Employee Management.mmd b/starter-ui/src/main/webapp/erDiagram/Employee Management.mmd
new file mode 100644
index 0000000..b2a76d5
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Employee Management.mmd
@@ -0,0 +1,20 @@
+erDiagram
+ EMPLOYEE ||--o{ DEPARTMENT : belongs_to %%{ EMPLOYEE[department],DEPARTMENT[employees] }%%
+ EMPLOYEE { %%{ icon[person],title[Employee],description[Represents employees in the employee management system.] }%%
+ int employeeID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string position %%{ required[true] }%%
+ datetime hireDate %%{ required[true] }%%
+ }
+ DEPARTMENT { %%{ icon[briefcase],title[Department],description[Represents departments in the organization.] }%%
+ int departmentID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string location
+ }
+ MANAGER ||--o{ DEPARTMENT : manages %%{ MANAGER[manages],DEPARTMENT[manager] }%%
+ MANAGER { %%{ icon[users],title[Manager],description[Represents managers overseeing departments in the organization.] }%%
+ int managerID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ }
+%%{ icon[id-badge],title[Employee Management],home-page-description[Represents an employee management system managing employees and departments.],about-us-page-description[This system tracks employees, departments, and managers within the organization.],menu[home, employees, departments, managers, about us, contact us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Energy Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Energy Management System.mmd
new file mode 100644
index 0000000..3a82cf2
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Energy Management System.mmd
@@ -0,0 +1,44 @@
+erDiagram
+ UTILITY ||--o{ METER : owns %%{ UTILITY[meters],METER[utility] }%%
+ UTILITY { %%{ icon[lightning-bolt],title[Energy Management System],description[A system for managing energy consumption and utilities. Track meters, usage, and billing efficiently.] }%%
+ int utilityId PK %%{ htmllabel[Utility ID] }%%
+ string name %%{ display[true],required[true],tooltip[Utility name] }%%
+ string location %%{ tooltip[Utility location] }%%
+ }
+ METER ||--o{ USAGE : records %%{ METER[usage],USAGE[meter] }%%
+ METER { %%{ icon[meter],title[Meter],description[Manage energy meters and track usage data.] }%%
+ int meterId PK %%{ display[true] }%%
+ string type %%{ tooltip[Meter type] }%%
+ string serialNumber %%{ tooltip[Meter serial number] }%%
+ }
+ USAGE ||--o{ BILLING : generates %%{ USAGE[billings],BILLING[usage] }%%
+ USAGE { %%{ icon[chart],title[Usage],description[Record energy usage data and analyze consumption patterns.] }%%
+ int usageId PK %%{ display[true] }%%
+ float amount %%{ tooltip[Usage amount] }%%
+ date dateRecorded %%{ tooltip[Date of usage recording] }%%
+ }
+ BILLING { %%{ icon[dollar-sign],title[Billing],description[Generate bills and manage energy billing cycles.] }%%
+ int billingId PK
+ float amount %%{ display[true],tooltip[Billing amount] }%%
+ date dueDate %%{ tooltip[Due date for payment] }%%
+ }
+ PROVIDER ||--o{ UTILITY : supplies %%{ PROVIDER[utilities],UTILITY[provider] }%%
+ PROVIDER { %%{ icon[provider],title[Provider],description[Manage utility providers and supply contracts.] }%%
+ int providerId PK
+ string name %%{ display[true],required[true],tooltip[Provider name] }%%
+ string contactInfo %%{ tooltip[Provider contact information] }%%
+ }
+ CUSTOMER ||--o{ USAGE : consumes %%{ CUSTOMER[usage],USAGE[customer] }%%
+ CUSTOMER { %%{ icon[customer],title[Customer],description[Manage energy customers and consumption data.] }%%
+ int customerId PK
+ string name %%{ display[true],required[true],tooltip[Customer name] }%%
+ string address %%{ tooltip[Customer address] }%%
+ }
+ ALERT ||--o{ USAGE : triggers %%{ ALERT[usage],USAGE[alert] }%%
+ ALERT { %%{ icon[alert],title[Alert],description[Set up alerts for unusual energy consumption patterns.] }%%
+ int alertId PK
+ string description %%{ display[true],required[true],tooltip[Alert description] }%%
+ date dateTriggered %%{ tooltip[Date of alert triggered] }%%
+ }
+%%{ icon[lightning-bolt],title[Energy Management System],home-page-description[A system for managing energy consumption and utilities. Track meters, usage, and billing efficiently.],about-us-page-description[Explore our energy management system and optimize your energy consumption. Connect with utility providers and monitor your usage in real-time.],menu[Home, Meters, Usage, Billing, About Us, Contact Us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Event Management.mmd b/starter-ui/src/main/webapp/erDiagram/Event Management.mmd
new file mode 100644
index 0000000..915ccfa
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Event Management.mmd
@@ -0,0 +1,28 @@
+erDiagram
+ EVENT ||--o{ ATTENDEE : hosts %%{ EVENT[attendees],ATTENDEE[event] }%%
+ EVENT { %%{ icon[calendar],title[Event],description[Represents events organized by the event management system.] }%%
+ int eventID PK %%{ required[true] }%%
+ string eventName %%{ display[true],required[true] }%%
+ datetime eventDateTime %%{ required[true] }%%
+ string location %%{ required[true] }%%
+ }
+ ATTENDEE { %%{ icon[person],title[Attendee],description[Represents attendees participating in events.] }%%
+ int attendeeID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string email
+ string organization
+ }
+ ORGANIZER ||--o{ EVENT : organizes %%{ ORGANIZER[organizes],EVENT[organizer] }%%
+ ORGANIZER { %%{ icon[people],title[Organizer],description[Represents organizers managing events.] }%%
+ int organizerID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string company
+ }
+ VENUE ||--o{ EVENT : hosts %%{ VENUE[events],EVENT[venue] }%%
+ VENUE { %%{ icon[map-marker-alt],title[Venue],description[Represents venues where events are hosted.] }%%
+ int venueID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string location %%{ required[true] }%%
+ int capacity %%{ required[true] }%%
+ }
+%%{ icon[calendar-alt],title[Event Management],home-page-description[Represents an event management system organizing various events.],about-us-page-description[This system manages events, attendees, organizers, and venues.],menu[home, events, organizers, venues, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Flight Reservation System.mmd b/starter-ui/src/main/webapp/erDiagram/Flight Reservation System.mmd
new file mode 100644
index 0000000..2475974
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Flight Reservation System.mmd
@@ -0,0 +1,41 @@
+erDiagram
+ AIRLINE ||--o{ FLIGHT : "operates" %%{ AIRLINE[flights],FLIGHT[airline] }%%
+ AIRLINE { %%{ icon[airline],title[Airline],description[Manage airline information and flight operations.] }%%
+ int airlineId PK
+ string name %%{ display[true],required[true],tooltip[Airline name] }%%
+ string country %%{ tooltip[Country of origin] }%%
+ string headquarters %%{ tooltip[Headquarters location] }%%
+ string website %%{ tooltip[Airline website] }%%
+ }
+ FLIGHT ||--o{ RESERVATION : "reserved on" %%{ FLIGHT[reservations],RESERVATION[flight] }%%
+ FLIGHT { %%{ icon[flight],title[Flight],description[Manage flight schedules and details.] }%%
+ int flightId PK
+ string flightNumber %%{ display[true],required[true],tooltip[Flight number] }%%
+ string origin %%{ tooltip[Flight origin] }%%
+ string destination %%{ tooltip[Flight destination] }%%
+ date departureDate %%{ tooltip[Departure date] }%%
+ date arrivalDate %%{ tooltip[Arrival date] }%%
+ float price %%{ tooltip[Flight price] }%%
+ }
+ PASSENGER ||--o{ RESERVATION : "books" %%{ PASSENGER[reservations],RESERVATION[passenger] }%%
+ PASSENGER { %%{ icon[passenger],title[Passenger],description[Manage passenger information and bookings.] }%%
+ int passengerId PK
+ string name %%{ display[true],required[true],tooltip[Passenger's name] }%%
+ string email %%{ tooltip[Passenger's email address] }%%
+ string phone %%{ tooltip[Passenger's phone number] }%%
+ string nationality %%{ tooltip[Passenger's nationality] }%%
+ }
+ RESERVATION { %%{ icon[reservations],title[Reservation],description[Manage flight reservations and booking details.] }%%
+ int reservationId PK
+ date bookingDate %%{ display[true],required[true],tooltip[Booking date] }%%
+ string status %%{ tooltip[Reservation status] }%%
+ }
+ PAYMENT ||--o{ RESERVATION : "confirms" %%{ PAYMENT[reservations],RESERVATION[payments] }%%
+ PAYMENT { %%{ icon[payment],title[Payment],description[Manage payment transactions for flight reservations.] }%%
+ int paymentId PK
+ float amount %%{ display[true],required[true],tooltip[Payment amount] }%%
+ date paymentDate %%{ tooltip[Payment date] }%%
+ string method %%{ tooltip[Payment method] }%%
+ }
+%%{ icon[flight],title[Flight Reservation System],home-page-description[Manage flight schedules and reservations efficiently. Track flight details and passenger reservations.],about-us-page-description[Explore our flight reservation system and book flights with ease. Manage reservations, passengers, and payments seamlessly.],menu[Home, Flights, Reservations, Passengers, Reports, About Us, Contact Us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Healthcare System.mmd b/starter-ui/src/main/webapp/erDiagram/Healthcare System.mmd
new file mode 100644
index 0000000..02a5e62
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Healthcare System.mmd
@@ -0,0 +1,30 @@
+erDiagram
+ PATIENT ||--o{ APPOINTMENT : has %%{ PATIENT[appointments],APPOINTMENT[patient] }%%
+ PATIENT { %%{ icon[person],title[Patient],description[Represents patients in the healthcare system.] }%%
+ string patientID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string address
+ string insuranceProvider
+ }
+ APPOINTMENT ||--|{ DOCTOR : attends %%{ APPOINTMENT[doctor],DOCTOR[appointments] }%%
+ APPOINTMENT { %%{ icon[calendar],title[Appointment],description[Represents appointments made by patients.] }%%
+ int appointmentID PK %%{ required[true] }%%
+ datetime appointmentDateTime %%{ display[true],required[true] }%%
+ string reason
+ string status
+ }
+ DOCTOR { %%{ icon[stethoscope],title[Doctor],description[Represents doctors in the healthcare system.] }%%
+ string doctorID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string specialization
+ string department
+ }
+ PRESCRIPTION ||--|{ APPOINTMENT : issues %%{ PRESCRIPTION[appointment],APPOINTMENT[prescription] }%%
+ PRESCRIPTION { %%{ icon[file-prescription],title[Prescription],description[Represents prescriptions issued by doctors.] }%%
+ int prescriptionID PK %%{ required[true] }%%
+ string medicationName %%{ display[true],required[true] }%%
+ string dosage
+ string instructions
+ }
+%%{ icon[clinic-medical],title[Healthcare System],home-page-description[Represents a healthcare system providing medical services.],about-us-page-description[This system manages patients, appointments, doctors, and prescriptions.],menu[home, appointments, doctors, prescriptions, about us, contact us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Insurance Claim Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Insurance Claim Management System.mmd
new file mode 100644
index 0000000..ff9cf0e
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Insurance Claim Management System.mmd
@@ -0,0 +1,50 @@
+erDiagram
+ POLICYHOLDER ||--o{ POLICY : owns %%{ POLICYHOLDER[policies],POLICY[holder] }%%
+ POLICYHOLDER { %%{ icon[person],title[Insurance Claim Management System],description[A system for managing insurance policies and claims. Track policyholders, policies, and claims efficiently.] }%%
+ int policyholderId PK %%{ htmllabel[Policyholder ID],required[true] }%%
+ string name %%{ display[true],required[true],tooltip[Policyholder's name] }%%
+ string email %%{ tooltip[Policyholder's email address] }%%
+ }
+ POLICY ||--o{ CLAIM : covers %%{ POLICY[claims],CLAIM[policy] }%%
+ POLICY { %%{ icon[shield-check],title[Policy],description[Manage insurance policies and associated claims.] }%%
+ int policyId PK %%{ display[true] }%%
+ string type %%{ tooltip[Type of insurance policy] }%%
+ date startDate %%{ tooltip[Start date of policy coverage] }%%
+ date endDate %%{ tooltip[End date of policy coverage] }%%
+ }
+ CLAIM ||--o{ DOCUMENT : includes %%{ CLAIM[documents],DOCUMENT[claim] }%%
+ CLAIM { %%{ icon[file-text],title[Claim],description[Process and manage insurance claims submitted by policyholders.] }%%
+ int claimId PK %%{ display[true] }%%
+ string status %%{ tooltip[Claim status] }%%
+ date dateOfLoss %%{ tooltip[Date of loss or incident] }%%
+ }
+ DOCUMENT { %%{ icon[file],title[Document],description[View and manage documents related to insurance claims.] }%%
+ int documentId PK
+ string type %%{ display[true],required[true],tooltip[Type of document] }%%
+ string url %%{ tooltip[Document URL] }%%
+ }
+ INSURER ||--o{ CLAIM : processes %%{ INSURER[claims],CLAIM[insurer] }%%
+ INSURER { %%{ icon[person],title[Insurer],description[Manage insurer profiles and claim processing.] }%%
+ int insurerId PK
+ string name %%{ display[true],required[true],tooltip[Insurer's name] }%%
+ string email %%{ tooltip[Insurer's email address] }%%
+ }
+ PAYMENT ||--o{ CLAIM : processes %%{ PAYMENT[claims],CLAIM[payment] }%%
+ PAYMENT { %%{ icon[cash],title[Payment],description[Manage payments related to insurance claims.] }%%
+ int paymentId PK
+ float amount %%{ display[true],tooltip[Payment amount] }%%
+ date date %%{ tooltip[Payment date] }%%
+ }
+ ADJUSTER ||--|{ CLAIM : assesses %%{ ADJUSTER[claims],CLAIM[adjuster] }%%
+ ADJUSTER { %%{ icon[worker],title[Claims Adjuster],description[Assess and evaluate insurance claims.] }%%
+ int adjusterId PK
+ string name %%{ display[true],required[true],tooltip[Adjuster's name] }%%
+ string department %%{ tooltip[Adjuster's department] }%%
+ }
+ WITNESS ||--o{ CLAIM : provides %%{ WITNESS[claims],CLAIM[witness] }%%
+ WITNESS { %%{ icon[eye],title[Witness],description[Provide witness testimony for insurance claims.] }%%
+ int witnessId PK
+ string name %%{ display[true],required[true],tooltip[Witness's name] }%%
+ string statement %%{ tooltip[Witness statement] }%%
+ }
+%%{ icon[shield-check],title[Insurance Claim Management System],home-page-description[A system for managing insurance policies and claims. Track policyholders, policies, and claims efficiently.],about-us-page-description[Explore our insurance claim management system and streamline your insurance processes. Connect with policyholders, insurers, and adjusters seamlessly.],menu[Home, Policies, Claims, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Inventory System.mmd b/starter-ui/src/main/webapp/erDiagram/Inventory System.mmd
new file mode 100644
index 0000000..4f1d6e8
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Inventory System.mmd
@@ -0,0 +1,13 @@
+erDiagram
+ PRODUCT ||--o{ INVENTORY : contains %%{ PRODUCT[inventory],INVENTORY[product] }%%
+ PRODUCT { %%{ icon[box],title[Product],description[Represents products in the inventory system.] }%%
+ int productID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ float price %%{ required[true] }%%
+ }
+ INVENTORY { %%{ icon[warehouse],title[Inventory],description[Represents inventory items in the inventory system.] }%%
+ int inventoryID PK %%{ required[true] }%%
+ int quantity %%{ display[true],required[true] }%%
+ string location
+ }
+%%{ icon[boxes],title[Inventory System],home-page-description[Represents an inventory system managing products and inventory items.],about-us-page-description[This system tracks product inventory and manages stock levels.],menu[home, products, inventory, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Laboratory Information Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Laboratory Information Management System.mmd
new file mode 100644
index 0000000..8dc43b5
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Laboratory Information Management System.mmd
@@ -0,0 +1,46 @@
+erDiagram
+ LAB_TECHNICIAN ||--o{ SAMPLE : collects %%{ LAB_TECHNICIAN[samples],SAMPLE[technician] }%%
+ LAB_TECHNICIAN { %%{ icon[person],title[Laboratory Information Management System],description[A system for managing laboratory operations and data. Track samples, tests, and results efficiently.] }%%
+ int technicianId PK %%{ htmllabel[Technician ID],required[true] }%%
+ string name %%{ display[true],required[true],tooltip[Technician's name] }%%
+ string email %%{ tooltip[Technician's email address] }%%
+ }
+ SAMPLE ||--o{ TEST : undergoes %%{ SAMPLE[tests],TEST[sample] }%%
+ SAMPLE { %%{ icon[test-tube],title[Sample],description[Manage laboratory samples and associated tests.] }%%
+ int sampleId PK %%{ display[true] }%%
+ string type %%{ tooltip[Type of sample] }%%
+ date collectionDate %%{ tooltip[Date of sample collection] }%%
+ }
+ TEST { %%{ icon[flask],title[Test],description[Conduct laboratory tests and record test results.] }%%
+ int testId PK
+ string name %%{ display[true],required[true],tooltip[Test name] }%%
+ string method %%{ tooltip[Test method] }%%
+ string equipment %%{ tooltip[Test equipment] }%%
+ }
+ PATIENT ||--o{ SAMPLE : provides %%{ PATIENT[samples],SAMPLE[patient] }%%
+ PATIENT { %%{ icon[patient],title[Patient],description[Manage patient information and sample provision.] }%%
+ int patientId PK
+ string name %%{ display[true],required[true],tooltip[Patient's name] }%%
+ string dob %%{ tooltip[Date of birth] }%%
+ string gender %%{ tooltip[Patient's gender] }%%
+ }
+ RESULT { %%{ icon[check-circle],title[Result],description[Record and manage laboratory test results.] }%%
+ int resultId PK
+ string value %%{ display[true],required[true],tooltip[Test result value] }%%
+ string unit %%{ tooltip[Measurement unit] }%%
+ date dateRecorded %%{ tooltip[Date of result recording] }%%
+ }
+ LAB_ANALYST ||--o{ TEST : analyzes %%{ LAB_ANALYST[tests],TEST[analyst] }%%
+ LAB_ANALYST { %%{ icon[scientist],title[Lab Analyst],description[Analyze laboratory tests and interpret results.] }%%
+ int analystId PK
+ string name %%{ display[true],required[true],tooltip[Analyst's name] }%%
+ string department %%{ tooltip[Analyst's department] }%%
+ }
+ LAB_MANAGER ||--o{ SAMPLE : supervises %%{ LAB_MANAGER[samples],SAMPLE[manager] }%%
+ LAB_MANAGER { %%{ icon[manager],title[Lab Manager],description[Supervise laboratory operations and sample management.] }%%
+ int managerId PK
+ string name %%{ display[true],required[true],tooltip[Manager's name] }%%
+ string department %%{ tooltip[Manager's department] }%%
+ }
+%%{ icon[flask],title[Laboratory Information Management System],home-page-description[A system for managing laboratory operations and data. Track samples, tests, and results efficiently.],about-us-page-description[Explore our laboratory information management system and streamline your laboratory processes. Connect with technicians, analysts, and managers seamlessly.],menu[Home, Samples, Tests, About Us, Contact Us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Law Firm Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Law Firm Management System.mmd
new file mode 100644
index 0000000..63a37f3
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Law Firm Management System.mmd
@@ -0,0 +1,54 @@
+erDiagram
+ LAWYER ||--o{ CASE : handles %%{ LAWYER[cases],CASE[lawyer] }%%
+ LAWYER { %%{ icon[lawyer],title[Law Firm Management System],description[Manage cases and legal matters efficiently. Track lawyers, clients, and case details.] }%%
+ int lawyerId PK
+ string name %%{ display[true],required[true],tooltip[Lawyer's name] }%%
+ string email %%{ tooltip[Lawyer's email address] }%%
+ string specialization %%{ tooltip[Lawyer's specialization] }%%
+ string phone %%{ tooltip[Lawyer's phone number] }%%
+ }
+ CLIENT ||--o{ CASE : hires %%{ CLIENT[cases],CASE[client] }%%
+ CLIENT { %%{ icon[client],title[Client],description[Manage client information and legal matters.] }%%
+ int clientId PK
+ string name %%{ display[true],required[true],tooltip[Client's name] }%%
+ string email %%{ tooltip[Client's email address] }%%
+ string phone %%{ tooltip[Client's phone number] }%%
+ string address %%{ tooltip[Client's address] }%%
+ }
+ CASE { %%{ icon[case],title[Case],description[Track case details and legal proceedings.] }%%
+ int caseId PK
+ string name %%{ display[true],required[true],tooltip[Case name] }%%
+ date startDate %%{ tooltip[Case start date] }%%
+ date endDate %%{ tooltip[Case end date] }%%
+ string status %%{ tooltip[Case status] }%%
+ }
+ DOCUMENT ||--o{ CASE : relates %%{ DOCUMENT[cases],CASE[documents] }%%
+ DOCUMENT { %%{ icon[document],title[Document],description[Manage legal documents and case-related files.] }%%
+ int documentId PK
+ string name %%{ display[true],required[true],tooltip[Document name] }%%
+ string type %%{ tooltip[Document type] }%%
+ date uploadDate %%{ tooltip[Upload date] }%%
+ }
+ BILLING ||--o{ CLIENT : invoices %%{ BILLING[clients],CLIENT[billing] }%%
+ BILLING { %%{ icon[invoice],title[Billing],description[Generate and manage client invoices.] }%%
+ int billingId PK
+ float amount %%{ display[true],required[true],tooltip[Billing amount] }%%
+ date dueDate %%{ tooltip[Due date] }%%
+ string status %%{ tooltip[Billing status] }%%
+ }
+ PAYMENT ||--o{ BILLING : pays %%{ PAYMENT[billings],BILLING[payments] }%%
+ PAYMENT { %%{ icon[payment],title[Payment],description[Manage client payments and billing transactions.] }%%
+ int paymentId PK
+ float amount %%{ display[true],required[true],tooltip[Payment amount] }%%
+ date paymentDate %%{ tooltip[Payment date] }%%
+ string method %%{ tooltip[Payment method] }%%
+ }
+ MEETING ||--o{ CASE : schedules %%{ MEETING[cases],CASE[meetings] }%%
+ MEETING { %%{ icon[meeting],title[Meeting],description[Schedule meetings and appointments for case discussions.] }%%
+ int meetingId PK
+ string title %%{ display[true],required[true],tooltip[Meeting title] }%%
+ date dateTime %%{ tooltip[Meeting date and time] }%%
+ string location %%{ tooltip[Meeting location] }%%
+ }
+%%{ icon[case],title[Law Firm Management System],home-page-description[Manage cases and legal matters efficiently. Track lawyers, clients, and case details.],about-us-page-description[Explore our law firm management system and optimize your legal practice. Manage cases, clients, and documents seamlessly.],menu[Home, Cases, Lawyers, Clients, Reports, About Us, Contact Us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Legal Case Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Legal Case Management System.mmd
new file mode 100644
index 0000000..6c14712
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Legal Case Management System.mmd
@@ -0,0 +1,45 @@
+erDiagram
+ CASE ||--o{ TASK : has %%{ CASE[tasks],TASK[case] }%%
+ CASE { %%{ icon[case],title[Legal Case Management System],description[Manage legal cases and tasks efficiently. Track case details, tasks, and deadlines.] }%%
+ int caseId PK
+ string name %%{ display[true],required[true],tooltip[Case name] }%%
+ date startDate %%{ tooltip[Case start date] }%%
+ date endDate %%{ tooltip[Case end date] }%%
+ string status %%{ tooltip[Case status] }%%
+ string type %%{ tooltip[Case type] }%%
+ }
+ TASK { %%{ icon[task],title[Task],description[Manage tasks and deadlines for legal cases.] }%%
+ int taskId PK
+ string description %%{ display[true],required[true],tooltip[Task description] }%%
+ date deadline %%{ tooltip[Task deadline] }%%
+ string status %%{ tooltip[Task status] }%%
+ }
+ CLIENT ||--o{ CASE : hires %%{ CLIENT[cases],CASE[client] }%%
+ CLIENT { %%{ icon[client],title[Client],description[Manage client information and legal matters.] }%%
+ int clientId PK
+ string name %%{ display[true],required[true],tooltip[Client's name] }%%
+ string email %%{ tooltip[Client's email address] }%%
+ string phone %%{ tooltip[Client's phone number] }%%
+ }
+ LAWYER ||--o{ CASE : handles %%{ LAWYER[cases],CASE[lawyer] }%%
+ LAWYER { %%{ icon[lawyer],title[Lawyer],description[Manage lawyer information and legal representation.] }%%
+ int lawyerId PK
+ string name %%{ display[true],required[true],tooltip[Lawyer's name] }%%
+ string email %%{ tooltip[Lawyer's email address] }%%
+ string phone %%{ tooltip[Lawyer's phone number] }%%
+ }
+ DOCUMENT ||--o{ CASE : relates %%{ DOCUMENT[cases],CASE[documents] }%%
+ DOCUMENT { %%{ icon[document],title[Document],description[Manage legal documents and case-related files.] }%%
+ int documentId PK
+ string name %%{ display[true],required[true],tooltip[Document name] }%%
+ string type %%{ tooltip[Document type] }%%
+ date uploadDate %%{ tooltip[Upload date] }%%
+ }
+ HEARING ||--o{ CASE : schedules %%{ HEARING[cases],CASE[hearings] }%%
+ HEARING { %%{ icon[hearing],title[Hearing],description[Schedule hearings and court appearances for legal cases.] }%%
+ int hearingId PK
+ string location %%{ display[true],required[true],tooltip[Hearing location] }%%
+ date dateTime %%{ tooltip[Hearing date and time] }%%
+ string judge %%{ tooltip[Presiding judge] }%%
+ }
+%%{ icon[case],title[Legal Case Management System],home-page-description[Manage legal cases and tasks efficiently. Track case details, tasks, and deadlines.],about-us-page-description[Explore our legal case management system and streamline your legal practice. Manage cases, tasks, and clients seamlessly.],menu[Home, Cases, Tasks, Clients, Lawyers, Reports, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Library Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Library Management System.mmd
new file mode 100644
index 0000000..7e0cc5f
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Library Management System.mmd
@@ -0,0 +1,27 @@
+erDiagram
+ BOOK ||--o{ LOAN : is %%{ BOOK[loans],LOAN[book] }%%
+ BOOK { %%{ icon[book],title[Book],description[Represents books available in the library.] }%%
+ string ISBN PK %%{ required[true] }%%
+ string title %%{ display[true],required[true] }%%
+ string author %%{ required[true] }%%
+ int pages %%{ required[true] }%%
+ }
+ LOAN { %%{ icon[folder],title[Loan],description[Represents book loans to library patrons.] }%%
+ int loanID PK %%{ required[true] }%%
+ datetime loanDate %%{ display[true],required[true] }%%
+ datetime returnDate
+ }
+ PATRON ||--o{ LOAN : borrows %%{ PATRON[loans],LOAN[patron] }%%
+ PATRON { %%{ icon[person],title[Patron],description[Represents patrons of the library.] }%%
+ string patronID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string address
+ string email
+ }
+ LIBRARIAN ||--o{ LOAN : manages %%{ LIBRARIAN[manages],LOAN[librarian] }%%
+ LIBRARIAN { %%{ icon[people],title[Librarian],description[Represents librarians managing the library.] }%%
+ string librarianID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string department
+ }
+%%{ icon[pin-map],title[Library Management System],home-page-description[Represents a library management system.],about-us-page-description[This system manages books, loans, patrons, and librarians in the library.],menu[home, books, loans, patrons, librarians, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Membership Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Membership Management System.mmd
new file mode 100644
index 0000000..7569ab2
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Membership Management System.mmd
@@ -0,0 +1,52 @@
+erDiagram
+ MEMBER ||--o{ MEMBERSHIP : registers %%{ MEMBER[memberships],MEMBERSHIP[member] }%%
+ MEMBER { %%{ icon[person],title[Membership Management System],description[A system for managing membership registrations and benefits. Track members, subscriptions, and privileges efficiently.] }%%
+ int memberId PK %%{ htmllabel[Member ID],required[true] }%%
+ string name %%{ display[true],required[true],tooltip[Member's name] }%%
+ string email %%{ tooltip[Member's email address] }%%
+ date joinDate %%{ tooltip[Date of membership registration] }%%
+ }
+ MEMBERSHIP ||--o{ SUBSCRIPTION : includes %%{ MEMBERSHIP[subscriptions],SUBSCRIPTION[membership] }%%
+ MEMBERSHIP { %%{ icon[card],title[Membership],description[Manage membership subscriptions and benefits.] }%%
+ int membershipId PK %%{ display[true] }%%
+ string type %%{ tooltip[Type of membership] }%%
+ date startDate %%{ tooltip[Start date of membership] }%%
+ date endDate %%{ tooltip[End date of membership] }%%
+ }
+ SUBSCRIPTION { %%{ icon[subscription],title[Subscription],description[View and manage subscription details.] }%%
+ int subscriptionId PK
+ float price %%{ display[true],tooltip[Subscription price] }%%
+ string status %%{ tooltip[Subscription status] }%%
+ }
+ BENEFIT ||--o{ MEMBERSHIP : offers %%{ BENEFIT[memberships],MEMBERSHIP[benefits] }%%
+ BENEFIT { %%{ icon[star],title[Benefit],description[Manage membership benefits and privileges.] }%%
+ int benefitId PK
+ string name %%{ display[true],required[true],tooltip[Benefit name] }%%
+ string description %%{ tooltip[Benefit description] }%%
+ }
+ EVENT ||--o{ MEMBERSHIP : hosts %%{ EVENT[memberships],MEMBERSHIP[event] }%%
+ EVENT { %%{ icon[event],title[Event],description[Organize and manage events for members.] }%%
+ int eventId PK
+ string name %%{ display[true],required[true],tooltip[Event name] }%%
+ date date %%{ tooltip[Event date] }%%
+ string location %%{ tooltip[Event location] }%%
+ }
+ NOTIFICATION ||--o{ MEMBER : sends %%{ NOTIFICATION[members],MEMBER[notifications] }%%
+ NOTIFICATION { %%{ icon[notification],title[Notification],description[Send notifications to members.] }%%
+ int notificationId PK
+ string content %%{ display[true],required[true],tooltip[Notification content] }%%
+ date sentDate %%{ tooltip[Date of notification sent] }%%
+ }
+ PAYMENT ||--o{ MEMBERSHIP : processes %%{ PAYMENT[memberships],MEMBERSHIP[payment] }%%
+ PAYMENT { %%{ icon[cash],title[Payment],description[Process payments related to memberships.] }%%
+ int paymentId PK
+ float amount %%{ display[true],tooltip[Payment amount] }%%
+ date paymentDate %%{ tooltip[Date of payment] }%%
+ }
+ ADMIN ||--o{ MEMBERSHIP : manages %%{ ADMIN[memberships],MEMBERSHIP[admin] }%%
+ ADMIN { %%{ icon[admin],title[Admin],description[Manage membership system administration and settings.] }%%
+ int adminId PK
+ string name %%{ display[true],required[true],tooltip[Admin's name] }%%
+ string role %%{ tooltip[Admin's role] }%%
+ }
+%%{ icon[card],title[Membership Management System],home-page-description[A system for managing membership registrations and benefits. Track members, subscriptions, and privileges efficiently.],about-us-page-description[Explore our membership management system and discover exclusive benefits for our members. Connect with other members and stay updated with our events and notifications.],menu[Home, Memberships, Benefits, About Us, Contact
diff --git a/starter-ui/src/main/webapp/erDiagram/Music Streaming Platform.mmd b/starter-ui/src/main/webapp/erDiagram/Music Streaming Platform.mmd
new file mode 100644
index 0000000..c51f615
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Music Streaming Platform.mmd
@@ -0,0 +1,19 @@
+erDiagram
+ USER ||--o{ PLAYLIST : creates %%{ USER[playlists],PLAYLIST[user] }%%
+ USER { %%{ icon[person],title[Music Streaming Platform],description[A platform for streaming music online. Discover new tracks, create playlists, and enjoy your favorite tunes.] }%%
+ string userId PK %%{ htmllabel[User ID],required[true] }%%
+ string username %%{ display[true],required[true],tooltip[Username for login] }%%
+ string email %%{ tooltip[User's email address] }%%
+ }
+ PLAYLIST ||--|{ SONG : contains %%{ PLAYLIST[songs],SONG[playlist] }%%
+ PLAYLIST { %%{ icon[music-note],title[Playlist],description[Create and manage your playlists. Add your favorite songs and organize them your way.] }%%
+ int playlistId PK %%{ display[true] }%%
+ string title %%{ tooltip[Playlist title] }%%
+ }
+ SONG { %%{ icon[music],title[Song],description[Individual songs available for streaming. Listen to your favorite tracks anytime, anywhere.] }%%
+ string songId PK
+ string title %%{ display[true],required[true],tooltip[Song title] }%%
+ string artist %%{ tooltip[Artist name] }%%
+ string genre %%{ tooltip[Genre of the song] }%%
+ }
+%%{ icon[music],title[Music Streaming Platform],home-page-description[A platform for streaming music online. Discover new tracks, create playlists, and enjoy your favorite tunes.],about-us-page-description[Explore our music streaming platform and enjoy a wide range of songs from various genres. Connect with fellow music enthusiasts and share your favorite playlists.],menu[Home, Discover, Playlists, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Product Catalog.mmd b/starter-ui/src/main/webapp/erDiagram/Product Catalog.mmd
new file mode 100644
index 0000000..77d36b6
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Product Catalog.mmd
@@ -0,0 +1,14 @@
+erDiagram
+ CATEGORY ||--o{ PRODUCT : contains %%{ CATEGORY[products],PRODUCT[category] }%%
+ CATEGORY { %%{ icon[folder-open],title[Category],description[Represents product categories in the product catalog.] }%%
+ int categoryID PK %%{ required[true] }%%
+ string categoryName %%{ display[true],required[true] }%%
+ string description
+ }
+ PRODUCT { %%{ icon[box],title[Product],description[Represents products in the product catalog.] }%%
+ int productID PK %%{ required[true] }%%
+ string productName %%{ display[true],required[true] }%%
+ float price %%{ required[true] }%%
+ int stockQuantity %%{ required[true] }%%
+ }
+%%{ icon[store],title[Product Catalog],home-page-description[Represents a product catalog showcasing various products.],about-us-page-description[This system manages product categories and products within the catalog.],menu[home, products, categories, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Property Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Property Management System.mmd
new file mode 100644
index 0000000..4dc8975
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Property Management System.mmd
@@ -0,0 +1,39 @@
+erDiagram
+ PROPERTY_MANAGER ||--o{ PROPERTY : manages %%{ PROPERTY_MANAGER[properties],PROPERTY[manager] }%%
+ PROPERTY_MANAGER { %%{ icon[manager],title[Property Management System],description[Manage properties and rental units efficiently. Track property managers, tenants, and maintenance tasks.] }%%
+ int managerId PK
+ string name %%{ display[true],required[true],tooltip[Manager's name] }%%
+ string email %%{ tooltip[Manager's email address] }%%
+ }
+ PROPERTY ||--o{ TENANT : rents %%{ PROPERTY[tenants],TENANT[property] }%%
+ PROPERTY { %%{ icon[property],title[Property],description[Manage property details and rental units.] }%%
+ int propertyId PK
+ string name %%{ display[true],required[true],tooltip[Property name] }%%
+ string address %%{ tooltip[Property address] }%%
+ float rent %%{ tooltip[Monthly rent amount] }%%
+ }
+ TENANT { %%{ icon[tenant],title[Tenant],description[Manage tenant information and leases.] }%%
+ int tenantId PK
+ string name %%{ display[true],required[true],tooltip[Tenant's name] }%%
+ string email %%{ tooltip[Tenant's email address] }%%
+ string phone %%{ tooltip[Tenant's phone number] }%%
+ }
+ MAINTENANCE_TASK ||--o{ PROPERTY : assigned %%{ MAINTENANCE_TASK[properties],PROPERTY[maintenanceTasks] }%%
+ MAINTENANCE_TASK { %%{ icon[maintenance],title[Maintenance Task],description[Schedule and track property maintenance tasks.] }%%
+ int taskId PK
+ string description %%{ display[true],required[true],tooltip[Task description] }%%
+ date dueDate %%{ tooltip[Due date] }%%
+ }
+ PAYMENT ||--o{ TENANT : pays %%{ PAYMENT[tenants],TENANT[payments] }%%
+ PAYMENT { %%{ icon[payment],title[Payment],description[Manage tenant payments and rent collection.] }%%
+ int paymentId PK
+ float amount %%{ display[true],required[true],tooltip[Payment amount] }%%
+ date paymentDate %%{ tooltip[Payment date] }%%
+ }
+ LEASE ||--o{ TENANT : signs %%{ LEASE[tenants],TENANT[lease] }%%
+ LEASE { %%{ icon[lease],title[Lease],description[Manage lease agreements and terms.] }%%
+ int leaseId PK
+ date startDate %%{ display[true],required[true],tooltip[Lease start date] }%%
+ date endDate %%{ tooltip[Lease end date] }%%
+ }
+%%{ icon[property],title[Property Management System],home-page-description[Manage properties and rental units efficiently. Track property managers, tenants, and maintenance tasks.],about-us-page-description[Explore our property management system and streamline your property operations. Manage leases, payments, and maintenance seamlessly.],menu[Home, Properties, Tenants, Maintenance, Reports, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Recruitment Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Recruitment Management System.mmd
new file mode 100644
index 0000000..7e50fdf
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Recruitment Management System.mmd
@@ -0,0 +1,52 @@
+erDiagram
+ USER ||--o{ JOB_APPLICATION : applies %%{ USER[applications],JOB_APPLICATION[user] }%%
+ USER { %%{ icon[person],title[User],description[A system for managing recruitment processes. Post job openings, receive applications, and manage candidates efficiently.] }%%
+ string userId PK %%{ htmllabel[User ID],required[true] }%%
+ string username %%{ display[true],required[true],tooltip[Username for login] }%%
+ string email %%{ tooltip[User's email address] }%%
+ }
+ JOB_APPLICATION ||--|{ JOB : applies_for %%{ JOB_APPLICATION[job],JOB[applications] }%%
+ JOB_APPLICATION { %%{ icon[file-text],title[Job Application],description[Track job applications submitted by candidates.] }%%
+ int applicationId PK %%{ display[true] }%%
+ string status %%{ tooltip[Application status] }%%
+ string resume %%{ tooltip[Link to candidate's resume] }%%
+ }
+ JOB { %%{ icon[briefcase],title[Job],description[Manage job openings and applications.] }%%
+ int jobId PK
+ string title %%{ display[true],required[true],tooltip[Job title] }%%
+ string description %%{ tooltip[Job description] }%%
+ date startDate %%{ tooltip[Start date of employment] }%%
+ date endDate %%{ tooltip[End date of employment] }%%
+ }
+ CANDIDATE ||--o{ JOB_APPLICATION : submits %%{ CANDIDATE[applications],JOB_APPLICATION[candidate] }%%
+ CANDIDATE { %%{ icon[person],title[Candidate],description[Manage candidate profiles and applications.] }%%
+ int candidateId PK
+ string name %%{ display[true],required[true],tooltip[Candidate's name] }%%
+ string email %%{ tooltip[Candidate's email address] }%%
+ string phone %%{ tooltip[Candidate's phone number] }%%
+ }
+ INTERVIEW ||--|{ CANDIDATE : schedules %%{ INTERVIEW[candidate],CANDIDATE[interviews] }%%
+ INTERVIEW { %%{ icon[calendar],title[Interview],description[Schedule and manage interviews with candidates.] }%%
+ int interviewId PK
+ date date %%{ display[true],tooltip[Interview date] }%%
+ string location %%{ tooltip[Interview location] }%%
+ }
+ RECRUITER ||--o{ INTERVIEW : schedules %%{ RECRUITER[interviews],INTERVIEW[recruiter] }%%
+ RECRUITER { %%{ icon[person],title[Recruiter],description[Manage recruiter profiles and interview schedules.] }%%
+ int recruiterId PK
+ string name %%{ display[true],required[true],tooltip[Recruiter's name] }%%
+ string department %%{ tooltip[Recruiter's department] }%%
+ }
+ OFFER ||--o{ CANDIDATE : extends %%{ OFFER[candidate],CANDIDATE[offer] }%%
+ OFFER { %%{ icon[document],title[Job Offer],description[Create and manage job offers extended to candidates.] }%%
+ int offerId PK
+ string status %%{ tooltip[Offer status] }%%
+ float salary %%{ tooltip[Offered salary] }%%
+ }
+ FEEDBACK ||--|{ INTERVIEW : provides %%{ FEEDBACK[interview],INTERVIEW[feedback] }%%
+ FEEDBACK { %%{ icon[comment],title[Interview Feedback],description[Provide feedback on candidate interviews.] }%%
+ int feedbackId PK
+ string comments %%{ display[true],tooltip[Interviewer's comments] }%%
+ int rating %%{ tooltip[Interview rating] }%%
+ }
+%%{ icon[briefcase],title[Recruitment Management System],home-page-description[A system for managing recruitment processes. Post job openings, receive applications, and manage candidates efficiently.],about-us-page-description[Explore our recruitment management system and streamline your hiring process. Connect with talented candidates and make informed decisions.],menu[Home, Jobs, Candidates, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Sales Tracking.mmd b/starter-ui/src/main/webapp/erDiagram/Sales Tracking.mmd
new file mode 100644
index 0000000..5b738ea
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Sales Tracking.mmd
@@ -0,0 +1,27 @@
+erDiagram
+ SALE ||--o{ PRODUCT : includes %%{ SALE[products],PRODUCT[sales] }%%
+ SALE { %%{ icon[cash-register],title[Sale],description[Represents sales transactions in the sales tracking system.] }%%
+ int saleID PK %%{ required[true] }%%
+ datetime saleDateTime %%{ required[true] }%%
+ float totalAmount %%{ display[true],required[true],tooltip[Total amount of the sale] }%%
+ }
+ PRODUCT { %%{ icon[box],title[Product],description[Represents products sold in sales transactions.] }%%
+ int productID PK %%{ required[true] }%%
+ string productName %%{ display[true],required[true],tooltip[Name of the product] }%%
+ float price %%{ required[true],tooltip[Price of the product] }%%
+ int quantity %%{ required[true],tooltip[Quantity of the product sold] }%%
+ }
+ CUSTOMER ||--o{ SALE : makes %%{ CUSTOMER[sales],SALE[customer] }%%
+ CUSTOMER { %%{ icon[people],title[Customer],description[Represents customers making purchases in sales transactions.] }%%
+ int customerID PK %%{ display[true],required[true] }%%
+ string name %%{ required[true],tooltip[Name of the customer] }%%
+ string email
+ string address
+ }
+ EMPLOYEE ||--o{ SALE : records %%{ EMPLOYEE[sales],SALE[employee] }%%
+ EMPLOYEE { %%{ icon[person],title[Employee],description[Represents employees recording sales transactions in the sales tracking system.] }%%
+ int employeeID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true],tooltip[Name of the employee] }%%
+ string department
+ }
+%%{ icon[chart-line],title[Sales Tracking],home-page-description[Represents a sales tracking system monitoring sales transactions.],about-us-page-description[This system tracks sales, products, customers, and employees.],menu[home, sales, products, customers, employees, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/School Management.mmd b/starter-ui/src/main/webapp/erDiagram/School Management.mmd
new file mode 100644
index 0000000..74bbbd3
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/School Management.mmd
@@ -0,0 +1,26 @@
+erDiagram
+ STUDENT ||--o{ ENROLLMENT : enrolls %%{ STUDENT[enrollments],ENROLLMENT[student] }%%
+ STUDENT { %%{ icon[person],title[Student],description[Represents students in the school.] }%%
+ string studentID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string address
+ int age %%{ required[true] }%%
+ }
+ ENROLLMENT ||--|{ COURSE : contains %%{ ENROLLMENT[courses],COURSE[enrollments] }%%
+ ENROLLMENT { %%{ icon[folder],title[Enrollment],description[Represents student enrollment in courses.] }%%
+ int enrollmentID PK %%{ display[true],required[true] }%%
+ string semester
+ }
+ COURSE { %%{ icon[book],title[Course],description[Represents courses offered in the school.] }%%
+ string courseCode PK %%{ required[true] }%%
+ string courseName %%{ display[true],required[true] }%%
+ int credits %%{ required[true] }%%
+ }
+ TEACHER ||--o{ COURSE : teaches %%{ TEACHER[teaches],COURSE[teacher] }%%
+ TEACHER { %%{ icon[people],title[Teacher],description[Represents teachers in the school.] }%%
+ string teacherID PK %%{ required[true] }%%
+ string name %%{ display[true],required[true] }%%
+ string specialization
+ }
+%%{ icon[pin-map],title[School Management],home-page-description[Represents a school management system.],about-us-page-description[This system manages student enrollments, courses, and teachers.],menu[home, courses, teachers, about us, contact us] }%%
+
diff --git a/starter-ui/src/main/webapp/erDiagram/Video Streaming Platform.mmd b/starter-ui/src/main/webapp/erDiagram/Video Streaming Platform.mmd
new file mode 100644
index 0000000..66dfab1
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Video Streaming Platform.mmd
@@ -0,0 +1,21 @@
+erDiagram
+ USER ||--o{ VIDEO : watches %%{ USER[watchHistory],VIDEO[viewers] }%%
+ USER { %%{ icon[people],title[Video Streaming Platform],description[A platform for users to watch and stream videos] }%%
+ long userId PK %%{ htmllabel[User ID],required[true] }%%
+ string username %%{ display[true],required[true],tooltip[Username of the user] }%%
+ string email %%{ required[true],tooltip[Email address of the user] }%%
+ }
+ VIDEO ||--|{ CATEGORY : belongs_to %%{ VIDEO[category],CATEGORY[videos] }%%
+ VIDEO { %%{ icon[video-camera],title[Video],description[Individual videos available for streaming] }%%
+ int videoId PK %%{ tooltip[Unique identifier for the video] }%%
+ string title %%{ display[true],required[true],tooltip[Title of the video] }%%
+ string description %%{ tooltip[Description of the video] }%%
+ string duration %%{ tooltip[Duration of the video] }%%
+ string format %%{ tooltip[Format of the video] }%%
+ }
+ CATEGORY { %%{ icon[folder-open],title[Category],description[Categories to organize videos] }%%
+ int categoryId PK
+ string name %%{ display[true],required[true],tooltip[Name of the category] }%%
+ string description %%{ tooltip[Description of the category] }%%
+ }
+%%{ icon[pin-map],title[abc],home-page-description[xyz],about-us-page-description[xyz],menu[home, services, about us, contact us] }%%
diff --git a/starter-ui/src/main/webapp/erDiagram/Waste Management System.mmd b/starter-ui/src/main/webapp/erDiagram/Waste Management System.mmd
new file mode 100644
index 0000000..3d64816
--- /dev/null
+++ b/starter-ui/src/main/webapp/erDiagram/Waste Management System.mmd
@@ -0,0 +1,39 @@
+erDiagram
+ WASTE_PROVIDER ||--o{ WASTE_COLLECTION : "provides" %%{ WASTE_PROVIDER[collection],WASTE_COLLECTION[provider] }%%
+ WASTE_PROVIDER { %%{ icon[waste-provider],title[Waste Provider],description[Manage waste provider information and services.] }%%
+ int providerId PK
+ string name %%{ display[true],required[true],tooltip[Provider name] }%%
+ string address %%{ tooltip[Provider address] }%%
+ string contactNumber %%{ tooltip[Provider contact number] }%%
+ string email %%{ tooltip[Provider email] }%%
+ string website %%{ tooltip[Provider website] }%%
+ }
+ WASTE_COLLECTION { %%{ icon[waste-collection],title[Waste Collection],description[Manage waste collection services and schedules.] }%%
+ int collectionId PK
+ string type %%{ display[true],required[true],tooltip[Collection type] }%%
+ string schedule %%{ tooltip[Collection schedule] }%%
+ string frequency %%{ tooltip[Collection frequency] }%%
+ float cost %%{ tooltip[Collection cost] }%%
+ }
+ CUSTOMER ||--o{ WASTE_COLLECTION : "subscribes to" %%{ CUSTOMER[collection],WASTE_COLLECTION[customer] }%%
+ CUSTOMER { %%{ icon[customer],title[Customer],description[Manage customer information and waste collection subscriptions.] }%%
+ int customerId PK
+ string name %%{ display[true],required[true],tooltip[Customer name] }%%
+ string address %%{ tooltip[Customer address] }%%
+ string contactNumber %%{ tooltip[Customer contact number] }%%
+ string email %%{ tooltip[Customer email] }%%
+ }
+ WASTE_DISPOSAL ||--o{ WASTE_COLLECTION : "handled by" %%{ WASTE_DISPOSAL[collection],WASTE_COLLECTION[disposal] }%%
+ WASTE_DISPOSAL { %%{ icon[waste-disposal],title[Waste Disposal],description[Manage waste disposal methods and facilities.] }%%
+ int disposalId PK
+ string method %%{ display[true],required[true],tooltip[Disposal method] }%%
+ string facility %%{ tooltip[Disposal facility] }%%
+ string location %%{ tooltip[Disposal location] }%%
+ }
+ WASTE_CATEGORY ||--o{ WASTE_COLLECTION : "classified by" %%{ WASTE_CATEGORY[collection],WASTE_COLLECTION[category] }%%
+ WASTE_CATEGORY { %%{ icon[waste-category],title[Waste Category],description[Manage waste categories and classification.] }%%
+ int categoryId PK
+ string name %%{ display[true],required[true],tooltip[Category name] }%%
+ string description %%{ tooltip[Category description] }%%
+ }
+%%{ icon[waste-management],title[Waste Management System],home-page-description[Efficiently manage waste collection and disposal services. Track provider details, customer subscriptions, and disposal methods.],about-us-page-description[Explore our waste management system and contribute to a cleaner environment. Manage waste collection, disposal, and customer subscriptions seamlessly.],menu[Home, Collections, Providers, Customers, Reports, About Us, Contact Us] }%%
diff --git a/starter-ui/src/main/webapp/index.html b/starter-ui/src/main/webapp/index.html
index 3fb812e..e424ea3 100644
--- a/starter-ui/src/main/webapp/index.html
+++ b/starter-ui/src/main/webapp/index.html
@@ -46,15 +46,31 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -62,57 +78,57 @@
+ height="0" width="0" style="display:none;visibility:hidden">
-
-