diff --git a/.github/workflows/onTagCreateRelease.yml b/.github/workflows/onTagCreateRelease.yml new file mode 100644 index 0000000..50df544 --- /dev/null +++ b/.github/workflows/onTagCreateRelease.yml @@ -0,0 +1,79 @@ +name: Create Release + +on: + push: + tags: + - "v*" # Push events to matching v*, i.e. v1.0, v20.15.10 + + +jobs: + build: + name: Create Release + runs-on: ubuntu-latest + steps: + + - name: Checkout code + uses: actions/checkout@v2 + + + - name: Create release name using the tag name + run: | + githubRef=${{ github.ref }} + githubRef="${githubRef/'refs/tags'/''}" + githubRef="${githubRef/'/'/''}" + githubRef="${githubRef/'v'/''}" + echo "releaseLabel=$githubRef" >> $GITHUB_ENV + + + - name: Print release label + run: echo $releaseLabel + + + - name: Create release directory + run: mkdir -p release/bin + + + ### Generate jar file + + - name: Set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + + - name: Install Maven + run: sudo apt install maven + + + - name: Check Maven Version + run: mvn -version + + + - name: Build jar using Maven + run: mvn --batch-mode install verify -U -X + + + ### Create zip file + + - name: Copy jar file from the dist to the release directory + run: cp dist/javaxt-orm*.jar release/bin/javaxt-orm.jar + + + - name: Copy release assets to the release directory + run: cp -r src LICENSE.TXT release + + + - name: Create zip file + run: cd release; zip -r ../javaxt-orm_v${{ env.releaseLabel }}.zip * + + + ### Upload zip file to javaxt.com + + - name: Upload release to server + run: >- + curl -X POST -L + -H "Authorization: ${{ secrets.UPLOAD_AUTH }}" + -F "file=@javaxt-orm_v${{ env.releaseLabel }}.zip;type=application/zip" + "${{ secrets.UPLOAD_URL }}" + diff --git a/.gitignore b/.gitignore index 5a73ffd..ef80201 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ -build/ -dist/ -nbproject/ -build.xml -manifest.mf \ No newline at end of file +/build/ +/dist/ +/target/ +/nbproject/ +/build.xml +/manifest.mf + diff --git a/LICENSE.TXT b/LICENSE.TXT index 50147b1..f8d7956 100644 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -1,4 +1,4 @@ -Copyright (c) 2006-2023, Peter Borissow +Copyright (c) 2006-2024, Peter Borissow Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/pom.xml b/pom.xml index 352a3d5..7bbfb54 100644 --- a/pom.xml +++ b/pom.xml @@ -33,7 +33,7 @@ javaxt javaxt-core - 2.0.0 + 2.1.7 org.openjdk.nashorn @@ -52,6 +52,20 @@ ${src.dir} + + + + false + src + + **/*.java + + + + + + + diff --git a/src/javaxt/orm/Compiler.java b/src/javaxt/orm/Compiler.java new file mode 100644 index 0000000..d38ec8b --- /dev/null +++ b/src/javaxt/orm/Compiler.java @@ -0,0 +1,272 @@ +package javaxt.orm; + +import java.io.*; +import java.net.*; +import java.util.*; +import javax.tools.*; +import java.nio.charset.Charset; + +import static javaxt.utils.Console.console; + +//****************************************************************************** +//** Compiler +//****************************************************************************** +/** + * Used to compile ORM models into Java classes. The classes are ephemeral + * meaning they are not saved anywhere on disk (no class files or jar files). + * Instead the classes are stored in memory and are tied to the lifecycle of + * the JVM. + * + ******************************************************************************/ + +public class Compiler { + + private Class[] classes; + private HashMap outputFiles; + private URLClassLoader urlClassLoader; + private DiagnosticListener listener = null; + private Locale locale = null; + private Charset charset = Charset.defaultCharset(); + + + //************************************************************************** + //** Constructor + //************************************************************************** + public Compiler(Model[] models) throws Exception { + + if (models==null || models.length==0){ + classes = new Class[0]; + } + else{ + + outputFiles = new HashMap<>(); + urlClassLoader = getClassLoader(); + + + + //Convert models into an ArrayList + ArrayList arr = new ArrayList<>(); + for (Model model : models){ + arr.add(model); + } + + + //Compile classes + ArrayList classes = new ArrayList<>(); + int numErrors = 0; + while (classes.size()(models.length*models.length)){ + throw new Exception("Failed to compile " + getClassName(arr.get(0))); + } + } + + + //Convert the class list into an array. Sorts the classes to match + //the order of the input models. + this.classes = new Class[classes.size()]; + for (int i=0; i arr = new ArrayList<>(); + Iterator it = outputFiles.keySet().iterator(); + while (it.hasNext()){ + String name = it.next(); + if (name.startsWith(packageName + ".")){ + if (!name.equals(className)){ //don't include the current file since it hasn't been compiled yet + arr.add(outputFiles.get(name)); + } + } + } + if (!arr.isEmpty()) return arr; + } + + return super.list(location, packageName, kinds, recurse); + } + + + + public String inferBinaryName(JavaFileManager.Location location, JavaFileObject file) { + + if (location==StandardLocation.CLASS_PATH){ + Iterator it = outputFiles.keySet().iterator(); + while (it.hasNext()){ + String name = it.next(); + SimpleJavaFileObject f = outputFiles.get(name); + if (file==f) return name; + } + } + + return super.inferBinaryName(location, file); + } + }; + + + + + //Compile class + JavaCompiler.CompilationTask task = c.getTask( + null, + fileManager, + listener, + Collections.emptySet(), + Collections.emptySet(), + Collections.singleton(src) + ); + + if (task.call()) { + return urlClassLoader.loadClass(className); + } + else{ + throw new Exception("Failed to compile " + className); + } + } + + + //************************************************************************** + //** getClassLoader + //************************************************************************** + /** Returns a custom class loader used to find classes created by this class + */ + private URLClassLoader getClassLoader(){ + return new URLClassLoader(new URL[0]){ + protected Class findClass(final String name) throws ClassNotFoundException { + + + SimpleJavaFileObject f = outputFiles.get(name); + if (f!=null){ + try (InputStream is = f.openInputStream()) { + + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + int x; + byte[] buffer = new byte[256]; + while ((x = is.read(buffer, 0, buffer.length)) != -1) { + out.write(buffer, 0, x); + } + + out.flush(); + byte[] classBytes = out.toByteArray(); + return defineClass(name, classBytes, 0, classBytes.length); + + } + catch(Exception e){ + e.printStackTrace(); + } + } + + return super.findClass(name); + } + }; + } + + + //************************************************************************** + //** getClassName + //************************************************************************** + private String getClassName(Model model){ + return model.getPackageName() + "." + model.getName(); + } + + +} \ No newline at end of file diff --git a/src/javaxt/orm/Field.java b/src/javaxt/orm/Field.java index d73f21b..1f607a5 100644 --- a/src/javaxt/orm/Field.java +++ b/src/javaxt/orm/Field.java @@ -18,6 +18,7 @@ public class Field { private boolean unique = false; private Object defaultValue = null; private Integer length; + private boolean isModel = false; private ForeignKey foreignKey; @@ -26,12 +27,18 @@ public class Field { //************************************************************************** /** Creates a new instance of this class. */ - protected Field(String name, String type){ + protected Field(String name, String type, Model model){ this.name = name; this.columnName = Utils.camelCaseToUnderScore(name); - if (type.equalsIgnoreCase("int")){ - type = "integer"; + //Set columnType and tweak type as needed + if (type.equalsIgnoreCase("int") || type.equalsIgnoreCase("integer")){ + type = "Integer"; + columnType = "integer"; + } + else if (type.equalsIgnoreCase("int[]")){ + type = "Integer[]"; + columnType = "integer array"; } else if (type.equalsIgnoreCase("long")){ columnType = "bigint"; @@ -44,9 +51,13 @@ else if (type.equalsIgnoreCase("decimal") || type.equalsIgnoreCase("numeric")){ type = "BigDecimal"; columnType = "numeric"; } - else if (type.equalsIgnoreCase("text") || type.equalsIgnoreCase("string")){ - type = "string"; - columnType = "text"; + else if (type.equalsIgnoreCase("text") || type.equalsIgnoreCase("string") || type.equalsIgnoreCase("password")){ + type = "String"; + columnType = "varchar"; //varchar without the length specifier and text are equivalent + } + else if (type.equalsIgnoreCase("text[]") || type.equalsIgnoreCase("string[]")){ + type = "String[]"; + columnType = "varchar array"; //same as "text array" } else if (type.equalsIgnoreCase("char")){ type = "string"; @@ -74,24 +85,30 @@ else if (type.equalsIgnoreCase("geometry")){ type = "Geometry"; columnType = "geometry(GeometryZ)"; } - else if (type.equalsIgnoreCase("password")){ - //type = "String"; - columnType = "text"; - } else{ //Model? if (type.endsWith("[]")){ //Array of models String modelName = type.substring(0, type.length()-2); - type = "ArrayList<" + modelName + ">"; + type = "ArrayList<" + Utils.capitalize(modelName) + ">"; } else{ //Single model + isModel = true; columnName = columnName + "_id"; - foreignKey = new ForeignKey(columnName, type); columnType = "bigint"; + + //Typically, when we have a model field we want to create a + //foreign key to tie the field to the model. The only exception + //is if the model field references the parent model - which is + //very rare (most models don't have a model field with the same + //type). + if (!type.equals(model.getName())){ + foreignKey = new ForeignKey(columnName, type); + } + } } @@ -127,7 +144,7 @@ public boolean isLastModifiedDate(){ } public boolean isModel(){ - return foreignKey!=null; + return isModel; } public boolean isArray(){ @@ -178,7 +195,8 @@ public void setDefaultValue(Object defaultValue){ public void setLength(int length){ - if (columnType.equalsIgnoreCase("text")){ + if (columnType.equalsIgnoreCase("text") || + columnType.equalsIgnoreCase("varchar")){ columnType = "VARCHAR(" + length + ")"; this.length = length; } diff --git a/src/javaxt/orm/Main.java b/src/javaxt/orm/Main.java index 4c905c0..73739e1 100644 --- a/src/javaxt/orm/Main.java +++ b/src/javaxt/orm/Main.java @@ -1,59 +1,73 @@ -package javaxt.orm; -import java.util.*; -import javaxt.io.Jar; -import static javaxt.utils.Console.console; - -//****************************************************************************** -//** Main -//****************************************************************************** -/** - * Command line interface used to parse a model file and generate Java and SQL - * output files. - * - ******************************************************************************/ - -public class Main { - - - //************************************************************************** - //** main - //************************************************************************** - /** Entry point for the application - * @param arguments Command line arguments - */ - public static void main(String[] arguments) throws Exception { - HashMap args = console.parseArgs(arguments); - - - //Print version as needed - if (args.containsKey("-version")){ - Jar jar = new Jar(Main.class); - javaxt.io.File jarFile = new javaxt.io.File(jar.getFile()); - String version = jar.getVersion(); - if (version==null) version = "Unknown"; - System.out.println(jarFile.getName(false) + " version \"" + version + "\""); - return; - } - - - //Parse inputs - String input = args.get("-input"); - String output = args.get("-output"); - if (input==null){ - input = arguments[0]; - if (arguments.length>1) output = arguments[1]; - } - javaxt.io.File inputFile = new javaxt.io.File(input); - if (!inputFile.exists()) throw new IllegalArgumentException("Input file not found"); - javaxt.io.Directory outputDirectory = output==null ? - inputFile.getDirectory() : new javaxt.io.Directory(output); - - - //Create models - Model[] models = new Parser(inputFile.getText()).getModels(); - - - //Create files - Writer.write(models, outputDirectory); - } +package javaxt.orm; +import java.util.*; +import javaxt.io.Jar; +import static javaxt.utils.Console.console; + +//****************************************************************************** +//** Main +//****************************************************************************** +/** + * Command line interface used to parse a model file and generate Java and SQL + * output files. + * + ******************************************************************************/ + +public class Main { + + + //************************************************************************** + //** main + //************************************************************************** + /** Entry point for the application + * @param arguments Command line arguments + */ + public static void main(String[] arguments) throws Exception { + HashMap args = console.parseArgs(arguments); + + + //Print version as needed + if (args.containsKey("-version")){ + Jar jar = new Jar(Main.class); + javaxt.io.File jarFile = new javaxt.io.File(jar.getFile()); + String version = jar.getVersion(); + if (version==null) version = "Unknown"; + System.out.println(jarFile.getName(false) + " version \"" + version + "\""); + return; + } + + + //Parse inputs + String input = args.get("-input"); + String output = args.get("-output"); + if (input==null){ + if (arguments.length>0) input = arguments[0]; + if (arguments.length>1) output = arguments[1]; + } + + + //Get input file + javaxt.io.File inputFile; + try{ + if (input==null) throw new IllegalArgumentException("Input file is required"); + inputFile = new javaxt.io.File(input); + if (!inputFile.exists()) throw new IllegalArgumentException("Input file not found"); + } + catch(Exception e){ + System.out.println(e.getMessage()); + return; + } + + + //Get output directory + javaxt.io.Directory outputDirectory = output==null ? + inputFile.getDirectory() : new javaxt.io.Directory(output); + + + //Create models + Model[] models = new Parser(inputFile.getText()).getModels(); + + + //Create files + Writer.write(models, outputDirectory); + } } \ No newline at end of file diff --git a/src/javaxt/orm/Model.java b/src/javaxt/orm/Model.java index fee6fd8..7b4de08 100644 --- a/src/javaxt/orm/Model.java +++ b/src/javaxt/orm/Model.java @@ -16,6 +16,7 @@ public class Model { private String name; private TreeSet implementations; private ArrayList fields; + private ArrayList indexes; private static final String template = getTemplate(); private String tableName; private String escapedTableName; @@ -34,6 +35,7 @@ protected Model(String modelName, JSONObject modelInfo, String packageName, Hash this.name = modelName; this.implementations = new TreeSet<>(); this.fields = new ArrayList<>(); + this.indexes = new ArrayList<>(); this.options = options; this.packageName = packageName; this.tableName = Utils.camelCaseToUnderScore(name).toLowerCase(); @@ -61,17 +63,21 @@ protected Model(String modelName, JSONObject modelInfo, String packageName, Hash //Parse fields JSONArray arr = modelInfo.get("fields").toJSONArray(); - if (arr!=null) - for (int i=0; i includes = new TreeSet<>(); + + //Special case for models that implement java.security.Principal + boolean hasNameField = false; + String getSecurityPrincipalName = ""; + if (implementations.contains("java.security.Principal")){ + + //Check if fields have a name field + for (Field field : fields){ + if (field.getName().equalsIgnoreCase("name")){ + hasNameField = true; + break; + } + } + + //If no name field is found, we'll need to add a public getName() + if (!hasNameField){ + getSecurityPrincipalName += " public String getName(){\r\n"; + getSecurityPrincipalName += " return null;\r\n"; + getSecurityPrincipalName += " }\r\n\r\n"; + } + } + + + + //Loop through all the fields and generate java code for (int i=0; i it = fields.iterator(); - while (it.hasNext()){ - Field field = it.next(); + HashMap columnNames = new HashMap<>(); + + //Create a hashmap of indexes using field names as keys + HashMap indexes = new HashMap<>(); + for (JSONObject index : this.indexes){ + String fieldName = index.get("field").toString(); + indexes.put(fieldName, index); + } + + + //Add indexes to foreign key and geo fields + for (Field field : fields){ + if (!field.isArray()){ + ForeignKey foreignKey = field.getForeignKey(); if (foreignKey!=null){ + String columnName = foreignKey.getColumnName().toUpperCase(); String foreignTable = foreignKey.getForeignTable().toUpperCase(); + columnNames.put(field.getName(), columnName); + + + //Automatically index foreign key fields str.append("CREATE INDEX "); str.append(indexPrefix); str.append(foreignTable); @@ -982,9 +1116,15 @@ public String getIndexSQL(){ str.append("("); str.append(columnName); str.append(");\r\n"); + + indexes.remove(field.getName()); } else{ + String columnName = field.getColumnName().toUpperCase(); + columnNames.put(field.getName(), columnName); + + //Automatically index geospatial fields if (field.getColumnType().startsWith("geo")){ str.append("CREATE INDEX "); str.append(indexPrefix); @@ -994,6 +1134,8 @@ public String getIndexSQL(){ str.append(" USING GIST("); str.append(columnName); str.append(");\r\n"); + + indexes.remove(field.getName()); } } } @@ -1001,6 +1143,66 @@ public String getIndexSQL(){ //Handled by getDiamondTableSQL() } } + + + + //Add user defined indexes + for (String key : indexes.keySet()){ + JSONObject index = indexes.get(key); + String name = index.get("name").toString(); + String type = index.get("type").toString(); + + + ArrayList columns = new ArrayList<>(); + if (index.get("field").toObject() instanceof String){ + String fieldName = index.get("field").toString(); + String columnName = columnNames.get(fieldName); + if (columnName!=null){ + columns.add(columnName); + } + } + else{ + JSONArray arr = index.get("field").toJSONArray(); + if (arr!=null){ + for (JSONValue v : arr){ + String fieldName = v.toString(); + String columnName = columnNames.get(fieldName); + if (columnName!=null){ + columns.add(columnName); + } + } + } + } + if (columns.isEmpty()) continue; + + + + + + str.append("CREATE "); + if (type!=null) str.append(type + " "); + str.append("INDEX "); + if (name==null){ + str.append(indexPrefix); + for (int i=0; i0) str.append("_"); + str.append(columns.get(i)); + } + } + else{ + str.append(name.toUpperCase()); + } + + str.append(" ON "); + str.append(escapedTableName); + str.append("("); + for (int i=0; i0) str.append(", "); + str.append(columns.get(i)); + } + str.append(");\r\n"); + } + return str.toString(); } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy